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

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

Restrict view access for clearance officers only to students which can be cleared by the officer. No further site role is required (see comment in browser_test)

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