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

Last change on this file since 7338 was 7337, checked in by Henrik Bettermann, 14 years ago

Add missing UI tests for course advisers.

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