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

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

Redirect to contact form after rejecting clearance and pre-fill subject line.

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