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

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

Test student access with uploading a real jpeg passport file.

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