source: main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_browser.py @ 8048

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

Use ChangePasswordRequestPage? also for applicants.

Change password requests require a registration number.

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