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

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

Use jquery tabs in StudentBaseManageFormPage? and StudentClearanceManageFormPage? forms to separate form data and file upload widgets.

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