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

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

academics: Show students in departments.

students: Search for students in department.

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