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

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

Add second file upload viewlet, add tests and repair FileUpload? methods. Each button must get a dedicated id and name.

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