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

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

The StudentApplication? class is deprecated. We want to store the application_slip pdf file file instead.

Prepare everything in the students package for downloading such a pdf file.

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