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

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

Replace fullname form field by first-, middle- and lastname form fields.

We don't need a studentaddpage.pt.

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