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

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

Managers can access studycourse even if student doesn't have a certificate (regression test)

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