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

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

Use the viewlet manager 'FileManager?' to render either images or placeholders of uploaded files.

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