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

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

Start internationalization of students package (work in progress).

  • Property svn:keywords set to Id
File size: 85.0 KB
Line 
1## $Id: test_browser.py 7723 2012-02-29 13:29:35Z 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 os
26import grok
27from zope.event import notify
28from zope.component import createObject, queryUtility
29from zope.component.hooks import setSite, clearSite
30from zope.catalog.interfaces import ICatalog
31from zope.security.interfaces import Unauthorized
32from zope.securitypolicy.interfaces import IPrincipalRoleManager
33from zope.testbrowser.testing import Browser
34from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
35from waeup.sirp.testing import FunctionalLayer, FunctionalTestCase
36from waeup.sirp.app import University
37from waeup.sirp.configuration import SessionConfiguration
38from waeup.sirp.students.student import Student
39from waeup.sirp.students.studylevel import StudentStudyLevel
40from waeup.sirp.university.faculty import Faculty
41from waeup.sirp.university.department import Department
42from waeup.sirp.interfaces import IUserAccount
43from waeup.sirp.authentication import LocalRoleSetEvent
44from waeup.sirp.hostels.hostel import Hostel, Bed, NOT_OCCUPIED
45
46PH_LEN = 2059  # Length of placeholder file
47
48def lookup_submit_value(name, value, browser):
49    """Find a button with a certain value."""
50    for num in range(0, 100):
51        try:
52            button = browser.getControl(name=name, index=num)
53            if button.value.endswith(value):
54                return button
55        except IndexError:
56            break
57    return None
58
59class StudentsFullSetup(FunctionalTestCase):
60    # A test case that only contains a setup and teardown
61    #
62    # Complete setup for students handlings is rather complex and
63    # requires lots of things created before we can start. This is a
64    # setup that does all this, creates a university, creates PINs,
65    # etc.  so that we do not have to bother with that in different
66    # test cases.
67
68    layer = FunctionalLayer
69
70    def setUp(self):
71        super(StudentsFullSetup, self).setUp()
72
73        # Setup a sample site for each test
74        app = University()
75        self.dc_root = tempfile.mkdtemp()
76        app['datacenter'].setStoragePath(self.dc_root)
77
78        # Prepopulate the ZODB...
79        self.getRootFolder()['app'] = app
80        # we add the site immediately after creation to the
81        # ZODB. Catalogs and other local utilities are not setup
82        # before that step.
83        self.app = self.getRootFolder()['app']
84        # Set site here. Some of the following setup code might need
85        # to access grok.getSite() and should get our new app then
86        setSite(app)
87
88        # Add student with subobjects
89        student = Student()
90        student.firstname = u'Anna'
91        student.lastname = u'Tester'
92        student.reg_number = u'123'
93        student.matric_number = u'234'
94        student.sex = u'm'
95        student.email = 'aa@aa.ng'
96        student.phone = u'1234'
97        self.app['students'].addStudent(student)
98        self.student_id = student.student_id
99        self.student = self.app['students'][self.student_id]
100
101        # Set password
102        IUserAccount(
103            self.app['students'][self.student_id]).setPassword('spwd')
104
105        self.login_path = 'http://localhost/app/login'
106        self.container_path = 'http://localhost/app/students'
107        self.manage_container_path = self.container_path + '/@@manage'
108        self.add_student_path = self.container_path + '/addstudent'
109        self.student_path = self.container_path + '/' + self.student_id
110        self.manage_student_path = self.student_path + '/manage_base'
111        self.clearance_student_path = self.student_path + '/view_clearance'
112        self.personal_student_path = self.student_path + '/view_personal'
113        self.edit_clearance_student_path = self.student_path + '/edit_clearance'
114        self.edit_personal_student_path = self.student_path + '/edit_personal'
115        self.studycourse_student_path = self.student_path + '/studycourse'
116        self.payments_student_path = self.student_path + '/payments'
117        self.acco_student_path = self.student_path + '/accommodation'
118        self.history_student_path = self.student_path + '/history'
119
120        # Create 5 access codes with prefix'PWD'
121        pin_container = self.app['accesscodes']
122        pin_container.createBatch(
123            datetime.now(), 'some_userid', 'PWD', 9.99, 5)
124        pins = pin_container['PWD-1'].values()
125        self.pwdpins = [x.representation for x in pins]
126        self.existing_pwdpin = self.pwdpins[0]
127        parts = self.existing_pwdpin.split('-')[1:]
128        self.existing_pwdseries, self.existing_pwdnumber = parts
129        # Create 5 access codes with prefix 'CLR'
130        pin_container.createBatch(
131            datetime.now(), 'some_userid', 'CLR', 9.99, 5)
132        pins = pin_container['CLR-1'].values()
133        pins[0].owner = u'Hans Wurst'
134        self.existing_clrac = pins[0]
135        self.existing_clrpin = pins[0].representation
136        parts = self.existing_clrpin.split('-')[1:]
137        self.existing_clrseries, self.existing_clrnumber = parts
138        # Create 2 access codes with prefix 'HOS'
139        pin_container.createBatch(
140            datetime.now(), 'some_userid', 'HOS', 9.99, 2)
141        pins = pin_container['HOS-1'].values()
142        self.existing_hosac = pins[0]
143        self.existing_hospin = pins[0].representation
144        parts = self.existing_hospin.split('-')[1:]
145        self.existing_hosseries, self.existing_hosnumber = parts
146
147        # Populate university
148        self.certificate = createObject('waeup.Certificate')
149        self.certificate.code = u'CERT1'
150        self.certificate.application_category = 'basic'
151        self.certificate.study_mode = 'ug_ft'
152        self.certificate.start_level = 100
153        self.certificate.end_level = 500
154        self.app['faculties']['fac1'] = Faculty()
155        self.app['faculties']['fac1']['dep1'] = Department(code='dep1')
156        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
157            self.certificate)
158        self.course = createObject('waeup.Course')
159        self.course.code = 'COURSE1'
160        self.course.semester = 1
161        self.course.credits = 10
162        self.course.passmark = 40
163        self.app['faculties']['fac1']['dep1'].courses.addCourse(
164            self.course)
165        self.app['faculties']['fac1']['dep1'].certificates['CERT1'].addCourseRef(
166            self.course, level=100)
167
168        # Configure university
169        self.app['configuration'].accommodation_states = ['admitted']
170        self.app['configuration'].accommodation_session = 2004
171        self.app['configuration'].carry_over = True
172        configuration = SessionConfiguration()
173        # These attributes must also exist in the customization packages.
174        configuration.academic_session = 2004
175        configuration.fee_1 = 20000
176        configuration.boocking_fee = 500
177        self.app['configuration'].addSessionConfiguration(configuration)
178
179        # Create a hostel with two beds
180        hostel = Hostel()
181        hostel.hostel_id = u'hall-1'
182        hostel.hostel_name = u'Hall 1'
183        self.app['hostels'].addHostel(hostel)
184        bed = Bed()
185        bed.bed_id = u'hall-1_A_101_A'
186        bed.bed_number = 1
187        bed.owner = NOT_OCCUPIED
188        bed.bed_type = u'regular_male_fr'
189        self.app['hostels'][hostel.hostel_id].addBed(bed)
190        bed = Bed()
191        bed.bed_id = u'hall-1_A_101_B'
192        bed.bed_number = 2
193        bed.owner = NOT_OCCUPIED
194        bed.bed_type = u'regular_female_fr'
195        self.app['hostels'][hostel.hostel_id].addBed(bed)
196
197        # Set study course attributes of test student
198        self.student['studycourse'].certificate = self.certificate
199        self.student['studycourse'].current_session = 2004
200        self.student['studycourse'].entry_session = 2004
201        self.student['studycourse'].current_verdict = 'A'
202        self.student['studycourse'].current_level = 100
203        # Update the catalog
204        notify(grok.ObjectModifiedEvent(self.student))
205
206        # Put the prepopulated site into test ZODB and prepare test
207        # browser
208        self.browser = Browser()
209        self.browser.handleErrors = False
210
211    def tearDown(self):
212        super(StudentsFullSetup, self).tearDown()
213        clearSite()
214        shutil.rmtree(self.dc_root)
215
216
217
218class StudentsContainerUITests(StudentsFullSetup):
219    # Tests for StudentsContainer class views and pages
220
221    layer = FunctionalLayer
222
223    def test_anonymous_access(self):
224        # Anonymous users can't access students containers
225        self.assertRaises(
226            Unauthorized, self.browser.open, self.container_path)
227        self.assertRaises(
228            Unauthorized, self.browser.open, self.manage_container_path)
229        return
230
231    def test_manage_access(self):
232        # Managers can access the view page of students
233        # containers and can perform actions
234        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
235        self.browser.open(self.container_path)
236        self.assertEqual(self.browser.headers['Status'], '200 Ok')
237        self.assertEqual(self.browser.url, self.container_path)
238        self.browser.getLink("Manage student section").click()
239        self.assertEqual(self.browser.headers['Status'], '200 Ok')
240        self.assertEqual(self.browser.url, self.manage_container_path)
241        return
242
243    def test_add_search_delete_students(self):
244        # Managers can add search and remove students
245        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
246        self.browser.open(self.manage_container_path)
247        self.browser.getLink("Add student").click()
248        self.assertEqual(self.browser.headers['Status'], '200 Ok')
249        self.assertEqual(self.browser.url, self.add_student_path)
250        self.browser.getControl(name="form.firstname").value = 'Bob'
251        self.browser.getControl(name="form.lastname").value = 'Tester'
252        self.browser.getControl(name="form.reg_number").value = '1234'
253        self.browser.getControl("Create student record").click()
254        self.assertTrue('Student record created' in self.browser.contents)
255
256        # Registration and matric numbers must be unique
257        self.browser.getLink("Manage").click()
258        self.browser.getControl(name="form.reg_number").value = '123'
259        self.browser.getControl("Save").click()
260        self.assertMatches('...Registration number exists...',
261                           self.browser.contents)
262        self.browser.getControl(name="form.reg_number").value = '789'
263        self.browser.getControl(name="form.matric_number").value = '234'
264        self.browser.getControl("Save").click()
265        self.assertMatches('...Matriculation number exists...',
266                           self.browser.contents)
267
268        # We can find a student with a certain student_id
269        self.browser.open(self.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 = ['student_id']
273        self.browser.getControl(name="searchterm").value = self.student_id
274        self.browser.getControl("Search").click()
275        self.assertTrue('Anna Tester' in self.browser.contents)
276
277        # We can find a student in a certain session
278        self.browser.open(self.container_path)
279        self.browser.getControl(name="searchtype").value = ['current_session']
280        self.browser.getControl(name="searchterm").value = '2004'
281        self.browser.getControl("Search").click()
282        self.assertTrue('Anna Tester' in self.browser.contents)
283
284        # We can find a student in a certain study_mode
285        self.browser.open(self.container_path)
286        self.browser.getControl(name="searchtype").value = ['current_mode']
287        self.browser.getControl(name="searchterm").value = 'ug_ft'
288        self.browser.getControl("Search").click()
289        self.assertTrue('Anna Tester' in self.browser.contents)
290
291        # We can find a student in a certain department
292        self.browser.open(self.container_path)
293        self.browser.getControl(name="searchtype").value = ['depcode']
294        self.browser.getControl(name="searchterm").value = 'dep1'
295        self.browser.getControl("Search").click()
296        self.assertTrue('Anna Tester' in self.browser.contents)
297
298        # We can find a student by searching for all kind of name parts
299        self.browser.open(self.manage_container_path)
300        self.browser.getControl("Search").click()
301        self.assertTrue('Empty search string' in self.browser.contents)
302        self.browser.getControl(name="searchtype").value = ['fullname']
303        self.browser.getControl(name="searchterm").value = 'Anna Tester'
304        self.browser.getControl("Search").click()
305        self.assertTrue('Anna Tester' in self.browser.contents)
306        self.browser.open(self.manage_container_path)
307        self.browser.getControl(name="searchtype").value = ['fullname']
308        self.browser.getControl(name="searchterm").value = 'Anna'
309        self.browser.getControl("Search").click()
310        self.assertTrue('Anna Tester' in self.browser.contents)
311        self.browser.open(self.manage_container_path)
312        self.browser.getControl(name="searchtype").value = ['fullname']
313        self.browser.getControl(name="searchterm").value = 'Tester'
314        self.browser.getControl("Search").click()
315        self.assertTrue('Anna Tester' in self.browser.contents)
316        self.browser.open(self.manage_container_path)
317        self.browser.getControl(name="searchtype").value = ['fullname']
318        self.browser.getControl(name="searchterm").value = 'An'
319        self.browser.getControl("Search").click()
320        self.assertFalse('Anna Tester' in self.browser.contents)
321        self.browser.open(self.manage_container_path)
322        self.browser.getControl(name="searchtype").value = ['fullname']
323        self.browser.getControl(name="searchterm").value = 'An*'
324        self.browser.getControl("Search").click()
325        self.assertTrue('Anna Tester' in self.browser.contents)
326        self.browser.open(self.manage_container_path)
327        self.browser.getControl(name="searchtype").value = ['fullname']
328        self.browser.getControl(name="searchterm").value = 'tester'
329        self.browser.getControl("Search").click()
330        self.assertTrue('Anna Tester' in self.browser.contents)
331        self.browser.open(self.manage_container_path)
332        self.browser.getControl(name="searchtype").value = ['fullname']
333        self.browser.getControl(name="searchterm").value = 'Tester Ana'
334        self.browser.getControl("Search").click()
335        self.assertFalse('Anna Tester' in self.browser.contents)
336        self.browser.open(self.manage_container_path)
337        self.browser.getControl(name="searchtype").value = ['fullname']
338        self.browser.getControl(name="searchterm").value = 'Tester Anna'
339        self.browser.getControl("Search").click()
340        self.assertTrue('Anna Tester' in self.browser.contents)
341        # The old searchterm will be used again
342        self.browser.getControl("Search").click()
343        self.assertTrue('Anna Tester' in self.browser.contents)
344
345        ctrl = self.browser.getControl(name='entries')
346        ctrl.getControl(value=self.student_id).selected = True
347        self.browser.getControl("Remove selected", index=0).click()
348        self.assertTrue('Successfully removed' in self.browser.contents)
349        self.browser.getControl(name="searchtype").value = ['student_id']
350        self.browser.getControl(name="searchterm").value = self.student_id
351        self.browser.getControl("Search").click()
352        self.assertTrue('No student found' in self.browser.contents)
353
354        self.browser.open(self.container_path)
355        self.browser.getControl(name="searchtype").value = ['student_id']
356        self.browser.getControl(name="searchterm").value = self.student_id
357        self.browser.getControl("Search").click()
358        self.assertTrue('No student found' in self.browser.contents)
359        return
360
361class StudentUITests(StudentsFullSetup):
362    # Tests for Student class views and pages
363
364    layer = FunctionalLayer
365
366    def test_basic_auth(self):
367        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
368        self.browser.open('http://localhost/app')
369        self.browser.getLink("Logout").click()
370        self.assertTrue('You have been logged out' in self.browser.contents)
371        # But we are still logged in since we've used basic authentication here.
372        # Wikipedia says: Existing browsers retain authentication information
373        # until the tab or browser is closed or the user clears the history.
374        # HTTP does not provide a method for a server to direct clients to
375        # discard these cached credentials. This means that there is no
376        # effective way for a server to "log out" the user without closing
377        # the browser. This is a significant defect that requires browser
378        # manufacturers to support a "logout" user interface element ...
379        self.assertTrue('Manager' in self.browser.contents)
380
381    def test_manage_access(self):
382        # Managers can access the pages of students
383        # and can perform actions
384        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
385        self.browser.open(self.student_path)
386        self.assertEqual(self.browser.headers['Status'], '200 Ok')
387        self.assertEqual(self.browser.url, self.student_path)
388        self.browser.getLink("Manage").click()
389        self.assertEqual(self.browser.headers['Status'], '200 Ok')
390        self.assertEqual(self.browser.url, self.manage_student_path)
391        # Managers can edit base data and fire transitions
392        self.browser.getControl(name="transition").value = ['admit']
393        self.browser.getControl(name="form.firstname").value = 'John'
394        self.browser.getControl(name="form.lastname").value = 'Tester'
395        self.browser.getControl(name="form.reg_number").value = '345'
396        self.browser.getControl(name="password").value = 'secret'
397        self.browser.getControl(name="control_password").value = 'secret'
398        self.browser.getControl("Save").click()
399        self.assertMatches('...Form has been saved...',
400                           self.browser.contents)
401        self.browser.open(self.student_path)
402        self.browser.getLink("Clearance Data").click()
403        self.assertEqual(self.browser.headers['Status'], '200 Ok')
404        self.assertEqual(self.browser.url, self.clearance_student_path)
405        self.browser.getLink("Manage").click()
406        self.assertEqual(self.browser.headers['Status'], '200 Ok')
407        self.assertEqual(self.browser.url, self.edit_clearance_student_path)
408        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
409        self.browser.getControl("Save").click()
410        self.assertMatches('...Form has been saved...',
411                           self.browser.contents)
412
413        self.browser.open(self.student_path)
414        self.browser.getLink("Personal Data").click()
415        self.assertEqual(self.browser.headers['Status'], '200 Ok')
416        self.assertEqual(self.browser.url, self.personal_student_path)
417        self.browser.getLink("Edit").click()
418        self.assertEqual(self.browser.headers['Status'], '200 Ok')
419        self.assertEqual(self.browser.url, self.edit_personal_student_path)
420        self.browser.getControl("Save").click()
421        self.assertMatches('...Form has been saved...',
422                           self.browser.contents)
423
424        # Managers can browse all subobjects
425        self.browser.open(self.student_path)
426        self.browser.getLink("Payments").click()
427        self.assertEqual(self.browser.headers['Status'], '200 Ok')
428        self.assertEqual(self.browser.url, self.payments_student_path)
429        self.browser.open(self.student_path)
430        self.browser.getLink("Accommodation").click()
431        self.assertEqual(self.browser.headers['Status'], '200 Ok')
432        self.assertEqual(self.browser.url, self.acco_student_path)
433        self.browser.open(self.student_path)
434        self.browser.getLink("History").click()
435        self.assertEqual(self.browser.headers['Status'], '200 Ok')
436        self.assertEqual(self.browser.url, self.history_student_path)
437        self.assertMatches('...Student admitted by Manager...',
438                           self.browser.contents)
439        # Only the Application Slip does not exist
440        self.assertFalse('Application Slip' in self.browser.contents)
441        return
442
443    def test_manage_contact_student(self):
444        # Managers can contact student
445        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
446        self.browser.open(self.student_path)
447        self.browser.getLink("Send email").click()
448        self.browser.getControl(name="form.subject").value = 'Important subject'
449        self.browser.getControl(name="form.body").value = 'Hello!'
450        self.browser.getControl("Send message now").click()
451        self.assertTrue('Your message has been sent' in self.browser.contents)
452        return
453
454    def test_manage_remove_department(self):
455        # Lazy student is studying CERT1
456        lazystudent = Student()
457        lazystudent.firstname = u'Lazy'
458        lazystudent.lastname = u'Student'
459        self.app['students'].addStudent(lazystudent)
460        student_id = lazystudent.student_id
461        student_path = self.container_path + '/' + student_id
462        lazystudent['studycourse'].certificate = self.certificate
463        notify(grok.ObjectModifiedEvent(lazystudent))
464        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
465        self.browser.open(student_path + '/studycourse')
466        self.assertTrue('CERT1' in self.browser.contents)
467        # After some years the department is removed
468        del self.app['faculties']['fac1']['dep1']
469        # So CERT1 does no longer exist and lazy student's
470        # certificate reference is removed too
471        self.browser.open(student_path + '/studycourse')
472        self.assertEqual(self.browser.headers['Status'], '200 Ok')
473        self.assertEqual(self.browser.url, student_path + '/studycourse')
474        self.assertFalse('CERT1' in self.browser.contents)
475        self.assertMatches('...<div class="widget">Nothing</div>...',
476                           self.browser.contents)
477
478    def test_manage_upload_file(self):
479        # Managers can upload a file via the StudentClearanceManageFormPage
480        # The image is stored even if form has errors
481        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
482        self.browser.open(self.edit_clearance_student_path)
483        # No birth certificate has been uploaded yet
484        # Browsing the link shows a placerholder image
485        self.browser.open('birth_certificate')
486        self.assertEqual(
487            self.browser.headers['content-type'], 'image/jpeg')
488        self.assertEqual(len(self.browser.contents), PH_LEN)
489        # Create a pseudo image file and select it to be uploaded in form
490        # as birth certificate
491        self.browser.open(self.edit_clearance_student_path)
492        pseudo_image = StringIO('I pretend to be a graphics file')
493        ctrl = self.browser.getControl(name='birthcertificateupload')
494        file_ctrl = ctrl.mech_control
495        file_ctrl.add_file(pseudo_image, filename='my_birth_certificate.jpg')
496        # The Save action does not upload files
497        self.browser.getControl("Save").click() # submit form
498        self.assertFalse(
499            '<a target="image" href="birth_certificate">'
500            in self.browser.contents)
501        # ... but the correct upload submit button does
502        pseudo_image = StringIO('I pretend to be a graphics file')
503        ctrl = self.browser.getControl(name='birthcertificateupload')
504        file_ctrl = ctrl.mech_control
505        file_ctrl.add_file(pseudo_image, filename='my_birth_certificate.jpg')
506        self.browser.getControl(
507            name='upload_birthcertificateupload').click()
508        # There is a correct <img> link included
509        self.assertTrue(
510            '<a target="image" href="birth_certificate">'
511            in self.browser.contents)
512
513        # Browsing the link shows a real image
514        self.browser.open('birth_certificate')
515        self.assertEqual(
516            self.browser.headers['content-type'], 'image/jpeg')
517        self.assertEqual(len(self.browser.contents), 31)
518        # Reuploading a file which is bigger than 150k will raise an error
519        self.browser.open(self.edit_clearance_student_path)
520        photo_content = 'A' * 1024 * 151  # A string of 21 KB size
521        pseudo_image = StringIO(photo_content)
522        ctrl = self.browser.getControl(name='birthcertificateupload')
523        file_ctrl = ctrl.mech_control
524        file_ctrl.add_file(pseudo_image, filename='my_birth_certificate.jpg')
525        self.browser.getControl(
526            name='upload_birthcertificateupload').click()
527        self.assertTrue(
528            'Uploaded file is too big' in self.browser.contents)
529        # File names must meet several conditions
530        pseudo_image = StringIO('I pretend to be a graphics file')
531        ctrl = self.browser.getControl(name='birthcertificateupload')
532        file_ctrl = ctrl.mech_control
533        file_ctrl.add_file(pseudo_image, filename='my.photo.jpg')
534        self.browser.getControl(
535            name='upload_birthcertificateupload').click()
536        self.assertTrue('File name contains more than one dot'
537            in self.browser.contents)
538        ctrl = self.browser.getControl(name='birthcertificateupload')
539        file_ctrl = ctrl.mech_control
540        file_ctrl.add_file(pseudo_image, filename='my_birth_certificate')
541        self.browser.getControl(
542            name='upload_birthcertificateupload').click()
543        self.assertTrue('File name has no extension' in self.browser.contents)
544        ctrl = self.browser.getControl(name='birthcertificateupload')
545        file_ctrl = ctrl.mech_control
546        file_ctrl.add_file(pseudo_image, filename='my_birth_certificate.bmp')
547        self.browser.getControl(
548            name='upload_birthcertificateupload').click()
549        self.assertTrue('Only the following extension are allowed'
550            in self.browser.contents)
551        # Managers can delete files
552        self.browser.getControl(name='delete_birthcertificateupload').click()
553        self.assertTrue(
554            'birth_certificate deleted' in self.browser.contents)
555        # Managers can add and delete second file
556        self.browser.open(self.edit_clearance_student_path)
557        pseudo_image = StringIO('I pretend to be a graphics file')
558        ctrl = self.browser.getControl(name='birthcertificateupload')
559        file_ctrl = ctrl.mech_control
560        file_ctrl.add_file(pseudo_image, filename='my_acceptance_letter.jpg')
561        self.browser.getControl(
562            name='upload_acceptanceletterupload').click()
563        self.assertFalse(
564            '<a target="image" href="acceptance_letter">'
565            in self.browser.contents)
566        ctrl = self.browser.getControl(name='acceptanceletterupload')
567        file_ctrl = ctrl.mech_control
568        file_ctrl.add_file(pseudo_image, filename='my_acceptance_letter.jpg')
569        self.browser.getControl(
570            name='upload_acceptanceletterupload').click()
571        self.assertTrue(
572            '<a target="image" href="acceptance_letter">'
573            in self.browser.contents)
574        self.browser.getControl(
575            name='delete_acceptanceletterupload').click()
576        self.assertTrue(
577            'acceptance_letter deleted'
578            in self.browser.contents)
579        # Managers can upload a file via the StudentBaseManageFormPage
580        self.browser.open(self.manage_student_path)
581        pseudo_image = StringIO('I pretend to be a graphics file')
582        ctrl = self.browser.getControl(name='passportuploadmanage')
583        file_ctrl = ctrl.mech_control
584        file_ctrl.add_file(pseudo_image, filename='my_photo.bmp')
585        self.browser.getControl(
586            name='upload_passportuploadmanage').click()
587        self.assertTrue('jpg file extension expected'
588            in self.browser.contents)
589        ctrl = self.browser.getControl(name='passportuploadmanage')
590        file_ctrl = ctrl.mech_control
591        file_ctrl.add_file(pseudo_image, filename='my_photo.jpg')
592        self.browser.getControl(
593            name='upload_passportuploadmanage').click()
594        self.assertTrue(
595            '<img align="middle" height="125px" src="passport.jpg" />'
596            in self.browser.contents)
597        # The clearance slip can't be opened because it requires a proper
598        # passport jpg file.
599        self.browser.open(self.student_path + '/clearance.pdf')
600        self.assertEqual(self.browser.headers['Status'], '200 Ok')
601        self.assertTrue('Error in image file' in self.browser.contents)
602        # We remove the passport file again
603        self.browser.open(self.manage_student_path)
604        self.browser.getControl('Delete').click()
605        self.browser.open(self.student_path + '/clearance.pdf')
606        self.assertEqual(self.browser.headers['Status'], '200 Ok')
607        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
608
609    def test_manage_course_lists(self):
610        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
611        self.browser.open(self.student_path)
612        self.browser.getLink("Study Course").click()
613        self.assertEqual(self.browser.headers['Status'], '200 Ok')
614        self.assertEqual(self.browser.url, self.studycourse_student_path)
615        self.assertTrue('Undergraduate Full Time' in self.browser.contents)
616        self.browser.getLink("Manage").click()
617        self.assertTrue('Manage study course' in self.browser.contents)
618        # Before we can select a level, the certificate must
619        # be selected and saved
620        self.browser.getControl(name="form.certificate").value = ['CERT1']
621        self.browser.getControl(name="form.current_session").value = ['2004']
622        self.browser.getControl(name="form.current_verdict").value = ['A']
623        self.browser.getControl("Save").click()
624        # Now we can save also the current level which depends on start and end
625        # level of the certificate
626        self.browser.getControl(name="form.current_level").value = ['100']
627        self.browser.getControl("Save").click()
628        # Managers can add and remove any study level (course list)
629        self.browser.getControl(name="addlevel").value = ['100']
630        self.browser.getControl("Add study level").click()
631        self.assertMatches('...<span>100</span>...', self.browser.contents)
632        self.browser.getControl("Add study level").click()
633        self.assertMatches('...This level exists...', self.browser.contents)
634        self.browser.getControl("Remove selected").click()
635        self.assertMatches(
636            '...No study level selected...', self.browser.contents)
637        self.browser.getControl(name="val_id").value = ['100']
638        self.browser.getControl("Remove selected").click()
639        self.assertMatches('...Successfully removed...', self.browser.contents)
640        # Add level again
641        self.browser.getControl(name="addlevel").value = ['100']
642        self.browser.getControl("Add study level").click()
643
644        # Managers can view and manage course lists
645        self.browser.getLink("100").click()
646        self.assertMatches(
647            '...: Study Level 100 (Year 1)...', self.browser.contents)
648        self.browser.getLink("Manage").click()
649        self.browser.getControl(name="form.level_session").value = ['2002']
650        self.browser.getControl("Save").click()
651        self.browser.getControl("Remove selected").click()
652        self.assertMatches('...No ticket selected...', self.browser.contents)
653        ctrl = self.browser.getControl(name='val_id')
654        ctrl.getControl(value='COURSE1').selected = True
655        self.browser.getControl("Remove selected", index=0).click()
656        self.assertTrue('Successfully removed' in self.browser.contents)
657        self.browser.getControl("Add course ticket").click()
658        self.browser.getControl(name="form.course").value = ['COURSE1']
659        self.browser.getControl("Add course ticket").click()
660        self.assertTrue('Successfully added' in self.browser.contents)
661        self.browser.getControl("Add course ticket").click()
662        self.browser.getControl(name="form.course").value = ['COURSE1']
663        self.browser.getControl("Add course ticket").click()
664        self.assertTrue('The ticket exists' in self.browser.contents)
665        self.browser.getControl("Cancel").click()
666        self.browser.getLink("COURSE1").click()
667        self.browser.getLink("Manage").click()
668        self.browser.getControl(name="form.score").value = '10'
669        self.browser.getControl("Save").click()
670        self.assertTrue('Form has been saved' in self.browser.contents)
671        # Carry-over courses will be collected when next level is created
672        self.browser.open(self.student_path + '/studycourse/manage')
673        # Add next level
674        self.browser.getControl(name="addlevel").value = ['200']
675        self.browser.getControl("Add study level").click()
676        self.browser.getLink("200").click()
677        self.assertMatches(
678            '...: Study Level 200 (Year 2)...', self.browser.contents)
679        # COURSE1 has score 0 and thus will become a carry-over course
680        # in level 200
681        self.assertEqual(
682            sorted(self.student['studycourse']['200'].keys()), [u'COURSE1'])
683        self.assertTrue(
684            self.student['studycourse']['200']['COURSE1'].carry_over)
685        return
686
687    def test_manage_workflow(self):
688        # Managers can pass through the whole workflow
689        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
690        student = self.app['students'][self.student_id]
691        self.browser.open(self.manage_student_path)
692        self.assertTrue(student.clearance_locked)
693        self.browser.getControl(name="transition").value = ['admit']
694        self.browser.getControl("Save").click()
695        self.assertTrue(student.clearance_locked)
696        self.browser.getControl(name="transition").value = ['start_clearance']
697        self.browser.getControl("Save").click()
698        self.assertFalse(student.clearance_locked)
699        self.browser.getControl(name="transition").value = ['request_clearance']
700        self.browser.getControl("Save").click()
701        self.assertTrue(student.clearance_locked)
702        self.browser.getControl(name="transition").value = ['clear']
703        self.browser.getControl("Save").click()
704        self.browser.getControl(
705            name="transition").value = ['pay_first_school_fee']
706        self.browser.getControl("Save").click()
707        self.browser.getControl(name="transition").value = ['reset6']
708        self.browser.getControl("Save").click()
709        # In state returning the pay_school_fee transition triggers some
710        # changes of attributes
711        self.browser.getControl(name="transition").value = ['pay_school_fee']
712        self.browser.getControl("Save").click()
713        self.assertEqual(student['studycourse'].current_session, 2005) # +1
714        self.assertEqual(student['studycourse'].current_level, 200) # +100
715        self.assertEqual(student['studycourse'].current_verdict, '0') # 0 = not set
716        self.assertEqual(student['studycourse'].previous_verdict, 'A')
717        self.browser.getControl(name="transition").value = ['register_courses']
718        self.browser.getControl("Save").click()
719        self.browser.getControl(name="transition").value = ['validate_courses']
720        self.browser.getControl("Save").click()
721        self.browser.getControl(name="transition").value = ['return']
722        self.browser.getControl("Save").click()
723        return
724
725    def test_manage_import(self):
726        # Managers can import student data files
727        datacenter_path = 'http://localhost/app/datacenter'
728        # Prepare a csv file for students
729        open('students.csv', 'wb').write(
730"""firstname,lastname,reg_number,date_of_birth,matric_number,email,phone,password
731Aaren,Pieri,1,1990-01-02,100000,aa@aa.ng,1234,mypwd1
732Claus,Finau,2,1990-01-03,100001,aa@aa.ng,1234,mypwd1
733Brit,Berson,3,1990-01-04,100001,aa@aa.ng,1234,mypwd1
734""")
735        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
736        self.browser.open(datacenter_path)
737        self.browser.getLink('Upload CSV file').click()
738        filecontents = StringIO(open('students.csv', 'rb').read())
739        filewidget = self.browser.getControl(name='uploadfile:file')
740        filewidget.add_file(filecontents, 'text/plain', 'students.csv')
741        self.browser.getControl(name='SUBMIT').click()
742        self.browser.getLink('Batch processing').click()
743        button = lookup_submit_value(
744            'select', 'students_zope.mgr.csv', self.browser)
745        button.click()
746        importerselect = self.browser.getControl(name='importer')
747        modeselect = self.browser.getControl(name='mode')
748        importerselect.getControl('Student Importer').selected = True
749        modeselect.getControl(value='create').selected = True
750        self.browser.getControl('Proceed to step 3').click()
751        self.assertTrue('Header fields OK' in self.browser.contents)
752        self.browser.getControl('Perform import').click()
753        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
754        self.assertTrue('Successfully processed 2 rows' in self.browser.contents)
755        self.assertTrue('Batch processing finished' in self.browser.contents)
756        open('studycourses.csv', 'wb').write(
757"""reg_number,matric_number,certificate,current_session,current_level
7581,,CERT1,2008,100
759,100001,CERT1,2008,100
760,100002,CERT1,2008,100
761""")
762        self.browser.open(datacenter_path)
763        self.browser.getLink('Upload CSV file').click()
764        filecontents = StringIO(open('studycourses.csv', 'rb').read())
765        filewidget = self.browser.getControl(name='uploadfile:file')
766        filewidget.add_file(filecontents, 'text/plain', 'studycourses.csv')
767        self.browser.getControl(name='SUBMIT').click()
768        self.browser.getLink('Batch processing').click()
769        button = lookup_submit_value(
770            'select', 'studycourses_zope.mgr.csv', self.browser)
771        button.click()
772        importerselect = self.browser.getControl(name='importer')
773        modeselect = self.browser.getControl(name='mode')
774        importerselect.getControl(
775            'StudentStudyCourse Importer (update only)').selected = True
776        modeselect.getControl(value='create').selected = True
777        self.browser.getControl('Proceed to step 3').click()
778        self.assertTrue('Update mode only' in self.browser.contents)
779        self.browser.getControl('Proceed to step 3').click()
780        self.assertTrue('Header fields OK' in self.browser.contents)
781        self.browser.getControl('Perform import').click()
782        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
783        self.assertTrue('Successfully processed 2 rows'
784                        in self.browser.contents)
785        # The students are properly indexed and we can
786        # thus find a student in  the department
787        self.browser.open(self.manage_container_path)
788        self.browser.getControl(name="searchtype").value = ['depcode']
789        self.browser.getControl(name="searchterm").value = 'dep1'
790        self.browser.getControl("Search").click()
791        self.assertTrue('Aaren Pieri' in self.browser.contents)
792        # We can search for a new student by name ...
793        self.browser.getControl(name="searchtype").value = ['fullname']
794        self.browser.getControl(name="searchterm").value = 'Claus'
795        self.browser.getControl("Search").click()
796        self.assertTrue('Claus Finau' in self.browser.contents)
797        # ... and check if the imported password has been properly set
798        ctrl = self.browser.getControl(name='entries')
799        value = ctrl.options[0]
800        claus = self.app['students'][value]
801        self.assertTrue(IUserAccount(claus).checkPassword('mypwd1'))
802        return
803
804    def test_handle_clearance_by_co(self):
805        # Create clearance officer
806        self.app['users'].addUser('mrclear', 'mrclearsecret')
807        self.app['users']['mrclear'].email = 'mrclear@foo.ng'
808        self.app['users']['mrclear'].title = 'Carlo Pitter'
809        # Clearance officers need not necessarily to get
810        # the StudentsOfficer site role
811        #prmglobal = IPrincipalRoleManager(self.app)
812        #prmglobal.assignRoleToPrincipal('waeup.StudentsOfficer', 'mrclear')
813        # Assign local ClearanceOfficer role
814        department = self.app['faculties']['fac1']['dep1']
815        prmlocal = IPrincipalRoleManager(department)
816        prmlocal.assignRoleToPrincipal('waeup.local.ClearanceOfficer', 'mrclear')
817        IWorkflowState(self.student).setState('clearance started')
818        # Login as clearance officer
819        self.browser.open(self.login_path)
820        self.browser.getControl(name="form.login").value = 'mrclear'
821        self.browser.getControl(name="form.password").value = 'mrclearsecret'
822        self.browser.getControl("Login").click()
823        self.assertMatches('...You logged in...', self.browser.contents)
824        # CO can see his roles
825        self.browser.getLink("My Roles").click()
826        self.assertMatches(
827            '...<div>Academics Officer (view only)</div>...',
828            self.browser.contents)
829        #self.assertMatches(
830        #    '...<div>Students Officer (view only)</div>...',
831        #    self.browser.contents)
832        # But not his local role ...
833        self.assertFalse('Clearance Officer' in self.browser.contents)
834        # ... because we forgot to notify the department that the local role
835        # has changed
836        notify(LocalRoleSetEvent(
837            department, 'waeup.local.ClearanceOfficer', 'mrclear', granted=True))
838        self.browser.open('http://localhost/app/users/mrclear/my_roles')
839        self.assertTrue('Clearance Officer' in self.browser.contents)
840        self.assertMatches(
841            '...<a href="http://localhost/app/faculties/fac1/dep1">...',
842            self.browser.contents)
843        # CO can view the student ...
844        self.browser.open(self.clearance_student_path)
845        self.assertEqual(self.browser.headers['Status'], '200 Ok')
846        self.assertEqual(self.browser.url, self.clearance_student_path)
847        # ... but not other students
848        other_student = Student()
849        other_student.firstname = u'Dep2'
850        other_student.lastname = u'Student'
851        self.app['students'].addStudent(other_student)
852        other_student_path = (
853            'http://localhost/app/students/%s' % other_student.student_id)
854        self.assertRaises(
855            Unauthorized, self.browser.open, other_student_path)
856        # Only in state clearance requested the CO does see the 'Clear' button
857        self.browser.open(self.clearance_student_path)
858        self.assertFalse('Clear student' in self.browser.contents)
859        IWorkflowInfo(self.student).fireTransition('request_clearance')
860        self.browser.open(self.clearance_student_path)
861        self.assertTrue('Clear student' in self.browser.contents)
862        self.browser.getLink("Clear student").click()
863        self.assertTrue('Student has been cleared' in self.browser.contents)
864        self.assertTrue('cleared' in self.browser.contents)
865        self.browser.getLink("Reject clearance").click()
866        self.assertTrue('Clearance has been annulled' in self.browser.contents)
867        urlmessage = 'Clearance+has+been+annulled.'
868        # CO does now see the contact form
869        self.assertEqual(self.browser.url, self.student_path +
870            '/contactstudent?subject=%s' % urlmessage)
871        self.assertTrue('clearance started' in self.browser.contents)
872        IWorkflowInfo(self.student).fireTransition('request_clearance')
873        self.browser.open(self.clearance_student_path)
874        self.browser.getLink("Reject clearance").click()
875        self.assertTrue('Clearance request has been rejected'
876            in self.browser.contents)
877        self.assertTrue('clearance started' in self.browser.contents)
878        # CO does now also see the contact form and can send a message
879        self.browser.getControl(name="form.subject").value = 'Important subject'
880        self.browser.getControl(name="form.body").value = 'Clearance rejected'
881        self.browser.getControl("Send message now").click()
882        self.assertTrue('Your message has been sent' in self.browser.contents)
883        # The CO can't clear students if not in state
884        # clearance requested
885        self.browser.open(self.student_path + '/clear')
886        self.assertTrue('Student is in wrong state'
887            in self.browser.contents)
888        # The CO can go to his department throug the my_roles page
889        self.browser.open('http://localhost/app/users/mrclear/my_roles')
890        self.browser.getLink("http://localhost/app/faculties/fac1/dep1").click()
891        # and view the list of students
892        self.browser.getLink("Show students").click()
893        self.assertTrue(self.student_id in self.browser.contents)
894
895    def test_handle_courses_by_ca(self):
896        # Create course adviser
897        self.app['users'].addUser('mrsadvise', 'mrsadvisesecret')
898        self.app['users']['mrsadvise'].email = 'mradvise@foo.ng'
899        self.app['users']['mrsadvise'].title = 'Helen Procter'
900        # Assign local CourseAdviser100 role for a certificate
901        cert = self.app['faculties']['fac1']['dep1'].certificates['CERT1']
902        prmlocal = IPrincipalRoleManager(cert)
903        prmlocal.assignRoleToPrincipal('waeup.local.CourseAdviser100', 'mrsadvise')
904        IWorkflowState(self.student).setState('school fee paid')
905        # Login as course adviser
906        self.browser.open(self.login_path)
907        self.browser.getControl(name="form.login").value = 'mrsadvise'
908        self.browser.getControl(name="form.password").value = 'mrsadvisesecret'
909        self.browser.getControl("Login").click()
910        self.assertMatches('...You logged in...', self.browser.contents)
911        # CO can see his roles
912        self.browser.getLink("My Roles").click()
913        self.assertMatches(
914            '...<div>Academics Officer (view only)</div>...',
915            self.browser.contents)
916        # But not his local role ...
917        self.assertFalse('Course Adviser' in self.browser.contents)
918        # ... because we forgot to notify the certificate that the local role
919        # has changed
920        notify(LocalRoleSetEvent(
921            cert, 'waeup.local.CourseAdviser100', 'mrsadvise', granted=True))
922        self.browser.open('http://localhost/app/users/mrsadvise/my_roles')
923        self.assertTrue('Course Adviser 100L' in self.browser.contents)
924        self.assertMatches(
925            '...<a href="http://localhost/app/faculties/fac1/dep1/certificates/CERT1">...',
926            self.browser.contents)
927        # CA can view the student ...
928        self.browser.open(self.student_path)
929        self.assertEqual(self.browser.headers['Status'], '200 Ok')
930        self.assertEqual(self.browser.url, self.student_path)
931        # ... but not other students
932        other_student = Student()
933        other_student.firstname = u'Dep2'
934        other_student.lastname = u'Student'
935        self.app['students'].addStudent(other_student)
936        other_student_path = (
937            'http://localhost/app/students/%s' % other_student.student_id)
938        self.assertRaises(
939            Unauthorized, self.browser.open, other_student_path)
940        # We add study level 110 to the student's studycourse
941        studylevel = StudentStudyLevel()
942        studylevel.level = 110
943        self.student['studycourse'].addStudentStudyLevel(
944            cert,studylevel)
945        L110_student_path = self.studycourse_student_path + '/110'
946        # Only in state courses registered and only if the current level
947        # corresponds with the name of the study level object
948        # the 100L CA does see the 'Validate' button
949        self.browser.open(L110_student_path)
950        self.assertFalse('Validate' in self.browser.contents)
951        IWorkflowInfo(self.student).fireTransition('register_courses')
952        self.browser.open(L110_student_path)
953        self.assertFalse('Validate' in self.browser.contents)
954        self.student['studycourse'].current_level = 110
955        self.browser.open(L110_student_path)
956        self.assertTrue('Validate' in self.browser.contents)
957        # ... but a 100L CA does not see the button on other levels
958        studylevel2 = StudentStudyLevel()
959        studylevel2.level = 200
960        self.student['studycourse'].addStudentStudyLevel(
961            cert,studylevel2)
962        L200_student_path = self.studycourse_student_path + '/200'
963        self.browser.open(L200_student_path)
964        self.assertFalse('Validate' in self.browser.contents)
965        self.browser.open(L110_student_path)
966        self.browser.getLink("Validate courses").click()
967        self.assertTrue('Course list has been validated' in self.browser.contents)
968        self.assertTrue('courses validated' in self.browser.contents)
969        self.browser.getLink("Reject courses").click()
970        self.assertTrue('Course list request has been annulled.'
971            in self.browser.contents)
972        urlmessage = 'Course+list+request+has+been+annulled.'
973        self.assertEqual(self.browser.url, self.student_path +
974            '/contactstudent?subject=%s' % urlmessage)
975        self.assertTrue('school fee paid' in self.browser.contents)
976        IWorkflowInfo(self.student).fireTransition('register_courses')
977        self.browser.open(L110_student_path)
978        self.browser.getLink("Reject courses").click()
979        self.assertTrue('Course list request has been rejected'
980            in self.browser.contents)
981        self.assertTrue('school fee paid' in self.browser.contents)
982        # CA does now see the contact form and can send a message
983        self.browser.getControl(name="form.subject").value = 'Important subject'
984        self.browser.getControl(name="form.body").value = 'Course list rejected'
985        self.browser.getControl("Send message now").click()
986        self.assertTrue('Your message has been sent' in self.browser.contents)
987        # The CA can't validate courses if not in state
988        # courses registered
989        self.browser.open(L110_student_path + '/validate_courses')
990        self.assertTrue('Student is in the wrong state'
991            in self.browser.contents)
992        # The CA can go to his certificate throug the my_roles page
993        self.browser.open('http://localhost/app/users/mrsadvise/my_roles')
994        self.browser.getLink(
995            "http://localhost/app/faculties/fac1/dep1/certificates/CERT1").click()
996        # and view the list of students
997        self.browser.getLink("Show students").click()
998        self.assertTrue(self.student_id in self.browser.contents)
999
1000    def test_student_change_password(self):
1001        # Students can change the password
1002        self.browser.open(self.login_path)
1003        self.browser.getControl(name="form.login").value = self.student_id
1004        self.browser.getControl(name="form.password").value = 'spwd'
1005        self.browser.getControl("Login").click()
1006        self.assertEqual(self.browser.url, self.student_path)
1007        self.assertTrue('You logged in' in self.browser.contents)
1008        # Change password
1009        self.browser.getLink("Change password").click()
1010        self.browser.getControl(name="change_password").value = 'pw'
1011        self.browser.getControl(
1012            name="change_password_repeat").value = 'pw'
1013        self.browser.getControl("Save").click()
1014        self.assertTrue('Password must have at least' in self.browser.contents)
1015        self.browser.getControl(name="change_password").value = 'new_password'
1016        self.browser.getControl(
1017            name="change_password_repeat").value = 'new_passssword'
1018        self.browser.getControl("Save").click()
1019        self.assertTrue('Passwords do not match' in self.browser.contents)
1020        self.browser.getControl(name="change_password").value = 'new_password'
1021        self.browser.getControl(
1022            name="change_password_repeat").value = 'new_password'
1023        self.browser.getControl("Save").click()
1024        self.assertTrue('Password changed' in self.browser.contents)
1025        # We are still logged in. Changing the password hasn't thrown us out.
1026        self.browser.getLink("Base Data").click()
1027        self.assertEqual(self.browser.url, self.student_path)
1028        # We can logout
1029        self.browser.getLink("Logout").click()
1030        self.assertTrue('You have been logged out' in self.browser.contents)
1031        self.assertEqual(self.browser.url, 'http://localhost/app')
1032        # We can login again with the new password
1033        self.browser.getLink("Login").click()
1034        self.browser.open(self.login_path)
1035        self.browser.getControl(name="form.login").value = self.student_id
1036        self.browser.getControl(name="form.password").value = 'new_password'
1037        self.browser.getControl("Login").click()
1038        self.assertEqual(self.browser.url, self.student_path)
1039        self.assertTrue('You logged in' in self.browser.contents)
1040        return
1041
1042    def test_setpassword(self):
1043        # Set password for first-time access
1044        student = Student()
1045        student.reg_number = u'123456'
1046        student.firstname = u'Klaus'
1047        student.lastname = u'Tester'
1048        self.app['students'].addStudent(student)
1049        setpassword_path = 'http://localhost/app/setpassword'
1050        student_path = 'http://localhost/app/students/%s' % student.student_id
1051        self.browser.open(setpassword_path)
1052        self.browser.getControl(name="ac_series").value = self.existing_pwdseries
1053        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
1054        self.browser.getControl(name="reg_number").value = '223456'
1055        self.browser.getControl("Set").click()
1056        self.assertMatches('...No student found...',
1057                           self.browser.contents)
1058        self.browser.getControl(name="reg_number").value = '123456'
1059        self.browser.getControl(name="ac_number").value = '999999'
1060        self.browser.getControl("Set").click()
1061        self.assertMatches('...Access code is invalid...',
1062                           self.browser.contents)
1063        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
1064        self.browser.getControl("Set").click()
1065        self.assertMatches('...Password has been set. Your Student Id is...',
1066                           self.browser.contents)
1067        self.browser.getControl("Set").click()
1068        self.assertMatches(
1069            '...Password has already been set. Your Student Id is...',
1070            self.browser.contents)
1071        existing_pwdpin = self.pwdpins[1]
1072        parts = existing_pwdpin.split('-')[1:]
1073        existing_pwdseries, existing_pwdnumber = parts
1074        self.browser.getControl(name="ac_series").value = existing_pwdseries
1075        self.browser.getControl(name="ac_number").value = existing_pwdnumber
1076        self.browser.getControl(name="reg_number").value = '123456'
1077        self.browser.getControl("Set").click()
1078        self.assertMatches(
1079            '...You are using the wrong Access Code...',
1080            self.browser.contents)
1081        # The student can login with the new credentials
1082        self.browser.open(self.login_path)
1083        self.browser.getControl(name="form.login").value = student.student_id
1084        self.browser.getControl(
1085            name="form.password").value = self.existing_pwdnumber
1086        self.browser.getControl("Login").click()
1087        self.assertEqual(self.browser.url, student_path)
1088        self.assertTrue('You logged in' in self.browser.contents)
1089        return
1090
1091    def test_student_access(self):
1092        # Students can access their own objects
1093        # and can perform actions
1094        IWorkflowInfo(self.student).fireTransition('admit')
1095        self.browser.open(self.login_path)
1096        self.browser.getControl(name="form.login").value = self.student_id
1097        self.browser.getControl(name="form.password").value = 'spwd'
1098        self.browser.getControl("Login").click()
1099        # Student can upload a passport picture
1100        self.browser.open(self.student_path + '/change_portrait')
1101        ctrl = self.browser.getControl(name='passportuploadedit')
1102        file_obj = open(
1103            os.path.join(os.path.dirname(__file__), 'test_image.jpg'),'rb')
1104        file_ctrl = ctrl.mech_control
1105        file_ctrl.add_file(file_obj, filename='my_photo.jpg')
1106        self.browser.getControl(
1107            name='upload_passportuploadedit').click()
1108        self.assertTrue(
1109            '<img align="middle" height="125px" src="passport.jpg" />'
1110            in self.browser.contents)
1111        # Student can view the clearance data
1112        self.browser.getLink("Clearance Data").click()
1113        # Student can't open clearance edit form before starting clearance
1114        self.browser.open(self.student_path + '/cedit')
1115        self.assertMatches('...The requested form is locked...',
1116                           self.browser.contents)
1117        self.browser.getLink("Clearance Data").click()
1118        self.browser.getLink("Start clearance").click()
1119        self.student.email = None
1120        # Uups, we forgot to fill the email fields
1121        self.browser.getControl("Start clearance").click()
1122        self.assertMatches('...Not all required fields filled...',
1123                           self.browser.contents)
1124        self.student.email = 'aa@aa.ng'
1125        self.browser.open(self.student_path + '/start_clearance')
1126        self.browser.getControl(name="ac_series").value = '3'
1127        self.browser.getControl(name="ac_number").value = '4444444'
1128        self.browser.getControl("Start clearance now").click()
1129        self.assertMatches('...Activation code is invalid...',
1130                           self.browser.contents)
1131        self.browser.getControl(name="ac_series").value = self.existing_clrseries
1132        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
1133        # Owner is Hans Wurst, AC can't be invalidated
1134        self.browser.getControl("Start clearance now").click()
1135        self.assertMatches('...You are not the owner of this access code...',
1136                           self.browser.contents)
1137        # Set the correct owner
1138        self.existing_clrac.owner = self.student_id
1139        self.browser.getControl("Start clearance now").click()
1140        self.assertMatches('...Clearance process has been started...',
1141                           self.browser.contents)
1142        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
1143        self.browser.getControl("Save", index=0).click()
1144        # Student can view the clearance data
1145        self.browser.getLink("Clearance Data").click()
1146        # and go back to the edit form
1147        self.browser.getLink("Edit").click()
1148        self.browser.getControl("Save and request clearance").click()
1149       
1150        self.browser.getControl(name="ac_series").value = self.existing_clrseries
1151        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
1152        self.browser.getControl("Request clearance now").click()
1153        self.assertMatches('...Clearance has been requested...',
1154                           self.browser.contents)
1155        # Student can't reopen clearance form after requesting clearance
1156        self.browser.open(self.student_path + '/cedit')
1157        self.assertMatches('...The requested form is locked...',
1158                           self.browser.contents)
1159        # Student can't add study level if not in state 'school fee paid'
1160        self.browser.open(self.student_path + '/studycourse/add')
1161        self.assertMatches('...The requested form is locked...',
1162                           self.browser.contents)
1163        # ... and must be transferred first
1164        IWorkflowInfo(self.student).fireTransition('clear')
1165        IWorkflowInfo(self.student).fireTransition('pay_first_school_fee')
1166        # Now students can add the current study level
1167        self.browser.getLink("Study Course").click()
1168        self.browser.getLink("Add course list").click()
1169        self.assertMatches('...Add current level 100 (Year 1)...',
1170                           self.browser.contents)
1171        self.browser.getControl("Create course list now").click()
1172        self.browser.getLink("100").click()
1173        self.browser.getLink("Add and remove courses").click()
1174        self.browser.getControl("Add course ticket").click()
1175        self.browser.getControl(name="form.course").value = ['COURSE1']
1176        self.browser.getControl("Add course ticket").click()
1177        self.assertMatches('...The ticket exists...',
1178                           self.browser.contents)
1179        self.student['studycourse'].current_level = 200
1180        self.browser.getLink("Study Course").click()
1181        self.browser.getLink("Add course list").click()
1182        self.assertMatches('...Add current level 200 (Year 2)...',
1183                           self.browser.contents)
1184        self.browser.getControl("Create course list now").click()
1185        self.browser.getLink("200").click()
1186        self.browser.getLink("Add and remove courses").click()
1187        self.browser.getControl("Add course ticket").click()
1188        self.browser.getControl(name="form.course").value = ['COURSE1']
1189        self.browser.getControl("Add course ticket").click()
1190        self.assertMatches('...The ticket exists...',
1191                           self.browser.contents)
1192        # Indeed the ticket exists as carry-over course from level 100
1193        # since its score was 0
1194        self.assertTrue(
1195            self.student['studycourse']['200']['COURSE1'].carry_over is True)
1196        # Students can open the pdf course registration slip
1197        self.browser.open(self.student_path + '/studycourse/200')
1198        self.browser.getLink("Download course registration slip").click()
1199        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1200        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1201        # Students can remove course tickets
1202        self.browser.open(self.student_path + '/studycourse/200/edit')
1203        self.browser.getControl("Remove selected", index=0).click()
1204        self.assertTrue('No ticket selected' in self.browser.contents)
1205        # No ticket can be selected since the carry-over course is a core course
1206        self.assertRaises(
1207            LookupError, self.browser.getControl, name='val_id')
1208        self.student['studycourse']['200']['COURSE1'].mandatory = False
1209        self.browser.open(self.student_path + '/studycourse/200/edit')
1210        # Now the student can remove the ticket
1211        ctrl = self.browser.getControl(name='val_id')
1212        ctrl.getControl(value='COURSE1').selected = True
1213        self.browser.getControl("Remove selected", index=0).click()
1214        self.assertTrue('Successfully removed' in self.browser.contents)
1215        self.browser.getControl("Register course list").click()
1216        self.assertTrue('Course list has been registered' in self.browser.contents)
1217        self.assertEqual(self.student.state, 'courses registered')
1218        return
1219
1220    def test_manage_payments(self):
1221        # Managers can add online school fee payment tickets
1222        # if certain requirements are met
1223        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1224        self.browser.open(self.payments_student_path)
1225        self.browser.getControl("Add online payment ticket").click()
1226        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1227        self.browser.getControl("Create ticket").click()
1228        self.assertMatches('...ticket created...',
1229                           self.browser.contents)
1230        ctrl = self.browser.getControl(name='val_id')
1231        value = ctrl.options[0]
1232        self.browser.getLink(value).click()
1233        self.assertMatches('...Amount Authorized...',
1234                           self.browser.contents)
1235        payment_url = self.browser.url
1236
1237        # The pdf payment receipt can't yet be opened
1238        self.browser.open(payment_url + '/payment_receipt.pdf')
1239        self.assertMatches('...Ticket not yet paid...',
1240                           self.browser.contents)
1241
1242        # The same payment (with same p_item, p_session and p_category)
1243        # can be initialized a second time if the former ticket is not yet paid.
1244        self.browser.open(self.payments_student_path)
1245        self.browser.getControl("Add online payment ticket").click()
1246        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1247        self.browser.getControl("Create ticket").click()
1248        self.assertMatches('...Payment ticket created...',
1249                           self.browser.contents)
1250
1251        # Managers can open the callback view which simulates a valid callback
1252        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
1253        self.browser.open(payment_url)
1254        self.browser.getLink("Request callback").click()
1255        self.assertMatches('...Valid callback received...',
1256                          self.browser.contents)
1257
1258        # Callback can't be applied twice
1259        self.browser.open(payment_url + '/callback')
1260        self.assertMatches('...This ticket has already been paid...',
1261                          self.browser.contents)
1262
1263        # Now the first ticket is paid and no more ticket of same type
1264        # (with same p_item, p_session and p_category) can be added
1265        self.browser.open(self.payments_student_path)
1266        self.browser.getControl("Add online payment ticket").click()
1267        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1268        self.browser.getControl("Create ticket").click()
1269        self.assertMatches(
1270            '...This type of payment has already been made...',
1271            self.browser.contents)
1272
1273        # Managers can open the pdf payment receipt
1274        self.browser.open(payment_url)
1275        self.browser.getLink("Download payment receipt").click()
1276        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1277        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1278
1279        # Managers can remove online school fee payment tickets
1280        self.browser.open(self.payments_student_path)
1281        self.browser.getControl("Remove selected").click()
1282        self.assertMatches('...No payment selected...', self.browser.contents)
1283        ctrl = self.browser.getControl(name='val_id')
1284        value = ctrl.options[0]
1285        ctrl.getControl(value=value).selected = True
1286        self.browser.getControl("Remove selected", index=0).click()
1287        self.assertTrue('Successfully removed' in self.browser.contents)
1288
1289        # Managers can add online clearance payment tickets
1290        self.browser.open(self.payments_student_path + '/addop')
1291        self.browser.getControl(name="form.p_category").value = ['clearance']
1292        self.browser.getControl("Create ticket").click()
1293        self.assertMatches('...ticket created...',
1294                           self.browser.contents)
1295
1296        # Managers can open the callback view which simulates a valid callback
1297        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
1298        ctrl = self.browser.getControl(name='val_id')
1299        value = ctrl.options[1] # The clearance payment is the second in the table
1300        self.browser.getLink(value).click()
1301        self.browser.open(self.browser.url + '/callback')
1302        self.assertMatches('...Valid callback received...',
1303                          self.browser.contents)
1304        expected = '''...
1305        <td>
1306          Paid
1307        </td>...'''
1308        self.assertMatches(expected,self.browser.contents)
1309        # The new CLR-0 pin has been created
1310        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
1311        pin = self.app['accesscodes']['CLR-0'].keys()[0]
1312        ac = self.app['accesscodes']['CLR-0'][pin]
1313        ac.owner = self.student_id
1314        return
1315
1316    def test_student_payments(self):
1317        # Login
1318        self.browser.open(self.login_path)
1319        self.browser.getControl(name="form.login").value = self.student_id
1320        self.browser.getControl(name="form.password").value = 'spwd'
1321        self.browser.getControl("Login").click()
1322
1323        # Students can add online clearance payment tickets
1324        self.browser.open(self.payments_student_path + '/addop')
1325        self.browser.getControl(name="form.p_category").value = ['clearance']
1326        self.browser.getControl("Create ticket").click()
1327        self.assertMatches('...ticket created...',
1328                           self.browser.contents)
1329
1330        # Students can open the callback view which simulates a valid callback
1331        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
1332        ctrl = self.browser.getControl(name='val_id')
1333        value = ctrl.options[0]
1334        self.browser.getLink(value).click()
1335        payment_url = self.browser.url
1336        self.browser.open(payment_url + '/callback')
1337        self.assertMatches('...Valid callback received...',
1338                          self.browser.contents)
1339        expected = '''...
1340        <td>
1341          Paid
1342        </td>...'''
1343        self.assertMatches(expected,self.browser.contents)
1344        # The new CLR-0 pin has been created
1345        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
1346        pin = self.app['accesscodes']['CLR-0'].keys()[0]
1347        ac = self.app['accesscodes']['CLR-0'][pin]
1348        ac.owner = self.student_id
1349
1350        # Students can open the pdf payment receipt
1351        self.browser.open(payment_url + '/payment_receipt.pdf')
1352        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1353        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1354
1355        # The new CLR-0 pin can be used for starting clearance
1356        # but they have to upload a passport picture first
1357        # which is only possible in state admitted
1358        self.browser.open(self.student_path + '/change_portrait')
1359        self.assertMatches('...form is locked...',
1360                          self.browser.contents)
1361        IWorkflowInfo(self.student).fireTransition('admit')
1362        self.browser.open(self.student_path + '/change_portrait')
1363        pseudo_image = StringIO('I pretend to be a graphics file')
1364        ctrl = self.browser.getControl(name='passportuploadedit')
1365        file_ctrl = ctrl.mech_control
1366        file_ctrl.add_file(pseudo_image, filename='my_photo.jpg')
1367        self.browser.getControl(
1368            name='upload_passportuploadedit').click()
1369        self.browser.open(self.student_path + '/start_clearance')
1370        parts = pin.split('-')[1:]
1371        clrseries, clrnumber = parts
1372        self.browser.getControl(name="ac_series").value = clrseries
1373        self.browser.getControl(name="ac_number").value = clrnumber
1374        self.browser.getControl("Start clearance now").click()
1375        self.assertMatches('...Clearance process has been started...',
1376                           self.browser.contents)
1377
1378        # Students can add online school fee payment tickets
1379        self.browser.open(self.payments_student_path)
1380        self.browser.getControl("Add online payment ticket").click()
1381        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1382        self.browser.getControl("Create ticket").click()
1383        self.assertMatches('...ticket created...',
1384                           self.browser.contents)
1385        ctrl = self.browser.getControl(name='val_id')
1386        value = ctrl.options[0]
1387        self.browser.getLink(value).click()
1388        self.assertMatches('...Amount Authorized...',
1389                           self.browser.contents)
1390
1391        # Students can open the callback view which simulates a valid callback
1392        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
1393        self.browser.open(self.browser.url + '/callback')
1394        self.assertMatches('...Valid callback received...',
1395                          self.browser.contents)
1396
1397        # Students can remove only online payment tickets which have
1398        # not received a valid callback
1399        self.browser.open(self.payments_student_path)
1400        self.assertRaises(
1401            LookupError, self.browser.getControl, name='val_id')
1402        self.browser.open(self.payments_student_path + '/addop')
1403        self.browser.getControl(name="form.p_category").value = ['gown']
1404        self.browser.getControl("Create ticket").click()
1405        self.browser.open(self.payments_student_path)
1406        ctrl = self.browser.getControl(name='val_id')
1407        value = ctrl.options[0]
1408        ctrl.getControl(value=value).selected = True
1409        self.browser.getControl("Remove selected", index=0).click()
1410        self.assertTrue('Successfully removed' in self.browser.contents)
1411
1412        # The new SFE-0 pin can be used for starting course registration
1413        IWorkflowInfo(self.student).fireTransition('request_clearance')
1414        IWorkflowInfo(self.student).fireTransition('clear')
1415        self.browser.open(self.studycourse_student_path)
1416        self.browser.getLink('Start course registration').click()
1417        pin = self.app['accesscodes']['SFE-0'].keys()[0]
1418        parts = pin.split('-')[1:]
1419        sfeseries, sfenumber = parts
1420        self.browser.getControl(name="ac_series").value = sfeseries
1421        self.browser.getControl(name="ac_number").value = sfenumber
1422        self.browser.getControl("Start course registration now").click()
1423        self.assertMatches('...Course registration has been started...',
1424                           self.browser.contents)
1425        self.assertTrue(self.student.state == 'school fee paid')
1426        return
1427
1428    def test_manage_accommodation(self):
1429        # Managers can add online booking fee payment tickets and open the
1430        # callback view (see test_manage_payments)
1431        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1432        self.browser.open(self.payments_student_path)
1433        self.browser.getControl("Add online payment ticket").click()
1434        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1435        # If student is not in accommodation session, payment cannot be processed
1436        self.app['configuration'].accommodation_session = 2011
1437        self.browser.getControl("Create ticket").click()
1438        self.assertMatches('...Your current session does not match...',
1439                           self.browser.contents)
1440        self.app['configuration'].accommodation_session = 2004
1441        self.browser.getControl("Add online payment ticket").click()
1442        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1443        self.browser.getControl("Create ticket").click()
1444        ctrl = self.browser.getControl(name='val_id')
1445        value = ctrl.options[0]
1446        self.browser.getLink(value).click()
1447        self.browser.open(self.browser.url + '/callback')
1448        # The new HOS-0 pin has been created
1449        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
1450        pin = self.app['accesscodes']['HOS-0'].keys()[0]
1451        ac = self.app['accesscodes']['HOS-0'][pin]
1452        ac.owner = self.student_id
1453        parts = pin.split('-')[1:]
1454        sfeseries, sfenumber = parts
1455        # Managers can use HOS code and book a bed space with it
1456        self.browser.open(self.acco_student_path)
1457        self.browser.getLink("Book accommodation").click()
1458        self.assertMatches('...You are in the wrong...',
1459                           self.browser.contents)
1460        IWorkflowInfo(self.student).fireTransition('admit')
1461        # An existing HOS code can only be used if students
1462        # are in accommodation session
1463        self.student['studycourse'].current_session = 2003
1464        self.browser.getLink("Book accommodation").click()
1465        self.assertMatches('...Your current session does not match...',
1466                           self.browser.contents)
1467        self.student['studycourse'].current_session = 2004
1468        # All requirements are met and ticket can be created
1469        self.browser.getLink("Book accommodation").click()
1470        self.assertMatches('...Activation Code:...',
1471                           self.browser.contents)
1472        self.browser.getControl(name="ac_series").value = sfeseries
1473        self.browser.getControl(name="ac_number").value = sfenumber
1474        self.browser.getControl("Create bed ticket").click()
1475        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1476                           self.browser.contents)
1477        # Bed has been allocated
1478        bed1 = self.app['hostels']['hall-1']['hall-1_A_101_A']
1479        self.assertTrue(bed1.owner == self.student_id)
1480        # BedTicketAddPage is now blocked
1481        self.browser.getLink("Book accommodation").click()
1482        self.assertMatches('...You already booked a bed space...',
1483            self.browser.contents)
1484        # The bed ticket displays the data correctly
1485        self.browser.open(self.acco_student_path + '/2004')
1486        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1487                           self.browser.contents)
1488        self.assertMatches('...2004/2005...', self.browser.contents)
1489        self.assertMatches('...regular_male_fr...', self.browser.contents)
1490        self.assertMatches('...%s...' % pin, self.browser.contents)
1491        # Managers can relocate students if the student's bed_type has changed
1492        self.browser.getLink("Relocate student").click()
1493        self.assertMatches(
1494            "...Student can't be relocated...", self.browser.contents)
1495        self.student.sex = u'f'
1496        self.browser.getLink("Relocate student").click()
1497        self.assertMatches(
1498            "...Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
1499        self.assertTrue(bed1.owner == NOT_OCCUPIED)
1500        bed2 = self.app['hostels']['hall-1']['hall-1_A_101_B']
1501        self.assertTrue(bed2.owner == self.student_id)
1502        self.assertTrue(self.student['accommodation'][
1503            '2004'].bed_type == u'regular_female_fr')
1504        # The payment object still shows the original payment item
1505        payment_id = self.student['payments'].keys()[0]
1506        payment = self.student['payments'][payment_id]
1507        self.assertTrue(payment.p_item == u'regular_male_fr')
1508        # Managers can relocate students if the bed's bed_type has changed
1509        bed1.bed_type = u'regular_female_fr'
1510        bed2.bed_type = u'regular_male_fr'
1511        notify(grok.ObjectModifiedEvent(bed1))
1512        notify(grok.ObjectModifiedEvent(bed2))
1513        self.browser.getLink("Relocate student").click()
1514        self.assertMatches(
1515            "...Student relocated...", self.browser.contents)
1516        self.assertMatches(
1517            "... Hall 1, Block A, Room 101, Bed A...", self.browser.contents)
1518        self.assertMatches(bed1.owner, self.student_id)
1519        self.assertMatches(bed2.owner, NOT_OCCUPIED)
1520        # Managers can't relocate students if bed is reserved
1521        self.student.sex = u'm'
1522        bed1.bed_type = u'regular_female_reserved'
1523        notify(grok.ObjectModifiedEvent(bed1))
1524        self.browser.getLink("Relocate student").click()
1525        self.assertMatches(
1526            "...Students in reserved beds can't be relocated...",
1527            self.browser.contents)
1528        # Managers can relocate students if booking has been cancelled but
1529        # other bed space has been manually allocated after cancellation
1530        old_owner = bed1.releaseBed()
1531        self.assertMatches(old_owner, self.student_id)
1532        bed2.owner = self.student_id
1533        self.browser.open(self.acco_student_path + '/2004')
1534        self.assertMatches(
1535            "...booking cancelled...", self.browser.contents)
1536        self.browser.getLink("Relocate student").click()
1537        # We didn't informed the catalog therefore the new owner is not found
1538        self.assertMatches(
1539            "...There is no free bed in your category regular_male_fr...",
1540            self.browser.contents)
1541        # Now we fire the event properly
1542        notify(grok.ObjectModifiedEvent(bed2))
1543        self.browser.getLink("Relocate student").click()
1544        self.assertMatches(
1545            "...Student relocated...", self.browser.contents)
1546        self.assertMatches(
1547            "... Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
1548          # Managers can delete bed tickets
1549        self.browser.open(self.acco_student_path)
1550        ctrl = self.browser.getControl(name='val_id')
1551        value = ctrl.options[0]
1552        ctrl.getControl(value=value).selected = True
1553        self.browser.getControl("Remove selected", index=0).click()
1554        self.assertMatches('...Successfully removed...', self.browser.contents)
1555        # The bed has been properly released by the event handler
1556        self.assertMatches(bed1.owner, NOT_OCCUPIED)
1557        self.assertMatches(bed2.owner, NOT_OCCUPIED)
1558        return
1559
1560    def test_student_accommodation(self):
1561        # Login
1562        self.browser.open(self.login_path)
1563        self.browser.getControl(name="form.login").value = self.student_id
1564        self.browser.getControl(name="form.password").value = 'spwd'
1565        self.browser.getControl("Login").click()
1566
1567        # Students can add online booking fee payment tickets and open the
1568        # callback view (see test_manage_payments)
1569        self.browser.getLink("Payments").click()
1570        self.browser.getControl("Add online payment ticket").click()
1571        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1572        self.browser.getControl("Create ticket").click()
1573        ctrl = self.browser.getControl(name='val_id')
1574        value = ctrl.options[0]
1575        self.browser.getLink(value).click()
1576        self.browser.open(self.browser.url + '/callback')
1577        # The new HOS-0 pin has been created
1578        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
1579        pin = self.app['accesscodes']['HOS-0'].keys()[0]
1580        ac = self.app['accesscodes']['HOS-0'][pin]
1581        ac.owner = u'Anybody'
1582        parts = pin.split('-')[1:]
1583        sfeseries, sfenumber = parts
1584
1585        # Students can use HOS code and book a bed space with it
1586        self.browser.open(self.acco_student_path)
1587        self.browser.getLink("Book accommodation").click()
1588        self.assertMatches('...You are in the wrong...',
1589                           self.browser.contents)
1590        IWorkflowInfo(self.student).fireTransition('admit')
1591        self.browser.getLink("Book accommodation").click()
1592        self.assertMatches('...Activation Code:...',
1593                           self.browser.contents)
1594        self.browser.getControl(name="ac_series").value = u'nonsense'
1595        self.browser.getControl(name="ac_number").value = sfenumber
1596        self.browser.getControl("Create bed ticket").click()
1597        self.assertMatches('...Activation code is invalid...',
1598                           self.browser.contents)
1599        self.browser.getControl(name="ac_series").value = sfeseries
1600        self.browser.getControl(name="ac_number").value = sfenumber
1601        self.browser.getControl("Create bed ticket").click()
1602        self.assertMatches('...You are not the owner of this access code...',
1603                           self.browser.contents)
1604        ac.owner = self.student_id
1605        self.browser.getControl(name="ac_series").value = sfeseries
1606        self.browser.getControl(name="ac_number").value = sfenumber
1607        self.browser.getControl("Create bed ticket").click()
1608        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1609                           self.browser.contents)
1610
1611        # Bed has been allocated
1612        bed = self.app['hostels']['hall-1']['hall-1_A_101_A']
1613        self.assertTrue(bed.owner == self.student_id)
1614
1615        # BedTicketAddPage is now blocked
1616        self.browser.getLink("Book accommodation").click()
1617        self.assertMatches('...You already booked a bed space...',
1618            self.browser.contents)
1619
1620        # The bed ticket displays the data correctly
1621        self.browser.open(self.acco_student_path + '/2004')
1622        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1623                           self.browser.contents)
1624        self.assertMatches('...2004/2005...', self.browser.contents)
1625        self.assertMatches('...regular_male_fr...', self.browser.contents)
1626        self.assertMatches('...%s...' % pin, self.browser.contents)
1627
1628        # Students can open the pdf slip
1629        self.browser.open(self.browser.url + '/bed_allocation.pdf')
1630        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1631        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1632
1633        # Students can't relocate themselves
1634        self.assertFalse('Relocate' in self.browser.contents)
1635        relocate_path = self.acco_student_path + '/2004/relocate'
1636        self.assertRaises(
1637            Unauthorized, self.browser.open, relocate_path)
1638
1639        # Students can't the Remove button and check boxes
1640        self.browser.open(self.acco_student_path)
1641        self.assertFalse('Remove' in self.browser.contents)
1642        self.assertFalse('val_id' in self.browser.contents)
1643        return
1644
1645    def test_change_password_request(self):
1646        self.browser.open('http://localhost/app/changepw')
1647        self.browser.getControl(name="form.reg_number").value = '123'
1648        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
1649        self.browser.getControl("Get new login credentials").click()
1650        self.assertTrue('An email with' in self.browser.contents)
1651
1652    def test_reindex(self):
1653        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1654        self.browser.open('http://localhost/app/reindex')
1655        self.assertTrue('No catalog name provided' in self.browser.contents)
1656        self.browser.open('http://localhost/app/reindex?ctlg=xyz')
1657        self.assertTrue('xyz_catalog does not exist' in self.browser.contents)
1658        cat = queryUtility(ICatalog, name='students_catalog')
1659        results = cat.searchResults(student_id=(None, None))
1660        self.assertEqual(len(results),1)
1661        cat.clear()
1662        results = cat.searchResults(student_id=(None, None))
1663        self.assertEqual(len(results),0)
1664        self.browser.open('http://localhost/app/reindex?ctlg=students')
1665        self.assertTrue('1 students re-indexed' in self.browser.contents)
1666        results = cat.searchResults(student_id=(None, None))
1667        self.assertEqual(len(results),1)
Note: See TracBrowser for help on using the repository browser.