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

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

Add indexes faccode, depcode and certcode to students_catalog.

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