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

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

Add action buttons for clearance officers.

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