source: main/waeup.sirp/trunk/src/waeup/sirp/students/tests/test_browser.py @ 7188

Last change on this file since 7188 was 7188, checked in by Henrik Bettermann, 13 years ago

Add test for MyRolesPage? in w.s.b.pages. Even if this page is defined in another package, it's a good place to test it.

permissions.py: Fix typo

  • Property svn:keywords set to Id
File size: 66.6 KB
Line 
1## $Id: test_browser.py 7188 2011-11-25 06:54:08Z henrik $
2##
3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18"""
19Test the student-related UI components.
20"""
21import shutil
22import tempfile
23from StringIO import StringIO
24from datetime import datetime
25import grok
26from zope.event import notify
27from zope.component import createObject
28from zope.component.hooks import setSite, clearSite
29from zope.security.interfaces import Unauthorized
30from zope.securitypolicy.interfaces import IPrincipalRoleManager
31from zope.testbrowser.testing import Browser
32from hurry.workflow.interfaces import IWorkflowInfo
33from waeup.sirp.testing import FunctionalLayer, FunctionalTestCase
34from waeup.sirp.app import University
35from waeup.sirp.configuration import SessionConfiguration
36from waeup.sirp.students.student import Student
37from waeup.sirp.university.faculty import Faculty
38from waeup.sirp.university.department import Department
39from waeup.sirp.interfaces import IUserAccount
40from waeup.sirp.authentication import LocalRoleSetEvent
41from waeup.sirp.hostels.hostel import Hostel, Bed, NOT_OCCUPIED
42
43PH_LEN = 2059  # Length of placeholder file
44
45def lookup_submit_value(name, value, browser):
46    """Find a button with a certain value."""
47    for num in range(0, 100):
48        try:
49            button = browser.getControl(name=name, index=num)
50            if button.value.endswith(value):
51                return button
52        except IndexError:
53            break
54    return None
55
56class StudentsFullSetup(FunctionalTestCase):
57    # A test case that only contains a setup and teardown
58    #
59    # Complete setup for students handlings is rather complex and
60    # requires lots of things created before we can start. This is a
61    # setup that does all this, creates a university, creates PINs,
62    # etc.  so that we do not have to bother with that in different
63    # test cases.
64
65    layer = FunctionalLayer
66
67    def setUp(self):
68        super(StudentsFullSetup, self).setUp()
69
70        # Setup a sample site for each test
71        app = University()
72        self.dc_root = tempfile.mkdtemp()
73        app['datacenter'].setStoragePath(self.dc_root)
74
75        # Prepopulate the ZODB...
76        self.getRootFolder()['app'] = app
77        # we add the site immediately after creation to the
78        # ZODB. Catalogs and other local utilities are not setup
79        # before that step.
80        self.app = self.getRootFolder()['app']
81        # Set site here. Some of the following setup code might need
82        # to access grok.getSite() and should get our new app then
83        setSite(app)
84
85        # Add student with subobjects
86        student = Student()
87        student.fullname = u'Anna Tester'
88        student.reg_number = u'123'
89        student.matric_number = u'234'
90        student.sex = u'm'
91        student.email = 'aa@aa.ng'
92        student.phone = 1234
93        self.app['students'].addStudent(student)
94        self.student_id = student.student_id
95        self.student = self.app['students'][self.student_id]
96
97        # Set password
98        IUserAccount(
99            self.app['students'][self.student_id]).setPassword('spwd')
100
101        self.login_path = 'http://localhost/app/login'
102        self.container_path = 'http://localhost/app/students'
103        self.manage_container_path = self.container_path + '/@@manage'
104        self.add_student_path = self.container_path + '/addstudent'
105        self.student_path = self.container_path + '/' + self.student_id
106        self.manage_student_path = self.student_path + '/manage_base'
107        self.clearance_student_path = self.student_path + '/view_clearance'
108        self.personal_student_path = self.student_path + '/view_personal'
109        self.edit_clearance_student_path = self.student_path + '/edit_clearance'
110        self.edit_personal_student_path = self.student_path + '/edit_personal'
111        self.studycourse_student_path = self.student_path + '/studycourse'
112        self.payments_student_path = self.student_path + '/payments'
113        self.acco_student_path = self.student_path + '/accommodation'
114        self.history_student_path = self.student_path + '/history'
115
116        # Create 5 access codes with prefix'PWD'
117        pin_container = self.app['accesscodes']
118        pin_container.createBatch(
119            datetime.now(), 'some_userid', 'PWD', 9.99, 5)
120        pins = pin_container['PWD-1'].values()
121        self.pwdpins = [x.representation for x in pins]
122        self.existing_pwdpin = self.pwdpins[0]
123        parts = self.existing_pwdpin.split('-')[1:]
124        self.existing_pwdseries, self.existing_pwdnumber = parts
125        # Create 5 access codes with prefix 'CLR'
126        pin_container.createBatch(
127            datetime.now(), 'some_userid', 'CLR', 9.99, 5)
128        pins = pin_container['CLR-1'].values()
129        pins[0].owner = u'Hans Wurst'
130        self.existing_clrac = pins[0]
131        self.existing_clrpin = pins[0].representation
132        parts = self.existing_clrpin.split('-')[1:]
133        self.existing_clrseries, self.existing_clrnumber = parts
134        # Create 2 access codes with prefix 'HOS'
135        pin_container.createBatch(
136            datetime.now(), 'some_userid', 'HOS', 9.99, 2)
137        pins = pin_container['HOS-1'].values()
138        self.existing_hosac = pins[0]
139        self.existing_hospin = pins[0].representation
140        parts = self.existing_hospin.split('-')[1:]
141        self.existing_hosseries, self.existing_hosnumber = parts
142
143        # Populate university
144        self.certificate = createObject('waeup.Certificate')
145        self.certificate.code = u'CERT1'
146        self.certificate.application_category = 'basic'
147        self.certificate.study_mode = 'ug_ft'
148        self.certificate.start_level = 100
149        self.certificate.end_level = 500
150        self.app['faculties']['fac1'] = Faculty()
151        self.app['faculties']['fac1']['dep1'] = Department()
152        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
153            self.certificate)
154        self.course = createObject('waeup.Course')
155        self.course.code = 'COURSE1'
156        self.course.semester = 1
157        self.course.credits = 10
158        self.course.passmark = 40
159        self.app['faculties']['fac1']['dep1'].courses.addCourse(
160            self.course)
161        self.app['faculties']['fac1']['dep1'].certificates['CERT1'].addCourseRef(
162            self.course, level=100)
163
164        # Configure university
165        self.app['configuration'].accommodation_states = ['admitted']
166        self.app['configuration'].accommodation_session = 2004
167        configuration = SessionConfiguration()
168        # These attributes must also exist in the customization packages.
169        configuration.academic_session = 2004
170        configuration.fee_1 = 20000
171        configuration.boocking_fee = 500
172        self.app['configuration'].addSessionConfiguration(configuration)
173
174        # Create a hostel with two beds
175        hostel = Hostel()
176        hostel.hostel_id = u'hall-1'
177        hostel.hostel_name = u'Hall 1'
178        self.app['hostels'].addHostel(hostel)
179        bed = Bed()
180        bed.bed_id = u'hall-1_A_101_A'
181        bed.bed_number = 1
182        bed.owner = NOT_OCCUPIED
183        bed.bed_type = u'regular_male_fr'
184        self.app['hostels'][hostel.hostel_id].addBed(bed)
185        bed = Bed()
186        bed.bed_id = u'hall-1_A_101_B'
187        bed.bed_number = 2
188        bed.owner = NOT_OCCUPIED
189        bed.bed_type = u'regular_female_fr'
190        self.app['hostels'][hostel.hostel_id].addBed(bed)
191
192        # Set study course attributes of test student
193        self.student['studycourse'].certificate = self.certificate
194        self.student['studycourse'].current_session = 2004
195        self.student['studycourse'].entry_session = 2004
196        self.student['studycourse'].current_verdict = 'A'
197        self.student['studycourse'].current_level = 100
198
199        # Put the prepopulated site into test ZODB and prepare test
200        # browser
201        self.browser = Browser()
202        self.browser.handleErrors = False
203
204    def tearDown(self):
205        super(StudentsFullSetup, self).tearDown()
206        clearSite()
207        shutil.rmtree(self.dc_root)
208
209
210
211class StudentsContainerUITests(StudentsFullSetup):
212    # Tests for StudentsContainer class views and pages
213
214    layer = FunctionalLayer
215
216    def test_anonymous_access(self):
217        # Anonymous users can't access students containers
218        self.assertRaises(
219            Unauthorized, self.browser.open, self.container_path)
220        self.assertRaises(
221            Unauthorized, self.browser.open, self.manage_container_path)
222        return
223
224    def test_manage_access(self):
225        # Managers can access the view page of students
226        # containers and can perform actions
227        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
228        self.browser.open(self.container_path)
229        self.assertEqual(self.browser.headers['Status'], '200 Ok')
230        self.assertEqual(self.browser.url, self.container_path)
231        self.browser.getLink("Manage student section").click()
232        self.assertEqual(self.browser.headers['Status'], '200 Ok')
233        self.assertEqual(self.browser.url, self.manage_container_path)
234        return
235
236    def test_add_search_delete_students(self):
237        # Managers can add search and remove students
238        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
239        self.browser.open(self.manage_container_path)
240        self.browser.getLink("Add student").click()
241        self.assertEqual(self.browser.headers['Status'], '200 Ok')
242        self.assertEqual(self.browser.url, self.add_student_path)
243        self.browser.getControl(name="form.fullname").value = 'Bob Tester'
244        self.browser.getControl("Create student record").click()
245        self.assertTrue('Student record created' in self.browser.contents)
246
247        # Registration and matric numbers must be unique
248        self.browser.getLink("Manage").click()
249        self.browser.getControl(name="form.reg_number").value = '123'
250        self.browser.getControl("Save").click()
251        self.assertMatches('...Registration number exists...',
252                           self.browser.contents)
253        self.browser.getControl(name="form.reg_number").value = '789'
254        self.browser.getControl(name="form.matric_number").value = '234'
255        self.browser.getControl("Save").click()
256        self.assertMatches('...Matriculation number exists...',
257                           self.browser.contents)
258
259        self.browser.open(self.container_path)
260        self.browser.getControl("Search").click()
261        self.assertTrue('Empty search string' in self.browser.contents)
262        self.browser.getControl(name="searchtype").value = ['student_id']
263        self.browser.getControl(name="searchterm").value = self.student_id
264        self.browser.getControl("Search").click()
265        self.assertTrue('Anna Tester' in self.browser.contents)
266
267        self.browser.open(self.manage_container_path)
268        self.browser.getControl("Search").click()
269        self.assertTrue('Empty search string' in self.browser.contents)
270        self.browser.getControl(name="searchtype").value = ['fullname']
271        self.browser.getControl(name="searchterm").value = 'Anna Tester'
272        self.browser.getControl("Search").click()
273        self.assertTrue('Anna Tester' in self.browser.contents)
274        # The old searchterm will be used again
275        self.browser.getControl("Search").click()
276        self.assertTrue('Anna Tester' in self.browser.contents)
277
278        ctrl = self.browser.getControl(name='entries')
279        ctrl.getControl(value=self.student_id).selected = True
280        self.browser.getControl("Remove selected", index=0).click()
281        self.assertTrue('Successfully removed' in self.browser.contents)
282        self.browser.getControl(name="searchtype").value = ['student_id']
283        self.browser.getControl(name="searchterm").value = self.student_id
284        self.browser.getControl("Search").click()
285        self.assertTrue('No student found' in self.browser.contents)
286
287        self.browser.open(self.container_path)
288        self.browser.getControl(name="searchtype").value = ['student_id']
289        self.browser.getControl(name="searchterm").value = self.student_id
290        self.browser.getControl("Search").click()
291        self.assertTrue('No student found' in self.browser.contents)
292        return
293
294class StudentUITests(StudentsFullSetup):
295    # Tests for Student class views and pages
296
297    layer = FunctionalLayer
298
299    def test_manage_access(self):
300        # Managers can access the pages of students
301        # and can perform actions
302        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
303
304        self.browser.open(self.student_path)
305        self.assertEqual(self.browser.headers['Status'], '200 Ok')
306        self.assertEqual(self.browser.url, self.student_path)
307        self.browser.getLink("Manage").click()
308        self.assertEqual(self.browser.headers['Status'], '200 Ok')
309        self.assertEqual(self.browser.url, self.manage_student_path)
310        # Managers can edit base data and fire transitions
311        self.browser.getControl(name="transition").value = ['admit']
312        self.browser.getControl(name="form.fullname").value = 'John Tester'
313        self.browser.getControl(name="form.reg_number").value = '345'
314        self.browser.getControl(name="password").value = 'secret'
315        self.browser.getControl(name="control_password").value = 'secret'
316        self.browser.getControl("Save").click()
317        self.assertMatches('...Form has been saved...',
318                           self.browser.contents)
319        self.browser.open(self.student_path)
320        self.browser.getLink("Clearance Data").click()
321        self.assertEqual(self.browser.headers['Status'], '200 Ok')
322        self.assertEqual(self.browser.url, self.clearance_student_path)
323        self.browser.getLink("Manage").click()
324        self.assertEqual(self.browser.headers['Status'], '200 Ok')
325        self.assertEqual(self.browser.url, self.edit_clearance_student_path)
326        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
327        self.browser.getControl("Save").click()
328        self.assertMatches('...Form has been saved...',
329                           self.browser.contents)
330
331        self.browser.open(self.student_path)
332        self.browser.getLink("Personal Data").click()
333        self.assertEqual(self.browser.headers['Status'], '200 Ok')
334        self.assertEqual(self.browser.url, self.personal_student_path)
335        self.browser.getLink("Manage").click()
336        self.assertEqual(self.browser.headers['Status'], '200 Ok')
337        self.assertEqual(self.browser.url, self.edit_personal_student_path)
338        self.browser.getControl("Save").click()
339        self.assertMatches('...Form has been saved...',
340                           self.browser.contents)
341
342        # Managers can browse all subobjects
343        self.browser.open(self.student_path)
344        self.browser.getLink("Payments").click()
345        self.assertEqual(self.browser.headers['Status'], '200 Ok')
346        self.assertEqual(self.browser.url, self.payments_student_path)
347        self.browser.open(self.student_path)
348        self.browser.getLink("Accommodation").click()
349        self.assertEqual(self.browser.headers['Status'], '200 Ok')
350        self.assertEqual(self.browser.url, self.acco_student_path)
351        self.browser.open(self.student_path)
352        self.browser.getLink("History").click()
353        self.assertEqual(self.browser.headers['Status'], '200 Ok')
354        self.assertEqual(self.browser.url, self.history_student_path)
355        self.assertMatches('...Student admitted by zope.mgr...',
356                           self.browser.contents)
357        return
358
359    def test_manage_upload_file(self):
360        # Managers can upload a file via the StudentClearanceManageFormPage
361        # The image is stored even if form has errors
362        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
363        self.browser.open(self.edit_clearance_student_path)
364        # No birth certificate has been uploaded yet
365        # Browsing the link shows a placerholder image
366        self.browser.open('birth_certificate')
367        self.assertEqual(
368            self.browser.headers['content-type'], 'image/jpeg')
369        self.assertEqual(len(self.browser.contents), PH_LEN)
370        # Create a pseudo image file and select it to be uploaded in form
371        # as birth certificate
372        self.browser.open(self.edit_clearance_student_path)
373        pseudo_image = StringIO('I pretend to be a graphics file')
374        ctrl = self.browser.getControl(name='birthcertificateupload')
375        file_ctrl = ctrl.mech_control
376        file_ctrl.add_file(pseudo_image, filename='my_birth_certificate.jpg')
377        # The Save action does not upload files
378        self.browser.getControl("Save").click() # submit form
379        self.assertFalse(
380            '<a target="image" href="birth_certificate">'
381            in self.browser.contents)
382        # ... but the correct upload submit button does
383        pseudo_image = StringIO('I pretend to be a graphics file')
384        ctrl = self.browser.getControl(name='birthcertificateupload')
385        file_ctrl = ctrl.mech_control
386        file_ctrl.add_file(pseudo_image, filename='my_birth_certificate.jpg')
387        self.browser.getControl(
388            name='upload_birthcertificateupload').click()
389        # There is a correct <img> link included
390        self.assertTrue(
391            '<a target="image" href="birth_certificate">'
392            in self.browser.contents)
393
394        # Browsing the link shows a real image
395        self.browser.open('birth_certificate')
396        self.assertEqual(
397            self.browser.headers['content-type'], 'image/jpeg')
398        self.assertEqual(len(self.browser.contents), 31)
399        # Reuploading a file which is bigger than 150k will raise an error
400        self.browser.open(self.edit_clearance_student_path)
401        photo_content = 'A' * 1024 * 151  # A string of 21 KB size
402        pseudo_image = StringIO(photo_content)
403        ctrl = self.browser.getControl(name='birthcertificateupload')
404        file_ctrl = ctrl.mech_control
405        file_ctrl.add_file(pseudo_image, filename='my_birth_certificate.jpg')
406        self.browser.getControl(
407            name='upload_birthcertificateupload').click()
408        self.assertTrue(
409            'Uploaded file is too big' in self.browser.contents)
410        # File names must meet several conditions
411        pseudo_image = StringIO('I pretend to be a graphics file')
412        ctrl = self.browser.getControl(name='birthcertificateupload')
413        file_ctrl = ctrl.mech_control
414        file_ctrl.add_file(pseudo_image, filename='my.photo.jpg')
415        self.browser.getControl(
416            name='upload_birthcertificateupload').click()
417        self.assertTrue('File name contains more than one dot'
418            in self.browser.contents)
419        ctrl = self.browser.getControl(name='birthcertificateupload')
420        file_ctrl = ctrl.mech_control
421        file_ctrl.add_file(pseudo_image, filename='my_birth_certificate')
422        self.browser.getControl(
423            name='upload_birthcertificateupload').click()
424        self.assertTrue('File name has no extension' in self.browser.contents)
425        ctrl = self.browser.getControl(name='birthcertificateupload')
426        file_ctrl = ctrl.mech_control
427        file_ctrl.add_file(pseudo_image, filename='my_birth_certificate.bmp')
428        self.browser.getControl(
429            name='upload_birthcertificateupload').click()
430        self.assertTrue('Only the following extension are allowed'
431            in self.browser.contents)
432        # Managers can delete files
433        self.browser.getControl(name='delete_birthcertificateupload').click()
434        self.assertTrue(
435            'birth_certificate deleted' in self.browser.contents)
436        # Managers can add and delete second file
437        self.browser.open(self.edit_clearance_student_path)
438        pseudo_image = StringIO('I pretend to be a graphics file')
439        ctrl = self.browser.getControl(name='birthcertificateupload')
440        file_ctrl = ctrl.mech_control
441        file_ctrl.add_file(pseudo_image, filename='my_acceptance_letter.jpg')
442        self.browser.getControl(
443            name='upload_acceptanceletterupload').click()
444        self.assertFalse(
445            '<a target="image" href="acceptance_letter">'
446            in self.browser.contents)
447        ctrl = self.browser.getControl(name='acceptanceletterupload')
448        file_ctrl = ctrl.mech_control
449        file_ctrl.add_file(pseudo_image, filename='my_acceptance_letter.jpg')
450        self.browser.getControl(
451            name='upload_acceptanceletterupload').click()
452        self.assertTrue(
453            '<a target="image" href="acceptance_letter">'
454            in self.browser.contents)
455        self.browser.getControl(
456            name='delete_acceptanceletterupload').click()
457        self.assertTrue(
458            'acceptance_letter deleted'
459            in self.browser.contents)
460        # Managers can upload a file via the StudentBaseManageFormPage
461        self.browser.open(self.manage_student_path)
462        pseudo_image = StringIO('I pretend to be a graphics file')
463        ctrl = self.browser.getControl(name='passportuploadmanage')
464        file_ctrl = ctrl.mech_control
465        file_ctrl.add_file(pseudo_image, filename='my_photo.bmp')
466        self.browser.getControl(
467            name='upload_passportuploadmanage').click()
468        self.assertTrue('jpg file extension expected'
469            in self.browser.contents)
470        ctrl = self.browser.getControl(name='passportuploadmanage')
471        file_ctrl = ctrl.mech_control
472        file_ctrl.add_file(pseudo_image, filename='my_photo.jpg')
473        self.browser.getControl(
474            name='upload_passportuploadmanage').click()
475        self.assertTrue(
476            '<img align="middle" height="125px" src="passport.jpg" />'
477            in self.browser.contents)
478
479    def test_manage_course_lists(self):
480        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
481        self.browser.open(self.student_path)
482        self.browser.getLink("Study Course").click()
483        self.assertEqual(self.browser.headers['Status'], '200 Ok')
484        self.assertEqual(self.browser.url, self.studycourse_student_path)
485        self.browser.getLink("Manage").click()
486        self.assertTrue('Manage study course' in self.browser.contents)
487        # Before we can select a level, the certificate must
488        # be selected and saved
489        self.browser.getControl(name="form.certificate").value = ['CERT1']
490        self.browser.getControl(name="form.current_session").value = ['2004']
491        self.browser.getControl(name="form.current_verdict").value = ['A']
492        self.browser.getControl("Save").click()
493        # Now we can save also the current level which depends on start and end
494        # level of the certificate
495        self.browser.getControl(name="form.current_level").value = ['100']
496        self.browser.getControl("Save").click()
497        # Managers can add and remove any study level (course list)
498        self.browser.getControl(name="addlevel").value = ['100']
499        self.browser.getControl("Add study level").click()
500        self.assertMatches('...<span>100</span>...', self.browser.contents)
501        self.browser.getControl("Add study level").click()
502        self.assertMatches('...This level exists...', self.browser.contents)
503        self.browser.getControl("Remove selected").click()
504        self.assertMatches(
505            '...No study level selected...', self.browser.contents)
506        self.browser.getControl(name="val_id").value = ['100']
507        self.browser.getControl("Remove selected").click()
508        self.assertMatches('...Successfully removed...', self.browser.contents)
509        # Add level again
510        self.browser.getControl(name="addlevel").value = ['100']
511        self.browser.getControl("Add study level").click()
512        self.browser.getControl(name="addlevel").value = ['100']
513
514        # Managers can view and manage course lists
515        self.browser.getLink("100").click()
516        self.assertMatches(
517            '...: Study Level 100 (Year 1)...', self.browser.contents)
518        self.browser.getLink("Manage").click()
519        self.browser.getControl(name="form.level_session").value = ['2002']
520        self.browser.getControl("Save").click()
521        self.browser.getControl("Remove selected").click()
522        self.assertMatches('...No ticket selected...', self.browser.contents)
523        ctrl = self.browser.getControl(name='val_id')
524        ctrl.getControl(value='COURSE1').selected = True
525        self.browser.getControl("Remove selected", index=0).click()
526        self.assertTrue('Successfully removed' in self.browser.contents)
527        self.browser.getControl("Add course ticket").click()
528        self.browser.getControl(name="form.course").value = ['COURSE1']
529        self.browser.getControl("Add course ticket").click()
530        self.assertTrue('Successfully added' in self.browser.contents)
531        self.browser.getControl("Add course ticket").click()
532        self.browser.getControl(name="form.course").value = ['COURSE1']
533        self.browser.getControl("Add course ticket").click()
534        self.assertTrue('The ticket exists' in self.browser.contents)
535        self.browser.getControl("Cancel").click()
536        self.browser.getLink("COURSE1").click()
537        self.browser.getLink("Manage").click()
538        self.browser.getControl(name="form.score").value = '10'
539        self.browser.getControl("Save").click()
540        self.assertTrue('Form has been saved' in self.browser.contents)
541        return
542
543    def test_manage_workflow(self):
544        # Managers can pass through the whole workflow
545        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
546        student = self.app['students'][self.student_id]
547        self.browser.open(self.manage_student_path)
548        self.assertTrue(student.clearance_locked)
549        self.browser.getControl(name="transition").value = ['admit']
550        self.browser.getControl("Save").click()
551        self.assertTrue(student.clearance_locked)
552        self.browser.getControl(name="transition").value = ['start_clearance']
553        self.browser.getControl("Save").click()
554        self.assertFalse(student.clearance_locked)
555        self.browser.getControl(name="transition").value = ['request_clearance']
556        self.browser.getControl("Save").click()
557        self.assertTrue(student.clearance_locked)
558        self.browser.getControl(name="transition").value = ['clear']
559        self.browser.getControl("Save").click()
560        self.browser.getControl(
561            name="transition").value = ['pay_first_school_fee']
562        self.browser.getControl("Save").click()
563        self.browser.getControl(name="transition").value = ['reset6']
564        self.browser.getControl("Save").click()
565        # In state returning the pay_school_fee transition triggers some
566        # changes of attributes
567        self.browser.getControl(name="transition").value = ['pay_school_fee']
568        self.browser.getControl("Save").click()
569        self.assertEqual(student['studycourse'].current_session, 2005) # +1
570        self.assertEqual(student['studycourse'].current_level, 200) # +100
571        self.assertEqual(student['studycourse'].current_verdict, '0') # 0 = not set
572        self.assertEqual(student['studycourse'].previous_verdict, 'A')
573        self.browser.getControl(name="transition").value = ['register_courses']
574        self.browser.getControl("Save").click()
575        self.browser.getControl(name="transition").value = ['validate_courses']
576        self.browser.getControl("Save").click()
577        self.browser.getControl(name="transition").value = ['return']
578        self.browser.getControl("Save").click()
579        return
580
581    def test_manage_import(self):
582        # Managers can import student data files
583        datacenter_path = 'http://localhost/app/datacenter'
584        # Prepare a csv file for students
585        open('students.csv', 'wb').write(
586"""firstname,lastname,fullname,reg_number,date_of_birth,matric_number,email,phone
587Aaren,Pieri,Aaren Pieri,1,1990-01-02,100000,aa@aa.ng,1234
588Claus,Finau,Claus Finau,2,1990-01-03,100001,aa@aa.ng,1234
589Brit,Berson,Brit Berson,3,1990-01-04,100001,aa@aa.ng,1234
590""")
591        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
592        self.browser.open(datacenter_path)
593        self.browser.getLink('Upload CSV file').click()
594        filecontents = StringIO(open('students.csv', 'rb').read())
595        filewidget = self.browser.getControl(name='uploadfile:file')
596        filewidget.add_file(filecontents, 'text/plain', 'students.csv')
597        self.browser.getControl(name='SUBMIT').click()
598        self.browser.getLink('Batch processing').click()
599        button = lookup_submit_value(
600            'select', 'students_zope.mgr.csv', self.browser)
601        button.click()
602        importerselect = self.browser.getControl(name='importer')
603        modeselect = self.browser.getControl(name='mode')
604        importerselect.getControl('Student Importer').selected = True
605        modeselect.getControl(value='create').selected = True
606        self.browser.getControl('Proceed to step 3...').click()
607        self.assertTrue('Header fields OK' in self.browser.contents)
608        self.browser.getControl('Perform import...').click()
609        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
610        self.assertTrue('Successfully processed 2 rows' in self.browser.contents)
611        self.assertTrue('Batch processing finished' in self.browser.contents)
612        open('studycourses.csv', 'wb').write(
613"""reg_number,matric_number,certificate,current_session,current_level
6141,,CERT1,2008,100
615,100001,CERT1,2008,100
616,100002,CERT1,2008,100
617""")
618        self.browser.open(datacenter_path)
619        self.browser.getLink('Upload CSV file').click()
620        filecontents = StringIO(open('studycourses.csv', 'rb').read())
621        filewidget = self.browser.getControl(name='uploadfile:file')
622        filewidget.add_file(filecontents, 'text/plain', 'studycourses.csv')
623        self.browser.getControl(name='SUBMIT').click()
624        self.browser.getLink('Batch processing').click()
625        button = lookup_submit_value(
626            'select', 'studycourses_zope.mgr.csv', self.browser)
627        button.click()
628        importerselect = self.browser.getControl(name='importer')
629        modeselect = self.browser.getControl(name='mode')
630        importerselect.getControl(
631            'StudentStudyCourse Importer (update only)').selected = True
632        modeselect.getControl(value='create').selected = True
633        self.browser.getControl('Proceed to step 3...').click()
634        self.assertTrue('Update mode only' in self.browser.contents)
635        self.browser.getControl('Proceed to step 3...').click()
636        self.assertTrue('Header fields OK' in self.browser.contents)
637        self.browser.getControl('Perform import...').click()
638        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
639        self.assertTrue('Successfully processed 2 rows'
640                        in self.browser.contents)
641        return
642
643    def test_handle_clearance_by_co(self):
644        # Create clearance officer
645        self.app['users'].addUser('mrclear', 'mrclearsecret')
646        # Clearance officers need to get the StudentsOfficer site role
647        prmglobal = IPrincipalRoleManager(self.app)
648        prmglobal.assignRoleToPrincipal('waeup.StudentsOfficer', 'mrclear')
649        # Assign local ClearanceOfficer role
650        department = self.app['faculties']['fac1']['dep1']
651        prmlocal = IPrincipalRoleManager(department)
652        prmlocal.assignRoleToPrincipal('waeup.local.ClearanceOfficer', 'mrclear')
653        IWorkflowInfo(self.student).fireTransition('admit')
654        IWorkflowInfo(self.student).fireTransition('start_clearance')
655        # Login as clearance officer
656        self.browser.open(self.login_path)
657        self.browser.getControl(name="form.login").value = 'mrclear'
658        self.browser.getControl(name="form.password").value = 'mrclearsecret'
659        self.browser.getControl("Login").click()
660        self.assertTrue('You logged in' in self.browser.contents)
661        # CO can see his roles
662        self.browser.getLink("My Roles").click()
663        self.assertMatches(
664            '...<div>Academics Officer (view only)</div>...',
665            self.browser.contents)
666        self.assertMatches(
667            '...<div>Students Officer (view only)</div>...',
668            self.browser.contents)
669        # But not his local role ...
670        self.assertFalse('Clearance Officer' in self.browser.contents)
671        # ... because we forgot to notify the department that the local role
672        # has changed
673        notify(LocalRoleSetEvent(
674            department, 'waeup.local.ClearanceOfficer', 'mrclear', granted=True))
675        self.browser.open('http://localhost/app/users/mrclear/my_roles')
676        self.assertTrue('Clearance Officer' in self.browser.contents)
677        self.assertMatches(
678            '...<a href="http://localhost/app/faculties/fac1/dep1">...',
679            self.browser.contents)
680        # CO can view the student
681        self.browser.open(self.clearance_student_path)
682        self.assertEqual(self.browser.headers['Status'], '200 Ok')
683        self.assertEqual(self.browser.url, self.clearance_student_path)
684        # Only in state clearance requested the CO does see the 'Clear' button
685        self.assertFalse('Clear student' in self.browser.contents)
686        IWorkflowInfo(self.student).fireTransition('request_clearance')
687        self.browser.open(self.clearance_student_path)
688        self.assertTrue('Clear student' in self.browser.contents)
689        self.browser.getLink("Clear student").click()
690        self.assertTrue('Student has been cleared' in self.browser.contents)
691        self.assertTrue('State: <span>cleared</span>' in self.browser.contents)
692        self.browser.getLink("Reject clearance").click()
693        self.assertTrue('Clearance has been annulled' in self.browser.contents)
694        self.assertTrue('State: <span>clearance started</span>'
695            in self.browser.contents)
696        IWorkflowInfo(self.student).fireTransition('request_clearance')
697        self.browser.open(self.clearance_student_path)
698        self.browser.getLink("Reject clearance").click()
699        self.assertTrue('Clearance request has been rejected'
700            in self.browser.contents)
701        self.assertTrue('State: <span>clearance started</span>'
702            in self.browser.contents)
703        # The CO can't clear students if not in state
704        # clearance requested
705        self.browser.open(self.student_path + '/clear')
706        self.assertTrue('Student is in the wrong state'
707            in self.browser.contents)
708
709    def test_student_change_password(self):
710        # Students can change the password
711        self.browser.open(self.login_path)
712        self.browser.getControl(name="form.login").value = self.student_id
713        self.browser.getControl(name="form.password").value = 'spwd'
714        self.browser.getControl("Login").click()
715        self.assertEqual(self.browser.url, self.student_path)
716        self.assertTrue('You logged in' in self.browser.contents)
717        # Change password
718        self.browser.getLink("Change password").click()
719        self.browser.getControl(name="change_password").value = 'pw'
720        self.browser.getControl(
721            name="change_password_repeat").value = 'pw'
722        self.browser.getControl("Save").click()
723        self.assertTrue('Password must have at least' in self.browser.contents)
724        self.browser.getControl(name="change_password").value = 'new_password'
725        self.browser.getControl(
726            name="change_password_repeat").value = 'new_passssword'
727        self.browser.getControl("Save").click()
728        self.assertTrue('Passwords do not match' in self.browser.contents)
729        self.browser.getControl(name="change_password").value = 'new_password'
730        self.browser.getControl(
731            name="change_password_repeat").value = 'new_password'
732        self.browser.getControl("Save").click()
733        self.assertTrue('Password changed' in self.browser.contents)
734        # We are still logged in. Changing the password hasn't thrown us out.
735        self.browser.getLink("My Data").click()
736        self.assertEqual(self.browser.url, self.student_path)
737        # We can logout
738        self.browser.getLink("Logout").click()
739        self.assertTrue('You have been logged out' in self.browser.contents)
740        self.assertEqual(self.browser.url, 'http://localhost/app')
741        # We can login again with the new password
742        self.browser.getLink("Login").click()
743        self.browser.open(self.login_path)
744        self.browser.getControl(name="form.login").value = self.student_id
745        self.browser.getControl(name="form.password").value = 'new_password'
746        self.browser.getControl("Login").click()
747        self.assertEqual(self.browser.url, self.student_path)
748        self.assertTrue('You logged in' in self.browser.contents)
749        return
750
751    def test_setpassword(self):
752        # Set password for first-time access
753        student = Student()
754        student.reg_number = u'123456'
755        student.fullname = u'Klaus Tester'
756        self.app['students'].addStudent(student)
757        setpassword_path = 'http://localhost/app/setpassword'
758        student_path = 'http://localhost/app/students/%s' % student.student_id
759        self.browser.open(setpassword_path)
760        self.browser.getControl(name="ac_series").value = self.existing_pwdseries
761        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
762        self.browser.getControl(name="reg_number").value = '223456'
763        self.browser.getControl("Show").click()
764        self.assertMatches('...No student found...',
765                           self.browser.contents)
766        self.browser.getControl(name="reg_number").value = '123456'
767        self.browser.getControl(name="ac_number").value = '999999'
768        self.browser.getControl("Show").click()
769        self.assertMatches('...Access code is invalid...',
770                           self.browser.contents)
771        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
772        self.browser.getControl("Show").click()
773        self.assertMatches('...Password has been set. Your Student Id is...',
774                           self.browser.contents)
775        self.browser.getControl("Show").click()
776        self.assertMatches(
777            '...Password has already been set. Your Student Id is...',
778            self.browser.contents)
779        existing_pwdpin = self.pwdpins[1]
780        parts = existing_pwdpin.split('-')[1:]
781        existing_pwdseries, existing_pwdnumber = parts
782        self.browser.getControl(name="ac_series").value = existing_pwdseries
783        self.browser.getControl(name="ac_number").value = existing_pwdnumber
784        self.browser.getControl(name="reg_number").value = '123456'
785        self.browser.getControl("Show").click()
786        self.assertMatches(
787            '...You are using the wrong Access Code...',
788            self.browser.contents)
789        # The student can login with the new credentials
790        self.browser.open(self.login_path)
791        self.browser.getControl(name="form.login").value = student.student_id
792        self.browser.getControl(
793            name="form.password").value = self.existing_pwdnumber
794        self.browser.getControl("Login").click()
795        self.assertEqual(self.browser.url, student_path)
796        self.assertTrue('You logged in' in self.browser.contents)
797        return
798
799    def test_student_access(self):
800        # Students can access their own objects
801        # and can perform actions
802        IWorkflowInfo(self.student).fireTransition('admit')
803        self.browser.open(self.login_path)
804        self.browser.getControl(name="form.login").value = self.student_id
805        self.browser.getControl(name="form.password").value = 'spwd'
806        self.browser.getControl("Login").click()
807        # Student can upload a passport picture
808        self.browser.open(self.student_path + '/change_portrait')
809        pseudo_image = StringIO('I pretend to be a graphics file')
810        ctrl = self.browser.getControl(name='passportuploadedit')
811        file_ctrl = ctrl.mech_control
812        file_ctrl.add_file(pseudo_image, filename='my_photo.jpg')
813        self.browser.getControl(
814            name='upload_passportuploadedit').click()
815        self.assertTrue(
816            '<img align="middle" height="125px" src="passport.jpg" />'
817            in self.browser.contents)
818        # Student can view the clearance data
819        self.browser.getLink("Clearance Data").click()
820        # Student can't open clearance edit form before starting clearance
821        self.browser.open(self.student_path + '/cedit')
822        self.assertMatches('...The requested form is locked...',
823                           self.browser.contents)
824        self.browser.getLink("Clearance Data").click()
825        self.browser.getLink("Start clearance").click()
826        self.student.email = None
827        # Uups, we forgot to fill the email fields
828        self.browser.getControl("Start clearance").click()
829        self.assertMatches('...Not all required fields filled...',
830                           self.browser.contents)
831        self.student.email = 'aa@aa.ng'
832        self.browser.open(self.student_path + '/start_clearance')
833        self.browser.getControl(name="ac_series").value = '3'
834        self.browser.getControl(name="ac_number").value = '4444444'
835        self.browser.getControl("Start clearance now").click()
836        self.assertMatches('...Activation code is invalid...',
837                           self.browser.contents)
838        self.browser.getControl(name="ac_series").value = self.existing_clrseries
839        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
840        # Owner is Hans Wurst, AC can't be invalidated
841        self.browser.getControl("Start clearance now").click()
842        self.assertMatches('...You are not the owner of this access code...',
843                           self.browser.contents)
844        # Set the correct owner
845        self.existing_clrac.owner = self.student_id
846        self.browser.getControl("Start clearance now").click()
847        self.assertMatches('...Clearance process has been started...',
848                           self.browser.contents)
849        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
850        self.browser.getControl("Save", index=0).click()
851        # Student can view the clearance data
852        self.browser.getLink("Clearance Data").click()
853        # and go back to the edit form
854        self.browser.getLink("Edit").click()
855        self.browser.getControl("Save and request clearance").click()
856       
857        self.browser.getControl(name="ac_series").value = self.existing_clrseries
858        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
859        self.browser.getControl("Request clearance now").click()
860        self.assertMatches('...Clearance has been requested...',
861                           self.browser.contents)
862        # Student can't reopen clearance form after requesting clearance
863        self.browser.open(self.student_path + '/cedit')
864        self.assertMatches('...The requested form is locked...',
865                           self.browser.contents)
866        # Student can't add study level if not in state 'school fee paid'
867        self.browser.open(self.student_path + '/studycourse/add')
868        self.assertMatches('...The requested form is locked...',
869                           self.browser.contents)
870        # ... and must be transferred first
871        IWorkflowInfo(self.student).fireTransition('clear')
872        IWorkflowInfo(self.student).fireTransition('pay_first_school_fee')
873        # Now students can add the current study level
874        self.browser.getLink("Study Course").click()
875        self.browser.getLink("Add course list").click()
876        self.assertMatches('...Add current level 100 (Year 1)...',
877                           self.browser.contents)
878        self.browser.getControl("Create course list now").click()
879        self.browser.getLink("100").click()
880        self.browser.getLink("Add and remove courses").click()
881        self.browser.getControl("Add course ticket").click()
882        self.browser.getControl(name="form.course").value = ['COURSE1']
883        self.browser.getControl("Add course ticket").click()
884        self.assertMatches('...The ticket exists...',
885                           self.browser.contents)
886        self.student['studycourse'].current_level = 200
887        self.browser.getLink("Study Course").click()
888        self.browser.getLink("Add course list").click()
889        self.assertMatches('...Add current level 200 (Year 2)...',
890                           self.browser.contents)
891        self.browser.getControl("Create course list now").click()
892        self.browser.getLink("200").click()
893        self.browser.getLink("Add and remove courses").click()
894        self.browser.getControl("Add course ticket").click()
895        self.browser.getControl(name="form.course").value = ['COURSE1']
896        self.browser.getControl("Add course ticket").click()
897        self.assertMatches('...Successfully added COURSE1...',
898                           self.browser.contents)
899        # Students can open the pdf course registration slip
900        self.browser.open(self.student_path + '/studycourse/200')
901        self.browser.getLink("Download course registration slip").click()
902        self.assertEqual(self.browser.headers['Status'], '200 Ok')
903        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
904        # Students can remove course tickets
905        self.browser.open(self.student_path + '/studycourse/200/edit')
906        self.browser.getControl("Remove selected", index=0).click()
907        self.assertTrue('No ticket selected' in self.browser.contents)
908        ctrl = self.browser.getControl(name='val_id')
909        ctrl.getControl(value='COURSE1').selected = True
910        self.browser.getControl("Remove selected", index=0).click()
911        self.assertTrue('Successfully removed' in self.browser.contents)
912        self.browser.getControl("Register course list").click()
913        self.assertTrue('Course list has been registered' in self.browser.contents)
914        self.assertEqual(self.student.state, 'courses registered')
915        return
916
917    def test_manage_payments(self):
918        # Managers can add online school fee payment tickets
919        # if certain requirements are met
920        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
921        self.browser.open(self.payments_student_path)
922        self.browser.getControl("Add online payment ticket").click()
923        self.browser.getControl(name="form.p_category").value = ['schoolfee']
924        self.browser.getControl("Create ticket").click()
925        self.assertMatches('...ticket created...',
926                           self.browser.contents)
927        ctrl = self.browser.getControl(name='val_id')
928        value = ctrl.options[0]
929        self.browser.getLink(value).click()
930        self.assertMatches('...Amount Authorized...',
931                           self.browser.contents)
932        payment_url = self.browser.url
933
934        # The pdf payment receipt can't yet be opened
935        self.browser.open(payment_url + '/payment_receipt.pdf')
936        self.assertMatches('...Ticket not yet paid...',
937                           self.browser.contents)
938
939        # The same payment ticket (with same p_item, p_session and p_category)
940        # can't be added twice.
941        self.browser.open(self.payments_student_path)
942        self.browser.getControl("Add online payment ticket").click()
943        self.browser.getControl(name="form.p_category").value = ['schoolfee']
944        self.browser.getControl("Create ticket").click()
945        self.assertMatches('...This payment ticket already exists...',
946                           self.browser.contents)
947
948        # Managers can open the callback view which simulates a valid callback
949        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
950        self.browser.open(payment_url)
951        self.browser.getLink("Request callback").click()
952        self.assertMatches('...Valid callback received...',
953                          self.browser.contents)
954
955        # Callback can't be applied twice
956        self.browser.open(payment_url + '/callback')
957        self.assertMatches('...This ticket has already been paid...',
958                          self.browser.contents)
959
960        # Managers can open the pdf payment receipt
961        self.browser.getLink("Download payment receipt").click()
962        self.assertEqual(self.browser.headers['Status'], '200 Ok')
963        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
964
965        # Managers can remove online school fee payment tickets
966        self.browser.open(self.payments_student_path)
967        self.browser.getControl("Remove selected").click()
968        self.assertMatches('...No payment selected...', self.browser.contents)
969        ctrl = self.browser.getControl(name='val_id')
970        value = ctrl.options[0]
971        ctrl.getControl(value=value).selected = True
972        self.browser.getControl("Remove selected", index=0).click()
973        self.assertTrue('Successfully removed' in self.browser.contents)
974
975        # Managers can add online clearance payment tickets
976        self.browser.open(self.payments_student_path + '/addop')
977        self.browser.getControl(name="form.p_category").value = ['clearance']
978        self.browser.getControl("Create ticket").click()
979        self.assertMatches('...ticket created...',
980                           self.browser.contents)
981
982        # Managers can open the callback view which simulates a valid callback
983        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
984        ctrl = self.browser.getControl(name='val_id')
985        value = ctrl.options[0]
986        self.browser.getLink(value).click()
987        self.browser.open(self.browser.url + '/callback')
988        self.assertMatches('...Valid callback received...',
989                          self.browser.contents)
990        expected = '''...
991        <td>
992          Paid
993        </td>...'''
994        self.assertMatches(expected,self.browser.contents)
995        # The new CLR-0 pin has been created
996        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
997        pin = self.app['accesscodes']['CLR-0'].keys()[0]
998        ac = self.app['accesscodes']['CLR-0'][pin]
999        ac.owner = self.student_id
1000        return
1001
1002    def test_student_payments(self):
1003        # Login
1004        self.browser.open(self.login_path)
1005        self.browser.getControl(name="form.login").value = self.student_id
1006        self.browser.getControl(name="form.password").value = 'spwd'
1007        self.browser.getControl("Login").click()
1008
1009        # Students can add online clearance payment tickets
1010        self.browser.open(self.payments_student_path + '/addop')
1011        self.browser.getControl(name="form.p_category").value = ['clearance']
1012        self.browser.getControl("Create ticket").click()
1013        self.assertMatches('...ticket created...',
1014                           self.browser.contents)
1015
1016        # Students can open the callback view which simulates a valid callback
1017        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
1018        ctrl = self.browser.getControl(name='val_id')
1019        value = ctrl.options[0]
1020        self.browser.getLink(value).click()
1021        payment_url = self.browser.url
1022        self.browser.open(payment_url + '/callback')
1023        self.assertMatches('...Valid callback received...',
1024                          self.browser.contents)
1025        expected = '''...
1026        <td>
1027          Paid
1028        </td>...'''
1029        self.assertMatches(expected,self.browser.contents)
1030        # The new CLR-0 pin has been created
1031        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
1032        pin = self.app['accesscodes']['CLR-0'].keys()[0]
1033        ac = self.app['accesscodes']['CLR-0'][pin]
1034        ac.owner = self.student_id
1035
1036        # Students can open the pdf payment receipt
1037        self.browser.open(payment_url + '/payment_receipt.pdf')
1038        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1039        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1040
1041        # The new CLR-0 pin can be used for starting clearance
1042        # but they have to upload a passport picture first
1043        # which is only possible in state admitted
1044        self.browser.open(self.student_path + '/change_portrait')
1045        self.assertMatches('...form is locked...',
1046                          self.browser.contents)
1047        IWorkflowInfo(self.student).fireTransition('admit')
1048        self.browser.open(self.student_path + '/change_portrait')
1049        pseudo_image = StringIO('I pretend to be a graphics file')
1050        ctrl = self.browser.getControl(name='passportuploadedit')
1051        file_ctrl = ctrl.mech_control
1052        file_ctrl.add_file(pseudo_image, filename='my_photo.jpg')
1053        self.browser.getControl(
1054            name='upload_passportuploadedit').click()
1055        self.browser.open(self.student_path + '/start_clearance')
1056        parts = pin.split('-')[1:]
1057        clrseries, clrnumber = parts
1058        self.browser.getControl(name="ac_series").value = clrseries
1059        self.browser.getControl(name="ac_number").value = clrnumber
1060        self.browser.getControl("Start clearance now").click()
1061        self.assertMatches('...Clearance process has been started...',
1062                           self.browser.contents)
1063
1064        # Students can add online school fee payment tickets
1065        self.browser.open(self.payments_student_path)
1066        self.browser.getControl("Add online payment ticket").click()
1067        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1068        self.browser.getControl("Create ticket").click()
1069        self.assertMatches('...ticket created...',
1070                           self.browser.contents)
1071        ctrl = self.browser.getControl(name='val_id')
1072        value = ctrl.options[0]
1073        self.browser.getLink(value).click()
1074        self.assertMatches('...Amount Authorized...',
1075                           self.browser.contents)
1076
1077        # Students can open the callback view which simulates a valid callback
1078        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
1079        self.browser.open(self.browser.url + '/callback')
1080        self.assertMatches('...Valid callback received...',
1081                          self.browser.contents)
1082
1083        # Students can remove only online payment tickets which have
1084        # not received a valid callback
1085        self.browser.open(self.payments_student_path)
1086        self.assertRaises(
1087            LookupError, self.browser.getControl, name='val_id')
1088        self.browser.open(self.payments_student_path + '/addop')
1089        self.browser.getControl(name="form.p_category").value = ['gown']
1090        self.browser.getControl("Create ticket").click()
1091        self.browser.open(self.payments_student_path)
1092        ctrl = self.browser.getControl(name='val_id')
1093        value = ctrl.options[0]
1094        ctrl.getControl(value=value).selected = True
1095        self.browser.getControl("Remove selected", index=0).click()
1096        self.assertTrue('Successfully removed' in self.browser.contents)
1097
1098        # The new SFE-0 pin can be used for starting course registration
1099        IWorkflowInfo(self.student).fireTransition('request_clearance')
1100        IWorkflowInfo(self.student).fireTransition('clear')
1101        self.browser.open(self.studycourse_student_path)
1102        self.browser.getLink('Start course registration').click()
1103        pin = self.app['accesscodes']['SFE-0'].keys()[0]
1104        parts = pin.split('-')[1:]
1105        sfeseries, sfenumber = parts
1106        self.browser.getControl(name="ac_series").value = sfeseries
1107        self.browser.getControl(name="ac_number").value = sfenumber
1108        self.browser.getControl("Start course registration now").click()
1109        self.assertMatches('...Course registration has been started...',
1110                           self.browser.contents)
1111        self.assertTrue(self.student.state == 'school fee paid')
1112        return
1113
1114    def test_manage_accommodation(self):
1115        # Managers can add online booking fee payment tickets and open the
1116        # callback view (see test_manage_payments)
1117        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1118        self.browser.open(self.payments_student_path)
1119        self.browser.getControl("Add online payment ticket").click()
1120        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1121        # If student is not in accommodation session, payment cannot be processed
1122        self.app['configuration'].accommodation_session = 2011
1123        self.browser.getControl("Create ticket").click()
1124        self.assertMatches('...Your current session does not match...',
1125                           self.browser.contents)
1126        self.app['configuration'].accommodation_session = 2004
1127        self.browser.getControl("Add online payment ticket").click()
1128        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1129        self.browser.getControl("Create ticket").click()
1130        ctrl = self.browser.getControl(name='val_id')
1131        value = ctrl.options[0]
1132        self.browser.getLink(value).click()
1133        self.browser.open(self.browser.url + '/callback')
1134        # The new HOS-0 pin has been created
1135        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
1136        pin = self.app['accesscodes']['HOS-0'].keys()[0]
1137        ac = self.app['accesscodes']['HOS-0'][pin]
1138        ac.owner = self.student_id
1139        parts = pin.split('-')[1:]
1140        sfeseries, sfenumber = parts
1141        # Managers can use HOS code and book a bed space with it
1142        self.browser.open(self.acco_student_path)
1143        self.browser.getLink("Book accommodation").click()
1144        self.assertMatches('...You are in the wrong...',
1145                           self.browser.contents)
1146        IWorkflowInfo(self.student).fireTransition('admit')
1147        # An existing HOS code can only be used if students
1148        # are in accommodation session
1149        self.student['studycourse'].current_session = 2003
1150        self.browser.getLink("Book accommodation").click()
1151        self.assertMatches('...Your current session does not match...',
1152                           self.browser.contents)
1153        self.student['studycourse'].current_session = 2004
1154        # All requirements are met and ticket can be created
1155        self.browser.getLink("Book accommodation").click()
1156        self.assertMatches('...Activation Code:...',
1157                           self.browser.contents)
1158        self.browser.getControl(name="ac_series").value = sfeseries
1159        self.browser.getControl(name="ac_number").value = sfenumber
1160        self.browser.getControl("Create bed ticket").click()
1161        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1162                           self.browser.contents)
1163        # Bed has been allocated
1164        bed1 = self.app['hostels']['hall-1']['hall-1_A_101_A']
1165        self.assertTrue(bed1.owner == self.student_id)
1166        # BedTicketAddPage is now blocked
1167        self.browser.getLink("Book accommodation").click()
1168        self.assertMatches('...You already booked a bed space...',
1169            self.browser.contents)
1170        # The bed ticket displays the data correctly
1171        self.browser.open(self.acco_student_path + '/2004')
1172        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1173                           self.browser.contents)
1174        self.assertMatches('...2004/2005...', self.browser.contents)
1175        self.assertMatches('...regular_male_fr...', self.browser.contents)
1176        self.assertMatches('...%s...' % pin, self.browser.contents)
1177        # Managers can relocate students if the student's bed_type has changed
1178        self.browser.getLink("Relocate student").click()
1179        self.assertMatches(
1180            "...Student can't be relocated...", self.browser.contents)
1181        self.student.sex = u'f'
1182        self.browser.getLink("Relocate student").click()
1183        self.assertMatches(
1184            "...Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
1185        self.assertTrue(bed1.owner == NOT_OCCUPIED)
1186        bed2 = self.app['hostels']['hall-1']['hall-1_A_101_B']
1187        self.assertTrue(bed2.owner == self.student_id)
1188        self.assertTrue(self.student['accommodation'][
1189            '2004'].bed_type == u'regular_female_fr')
1190        # The payment object still shows the original payment item
1191        payment_id = self.student['payments'].keys()[0]
1192        payment = self.student['payments'][payment_id]
1193        self.assertTrue(payment.p_item == u'regular_male_fr')
1194        # Managers can relocate students if the bed's bed_type has changed
1195        bed1.bed_type = u'regular_female_fr'
1196        bed2.bed_type = u'regular_male_fr'
1197        notify(grok.ObjectModifiedEvent(bed1))
1198        notify(grok.ObjectModifiedEvent(bed2))
1199        self.browser.getLink("Relocate student").click()
1200        self.assertMatches(
1201            "...Student relocated...", self.browser.contents)
1202        self.assertMatches(
1203            "... Hall 1, Block A, Room 101, Bed A...", self.browser.contents)
1204        self.assertMatches(bed1.owner, self.student_id)
1205        self.assertMatches(bed2.owner, NOT_OCCUPIED)
1206        # Managers can't relocate students if bed is reserved
1207        self.student.sex = u'm'
1208        bed1.bed_type = u'regular_female_reserved'
1209        notify(grok.ObjectModifiedEvent(bed1))
1210        self.browser.getLink("Relocate student").click()
1211        self.assertMatches(
1212            "...Students in reserved beds can't be relocated...",
1213            self.browser.contents)
1214        # Managers can relocate students if booking has been cancelled but
1215        # other bed space has been manually allocated after cancellation
1216        old_owner = bed1.releaseBed()
1217        self.assertMatches(old_owner, self.student_id)
1218        bed2.owner = self.student_id
1219        self.browser.open(self.acco_student_path + '/2004')
1220        self.assertMatches(
1221            "...booking cancelled...", self.browser.contents)
1222        self.browser.getLink("Relocate student").click()
1223        # We didn't informed the catalog therefore the new owner is not found
1224        self.assertMatches(
1225            "...There is no free bed in your category regular_male_fr...",
1226            self.browser.contents)
1227        # Now we fire the event properly
1228        notify(grok.ObjectModifiedEvent(bed2))
1229        self.browser.getLink("Relocate student").click()
1230        self.assertMatches(
1231            "...Student relocated...", self.browser.contents)
1232        self.assertMatches(
1233            "... Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
1234          # Managers can delete bed tickets
1235        self.browser.open(self.acco_student_path)
1236        ctrl = self.browser.getControl(name='val_id')
1237        value = ctrl.options[0]
1238        ctrl.getControl(value=value).selected = True
1239        self.browser.getControl("Remove selected", index=0).click()
1240        self.assertMatches('...Successfully removed...', self.browser.contents)
1241        # The bed has been properly released by the event handler
1242        self.assertMatches(bed1.owner, NOT_OCCUPIED)
1243        self.assertMatches(bed2.owner, NOT_OCCUPIED)
1244        return
1245
1246    def test_student_accommodation(self):
1247        # Login
1248        self.browser.open(self.login_path)
1249        self.browser.getControl(name="form.login").value = self.student_id
1250        self.browser.getControl(name="form.password").value = 'spwd'
1251        self.browser.getControl("Login").click()
1252
1253        # Students can add online booking fee payment tickets and open the
1254        # callback view (see test_manage_payments)
1255        self.browser.getLink("Payments").click()
1256        self.browser.getControl("Add online payment ticket").click()
1257        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1258        self.browser.getControl("Create ticket").click()
1259        ctrl = self.browser.getControl(name='val_id')
1260        value = ctrl.options[0]
1261        self.browser.getLink(value).click()
1262        self.browser.open(self.browser.url + '/callback')
1263        # The new HOS-0 pin has been created
1264        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
1265        pin = self.app['accesscodes']['HOS-0'].keys()[0]
1266        ac = self.app['accesscodes']['HOS-0'][pin]
1267        ac.owner = u'Anybody'
1268        parts = pin.split('-')[1:]
1269        sfeseries, sfenumber = parts
1270
1271        # Students can use HOS code and book a bed space with it
1272        self.browser.open(self.acco_student_path)
1273        self.browser.getLink("Book accommodation").click()
1274        self.assertMatches('...You are in the wrong...',
1275                           self.browser.contents)
1276        IWorkflowInfo(self.student).fireTransition('admit')
1277        self.browser.getLink("Book accommodation").click()
1278        self.assertMatches('...Activation Code:...',
1279                           self.browser.contents)
1280        self.browser.getControl(name="ac_series").value = u'nonsense'
1281        self.browser.getControl(name="ac_number").value = sfenumber
1282        self.browser.getControl("Create bed ticket").click()
1283        self.assertMatches('...Activation code is invalid...',
1284                           self.browser.contents)
1285        self.browser.getControl(name="ac_series").value = sfeseries
1286        self.browser.getControl(name="ac_number").value = sfenumber
1287        self.browser.getControl("Create bed ticket").click()
1288        self.assertMatches('...You are not the owner of this access code...',
1289                           self.browser.contents)
1290        ac.owner = self.student_id
1291        self.browser.getControl(name="ac_series").value = sfeseries
1292        self.browser.getControl(name="ac_number").value = sfenumber
1293        self.browser.getControl("Create bed ticket").click()
1294        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1295                           self.browser.contents)
1296
1297        # Bed has been allocated
1298        bed = self.app['hostels']['hall-1']['hall-1_A_101_A']
1299        self.assertTrue(bed.owner == self.student_id)
1300
1301        # BedTicketAddPage is now blocked
1302        self.browser.getLink("Book accommodation").click()
1303        self.assertMatches('...You already booked a bed space...',
1304            self.browser.contents)
1305
1306        # The bed ticket displays the data correctly
1307        self.browser.open(self.acco_student_path + '/2004')
1308        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1309                           self.browser.contents)
1310        self.assertMatches('...2004/2005...', self.browser.contents)
1311        self.assertMatches('...regular_male_fr...', self.browser.contents)
1312        self.assertMatches('...%s...' % pin, self.browser.contents)
1313
1314        # Students can open the pdf slip
1315        self.browser.open(self.browser.url + '/bed_allocation.pdf')
1316        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1317        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1318
1319        # Students can't relocate themselves
1320        self.assertFalse('Relocate' in self.browser.contents)
1321        relocate_path = self.acco_student_path + '/2004/relocate'
1322        self.assertRaises(
1323            Unauthorized, self.browser.open, relocate_path)
1324
1325        # Students can't the Remove button and check boxes
1326        self.browser.open(self.acco_student_path)
1327        self.assertFalse('Remove' in self.browser.contents)
1328        self.assertFalse('val_id' in self.browser.contents)
1329        return
Note: See TracBrowser for help on using the repository browser.