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

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

Implement carry-over course collection. Collect carry-over courses in base levels (not in repeating levels).

  • Property svn:keywords set to Id
File size: 85.0 KB
Line 
1## $Id: test_browser.py 7661 2012-02-17 07:43:22Z henrik $
2##
3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18"""
19Test the student-related UI components.
20"""
21import shutil
22import tempfile
23from StringIO import StringIO
24from datetime import datetime
25import os
26import grok
27from zope.event import notify
28from zope.component import createObject, queryUtility
29from zope.component.hooks import setSite, clearSite
30from zope.catalog.interfaces import ICatalog
31from zope.security.interfaces import Unauthorized
32from zope.securitypolicy.interfaces import IPrincipalRoleManager
33from zope.testbrowser.testing import Browser
34from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
35from waeup.sirp.testing import FunctionalLayer, FunctionalTestCase
36from waeup.sirp.app import University
37from waeup.sirp.configuration import SessionConfiguration
38from waeup.sirp.students.student import Student
39from waeup.sirp.students.studylevel import StudentStudyLevel
40from waeup.sirp.university.faculty import Faculty
41from waeup.sirp.university.department import Department
42from waeup.sirp.interfaces import IUserAccount
43from waeup.sirp.authentication import LocalRoleSetEvent
44from waeup.sirp.hostels.hostel import Hostel, Bed, NOT_OCCUPIED
45
46PH_LEN = 2059  # Length of placeholder file
47
48def lookup_submit_value(name, value, browser):
49    """Find a button with a certain value."""
50    for num in range(0, 100):
51        try:
52            button = browser.getControl(name=name, index=num)
53            if button.value.endswith(value):
54                return button
55        except IndexError:
56            break
57    return None
58
59class StudentsFullSetup(FunctionalTestCase):
60    # A test case that only contains a setup and teardown
61    #
62    # Complete setup for students handlings is rather complex and
63    # requires lots of things created before we can start. This is a
64    # setup that does all this, creates a university, creates PINs,
65    # etc.  so that we do not have to bother with that in different
66    # test cases.
67
68    layer = FunctionalLayer
69
70    def setUp(self):
71        super(StudentsFullSetup, self).setUp()
72
73        # Setup a sample site for each test
74        app = University()
75        self.dc_root = tempfile.mkdtemp()
76        app['datacenter'].setStoragePath(self.dc_root)
77
78        # Prepopulate the ZODB...
79        self.getRootFolder()['app'] = app
80        # we add the site immediately after creation to the
81        # ZODB. Catalogs and other local utilities are not setup
82        # before that step.
83        self.app = self.getRootFolder()['app']
84        # Set site here. Some of the following setup code might need
85        # to access grok.getSite() and should get our new app then
86        setSite(app)
87
88        # Add student with subobjects
89        student = Student()
90        student.firstname = u'Anna'
91        student.lastname = u'Tester'
92        student.reg_number = u'123'
93        student.matric_number = u'234'
94        student.sex = u'm'
95        student.email = 'aa@aa.ng'
96        student.phone = u'1234'
97        self.app['students'].addStudent(student)
98        self.student_id = student.student_id
99        self.student = self.app['students'][self.student_id]
100
101        # Set password
102        IUserAccount(
103            self.app['students'][self.student_id]).setPassword('spwd')
104
105        self.login_path = 'http://localhost/app/login'
106        self.container_path = 'http://localhost/app/students'
107        self.manage_container_path = self.container_path + '/@@manage'
108        self.add_student_path = self.container_path + '/addstudent'
109        self.student_path = self.container_path + '/' + self.student_id
110        self.manage_student_path = self.student_path + '/manage_base'
111        self.clearance_student_path = self.student_path + '/view_clearance'
112        self.personal_student_path = self.student_path + '/view_personal'
113        self.edit_clearance_student_path = self.student_path + '/edit_clearance'
114        self.edit_personal_student_path = self.student_path + '/edit_personal'
115        self.studycourse_student_path = self.student_path + '/studycourse'
116        self.payments_student_path = self.student_path + '/payments'
117        self.acco_student_path = self.student_path + '/accommodation'
118        self.history_student_path = self.student_path + '/history'
119
120        # Create 5 access codes with prefix'PWD'
121        pin_container = self.app['accesscodes']
122        pin_container.createBatch(
123            datetime.now(), 'some_userid', 'PWD', 9.99, 5)
124        pins = pin_container['PWD-1'].values()
125        self.pwdpins = [x.representation for x in pins]
126        self.existing_pwdpin = self.pwdpins[0]
127        parts = self.existing_pwdpin.split('-')[1:]
128        self.existing_pwdseries, self.existing_pwdnumber = parts
129        # Create 5 access codes with prefix 'CLR'
130        pin_container.createBatch(
131            datetime.now(), 'some_userid', 'CLR', 9.99, 5)
132        pins = pin_container['CLR-1'].values()
133        pins[0].owner = u'Hans Wurst'
134        self.existing_clrac = pins[0]
135        self.existing_clrpin = pins[0].representation
136        parts = self.existing_clrpin.split('-')[1:]
137        self.existing_clrseries, self.existing_clrnumber = parts
138        # Create 2 access codes with prefix 'HOS'
139        pin_container.createBatch(
140            datetime.now(), 'some_userid', 'HOS', 9.99, 2)
141        pins = pin_container['HOS-1'].values()
142        self.existing_hosac = pins[0]
143        self.existing_hospin = pins[0].representation
144        parts = self.existing_hospin.split('-')[1:]
145        self.existing_hosseries, self.existing_hosnumber = parts
146
147        # Populate university
148        self.certificate = createObject('waeup.Certificate')
149        self.certificate.code = u'CERT1'
150        self.certificate.application_category = 'basic'
151        self.certificate.study_mode = 'ug_ft'
152        self.certificate.start_level = 100
153        self.certificate.end_level = 500
154        self.app['faculties']['fac1'] = Faculty()
155        self.app['faculties']['fac1']['dep1'] = Department(code='dep1')
156        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
157            self.certificate)
158        self.course = createObject('waeup.Course')
159        self.course.code = 'COURSE1'
160        self.course.semester = 1
161        self.course.credits = 10
162        self.course.passmark = 40
163        self.app['faculties']['fac1']['dep1'].courses.addCourse(
164            self.course)
165        self.app['faculties']['fac1']['dep1'].certificates['CERT1'].addCourseRef(
166            self.course, level=100)
167
168        # Configure university
169        self.app['configuration'].accommodation_states = ['admitted']
170        self.app['configuration'].accommodation_session = 2004
171        configuration = SessionConfiguration()
172        # These attributes must also exist in the customization packages.
173        configuration.academic_session = 2004
174        configuration.fee_1 = 20000
175        configuration.boocking_fee = 500
176        self.app['configuration'].addSessionConfiguration(configuration)
177
178        # Create a hostel with two beds
179        hostel = Hostel()
180        hostel.hostel_id = u'hall-1'
181        hostel.hostel_name = u'Hall 1'
182        self.app['hostels'].addHostel(hostel)
183        bed = Bed()
184        bed.bed_id = u'hall-1_A_101_A'
185        bed.bed_number = 1
186        bed.owner = NOT_OCCUPIED
187        bed.bed_type = u'regular_male_fr'
188        self.app['hostels'][hostel.hostel_id].addBed(bed)
189        bed = Bed()
190        bed.bed_id = u'hall-1_A_101_B'
191        bed.bed_number = 2
192        bed.owner = NOT_OCCUPIED
193        bed.bed_type = u'regular_female_fr'
194        self.app['hostels'][hostel.hostel_id].addBed(bed)
195
196        # Set study course attributes of test student
197        self.student['studycourse'].certificate = self.certificate
198        self.student['studycourse'].current_session = 2004
199        self.student['studycourse'].entry_session = 2004
200        self.student['studycourse'].current_verdict = 'A'
201        self.student['studycourse'].current_level = 100
202        # Update the catalog
203        notify(grok.ObjectModifiedEvent(self.student))
204
205        # Put the prepopulated site into test ZODB and prepare test
206        # browser
207        self.browser = Browser()
208        self.browser.handleErrors = False
209
210    def tearDown(self):
211        super(StudentsFullSetup, self).tearDown()
212        clearSite()
213        shutil.rmtree(self.dc_root)
214
215
216
217class StudentsContainerUITests(StudentsFullSetup):
218    # Tests for StudentsContainer class views and pages
219
220    layer = FunctionalLayer
221
222    def test_anonymous_access(self):
223        # Anonymous users can't access students containers
224        self.assertRaises(
225            Unauthorized, self.browser.open, self.container_path)
226        self.assertRaises(
227            Unauthorized, self.browser.open, self.manage_container_path)
228        return
229
230    def test_manage_access(self):
231        # Managers can access the view page of students
232        # containers and can perform actions
233        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
234        self.browser.open(self.container_path)
235        self.assertEqual(self.browser.headers['Status'], '200 Ok')
236        self.assertEqual(self.browser.url, self.container_path)
237        self.browser.getLink("Manage student section").click()
238        self.assertEqual(self.browser.headers['Status'], '200 Ok')
239        self.assertEqual(self.browser.url, self.manage_container_path)
240        return
241
242    def test_add_search_delete_students(self):
243        # Managers can add search and remove students
244        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
245        self.browser.open(self.manage_container_path)
246        self.browser.getLink("Add student").click()
247        self.assertEqual(self.browser.headers['Status'], '200 Ok')
248        self.assertEqual(self.browser.url, self.add_student_path)
249        self.browser.getControl(name="form.firstname").value = 'Bob'
250        self.browser.getControl(name="form.lastname").value = 'Tester'
251        self.browser.getControl(name="form.reg_number").value = '1234'
252        self.browser.getControl("Create student record").click()
253        self.assertTrue('Student record created' in self.browser.contents)
254
255        # Registration and matric numbers must be unique
256        self.browser.getLink("Manage").click()
257        self.browser.getControl(name="form.reg_number").value = '123'
258        self.browser.getControl("Save").click()
259        self.assertMatches('...Registration number exists...',
260                           self.browser.contents)
261        self.browser.getControl(name="form.reg_number").value = '789'
262        self.browser.getControl(name="form.matric_number").value = '234'
263        self.browser.getControl("Save").click()
264        self.assertMatches('...Matriculation number exists...',
265                           self.browser.contents)
266
267        # We can find a student with a certain student_id
268        self.browser.open(self.container_path)
269        self.browser.getControl("Search").click()
270        self.assertTrue('Empty search string' in self.browser.contents)
271        self.browser.getControl(name="searchtype").value = ['student_id']
272        self.browser.getControl(name="searchterm").value = self.student_id
273        self.browser.getControl("Search").click()
274        self.assertTrue('Anna Tester' in self.browser.contents)
275
276        # We can find a student in a certain session
277        self.browser.open(self.container_path)
278        self.browser.getControl(name="searchtype").value = ['current_session']
279        self.browser.getControl(name="searchterm").value = '2004'
280        self.browser.getControl("Search").click()
281        self.assertTrue('Anna Tester' in self.browser.contents)
282
283        # We can find a student in a certain study_mode
284        self.browser.open(self.container_path)
285        self.browser.getControl(name="searchtype").value = ['current_mode']
286        self.browser.getControl(name="searchterm").value = 'ug_ft'
287        self.browser.getControl("Search").click()
288        self.assertTrue('Anna Tester' in self.browser.contents)
289
290        # We can find a student in a certain department
291        self.browser.open(self.container_path)
292        self.browser.getControl(name="searchtype").value = ['depcode']
293        self.browser.getControl(name="searchterm").value = 'dep1'
294        self.browser.getControl("Search").click()
295        self.assertTrue('Anna Tester' in self.browser.contents)
296
297        # We can find a student by searching for all kind of name parts
298        self.browser.open(self.manage_container_path)
299        self.browser.getControl("Search").click()
300        self.assertTrue('Empty search string' in self.browser.contents)
301        self.browser.getControl(name="searchtype").value = ['fullname']
302        self.browser.getControl(name="searchterm").value = 'Anna Tester'
303        self.browser.getControl("Search").click()
304        self.assertTrue('Anna Tester' in self.browser.contents)
305        self.browser.open(self.manage_container_path)
306        self.browser.getControl(name="searchtype").value = ['fullname']
307        self.browser.getControl(name="searchterm").value = 'Anna'
308        self.browser.getControl("Search").click()
309        self.assertTrue('Anna Tester' in self.browser.contents)
310        self.browser.open(self.manage_container_path)
311        self.browser.getControl(name="searchtype").value = ['fullname']
312        self.browser.getControl(name="searchterm").value = 'Tester'
313        self.browser.getControl("Search").click()
314        self.assertTrue('Anna Tester' in self.browser.contents)
315        self.browser.open(self.manage_container_path)
316        self.browser.getControl(name="searchtype").value = ['fullname']
317        self.browser.getControl(name="searchterm").value = 'An'
318        self.browser.getControl("Search").click()
319        self.assertFalse('Anna Tester' in self.browser.contents)
320        self.browser.open(self.manage_container_path)
321        self.browser.getControl(name="searchtype").value = ['fullname']
322        self.browser.getControl(name="searchterm").value = 'An*'
323        self.browser.getControl("Search").click()
324        self.assertTrue('Anna Tester' in self.browser.contents)
325        self.browser.open(self.manage_container_path)
326        self.browser.getControl(name="searchtype").value = ['fullname']
327        self.browser.getControl(name="searchterm").value = 'tester'
328        self.browser.getControl("Search").click()
329        self.assertTrue('Anna Tester' in self.browser.contents)
330        self.browser.open(self.manage_container_path)
331        self.browser.getControl(name="searchtype").value = ['fullname']
332        self.browser.getControl(name="searchterm").value = 'Tester Ana'
333        self.browser.getControl("Search").click()
334        self.assertFalse('Anna Tester' in self.browser.contents)
335        self.browser.open(self.manage_container_path)
336        self.browser.getControl(name="searchtype").value = ['fullname']
337        self.browser.getControl(name="searchterm").value = 'Tester Anna'
338        self.browser.getControl("Search").click()
339        self.assertTrue('Anna Tester' in self.browser.contents)
340        # The old searchterm will be used again
341        self.browser.getControl("Search").click()
342        self.assertTrue('Anna Tester' in self.browser.contents)
343
344        ctrl = self.browser.getControl(name='entries')
345        ctrl.getControl(value=self.student_id).selected = True
346        self.browser.getControl("Remove selected", index=0).click()
347        self.assertTrue('Successfully removed' in self.browser.contents)
348        self.browser.getControl(name="searchtype").value = ['student_id']
349        self.browser.getControl(name="searchterm").value = self.student_id
350        self.browser.getControl("Search").click()
351        self.assertTrue('No student found' in self.browser.contents)
352
353        self.browser.open(self.container_path)
354        self.browser.getControl(name="searchtype").value = ['student_id']
355        self.browser.getControl(name="searchterm").value = self.student_id
356        self.browser.getControl("Search").click()
357        self.assertTrue('No student found' in self.browser.contents)
358        return
359
360class StudentUITests(StudentsFullSetup):
361    # Tests for Student class views and pages
362
363    layer = FunctionalLayer
364
365    def test_basic_auth(self):
366        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
367        self.browser.open('http://localhost/app')
368        self.browser.getLink("Logout").click()
369        self.assertTrue('You have been logged out' in self.browser.contents)
370        # But we are still logged in since we've used basic authentication here.
371        # Wikipedia says: Existing browsers retain authentication information
372        # until the tab or browser is closed or the user clears the history.
373        # HTTP does not provide a method for a server to direct clients to
374        # discard these cached credentials. This means that there is no
375        # effective way for a server to "log out" the user without closing
376        # the browser. This is a significant defect that requires browser
377        # manufacturers to support a "logout" user interface element ...
378        self.assertTrue('Manager' in self.browser.contents)
379
380    def test_manage_access(self):
381        # Managers can access the pages of students
382        # and can perform actions
383        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
384        self.browser.open(self.student_path)
385        self.assertEqual(self.browser.headers['Status'], '200 Ok')
386        self.assertEqual(self.browser.url, self.student_path)
387        self.browser.getLink("Manage").click()
388        self.assertEqual(self.browser.headers['Status'], '200 Ok')
389        self.assertEqual(self.browser.url, self.manage_student_path)
390        # Managers can edit base data and fire transitions
391        self.browser.getControl(name="transition").value = ['admit']
392        self.browser.getControl(name="form.firstname").value = 'John'
393        self.browser.getControl(name="form.lastname").value = 'Tester'
394        self.browser.getControl(name="form.reg_number").value = '345'
395        self.browser.getControl(name="password").value = 'secret'
396        self.browser.getControl(name="control_password").value = 'secret'
397        self.browser.getControl("Save").click()
398        self.assertMatches('...Form has been saved...',
399                           self.browser.contents)
400        self.browser.open(self.student_path)
401        self.browser.getLink("Clearance Data").click()
402        self.assertEqual(self.browser.headers['Status'], '200 Ok')
403        self.assertEqual(self.browser.url, self.clearance_student_path)
404        self.browser.getLink("Manage").click()
405        self.assertEqual(self.browser.headers['Status'], '200 Ok')
406        self.assertEqual(self.browser.url, self.edit_clearance_student_path)
407        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
408        self.browser.getControl("Save").click()
409        self.assertMatches('...Form has been saved...',
410                           self.browser.contents)
411
412        self.browser.open(self.student_path)
413        self.browser.getLink("Personal Data").click()
414        self.assertEqual(self.browser.headers['Status'], '200 Ok')
415        self.assertEqual(self.browser.url, self.personal_student_path)
416        self.browser.getLink("Edit").click()
417        self.assertEqual(self.browser.headers['Status'], '200 Ok')
418        self.assertEqual(self.browser.url, self.edit_personal_student_path)
419        self.browser.getControl("Save").click()
420        self.assertMatches('...Form has been saved...',
421                           self.browser.contents)
422
423        # Managers can browse all subobjects
424        self.browser.open(self.student_path)
425        self.browser.getLink("Payments").click()
426        self.assertEqual(self.browser.headers['Status'], '200 Ok')
427        self.assertEqual(self.browser.url, self.payments_student_path)
428        self.browser.open(self.student_path)
429        self.browser.getLink("Accommodation").click()
430        self.assertEqual(self.browser.headers['Status'], '200 Ok')
431        self.assertEqual(self.browser.url, self.acco_student_path)
432        self.browser.open(self.student_path)
433        self.browser.getLink("History").click()
434        self.assertEqual(self.browser.headers['Status'], '200 Ok')
435        self.assertEqual(self.browser.url, self.history_student_path)
436        self.assertMatches('...Student admitted by Manager...',
437                           self.browser.contents)
438        # Only the Application Slip does not exist
439        self.assertFalse('Application Slip' in self.browser.contents)
440        return
441
442    def test_manage_contact_student(self):
443        # Managers can contact student
444        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
445        self.browser.open(self.student_path)
446        self.browser.getLink("Send email").click()
447        self.browser.getControl(name="form.subject").value = 'Important subject'
448        self.browser.getControl(name="form.body").value = 'Hello!'
449        self.browser.getControl("Send message now").click()
450        self.assertTrue('Your message has been sent' in self.browser.contents)
451        return
452
453    def test_manage_remove_department(self):
454        # Lazy student is studying CERT1
455        lazystudent = Student()
456        lazystudent.firstname = u'Lazy'
457        lazystudent.lastname = u'Student'
458        self.app['students'].addStudent(lazystudent)
459        student_id = lazystudent.student_id
460        student_path = self.container_path + '/' + student_id
461        lazystudent['studycourse'].certificate = self.certificate
462        notify(grok.ObjectModifiedEvent(lazystudent))
463        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
464        self.browser.open(student_path + '/studycourse')
465        self.assertTrue('CERT1' in self.browser.contents)
466        # After some years the department is removed
467        del self.app['faculties']['fac1']['dep1']
468        # So CERT1 does no longer exist and lazy student's
469        # certificate reference is removed too
470        self.browser.open(student_path + '/studycourse')
471        self.assertEqual(self.browser.headers['Status'], '200 Ok')
472        self.assertEqual(self.browser.url, student_path + '/studycourse')
473        self.assertFalse('CERT1' in self.browser.contents)
474        self.assertMatches('...<div class="widget">Nothing</div>...',
475                           self.browser.contents)
476
477    def test_manage_upload_file(self):
478        # Managers can upload a file via the StudentClearanceManageFormPage
479        # The image is stored even if form has errors
480        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
481        self.browser.open(self.edit_clearance_student_path)
482        # No birth certificate has been uploaded yet
483        # Browsing the link shows a placerholder image
484        self.browser.open('birth_certificate')
485        self.assertEqual(
486            self.browser.headers['content-type'], 'image/jpeg')
487        self.assertEqual(len(self.browser.contents), PH_LEN)
488        # Create a pseudo image file and select it to be uploaded in form
489        # as birth certificate
490        self.browser.open(self.edit_clearance_student_path)
491        pseudo_image = StringIO('I pretend to be a graphics file')
492        ctrl = self.browser.getControl(name='birthcertificateupload')
493        file_ctrl = ctrl.mech_control
494        file_ctrl.add_file(pseudo_image, filename='my_birth_certificate.jpg')
495        # The Save action does not upload files
496        self.browser.getControl("Save").click() # submit form
497        self.assertFalse(
498            '<a target="image" href="birth_certificate">'
499            in self.browser.contents)
500        # ... but the correct upload submit button does
501        pseudo_image = StringIO('I pretend to be a graphics file')
502        ctrl = self.browser.getControl(name='birthcertificateupload')
503        file_ctrl = ctrl.mech_control
504        file_ctrl.add_file(pseudo_image, filename='my_birth_certificate.jpg')
505        self.browser.getControl(
506            name='upload_birthcertificateupload').click()
507        # There is a correct <img> link included
508        self.assertTrue(
509            '<a target="image" href="birth_certificate">'
510            in self.browser.contents)
511
512        # Browsing the link shows a real image
513        self.browser.open('birth_certificate')
514        self.assertEqual(
515            self.browser.headers['content-type'], 'image/jpeg')
516        self.assertEqual(len(self.browser.contents), 31)
517        # Reuploading a file which is bigger than 150k will raise an error
518        self.browser.open(self.edit_clearance_student_path)
519        photo_content = 'A' * 1024 * 151  # A string of 21 KB size
520        pseudo_image = StringIO(photo_content)
521        ctrl = self.browser.getControl(name='birthcertificateupload')
522        file_ctrl = ctrl.mech_control
523        file_ctrl.add_file(pseudo_image, filename='my_birth_certificate.jpg')
524        self.browser.getControl(
525            name='upload_birthcertificateupload').click()
526        self.assertTrue(
527            'Uploaded file is too big' in self.browser.contents)
528        # File names must meet several conditions
529        pseudo_image = StringIO('I pretend to be a graphics file')
530        ctrl = self.browser.getControl(name='birthcertificateupload')
531        file_ctrl = ctrl.mech_control
532        file_ctrl.add_file(pseudo_image, filename='my.photo.jpg')
533        self.browser.getControl(
534            name='upload_birthcertificateupload').click()
535        self.assertTrue('File name contains more than one dot'
536            in self.browser.contents)
537        ctrl = self.browser.getControl(name='birthcertificateupload')
538        file_ctrl = ctrl.mech_control
539        file_ctrl.add_file(pseudo_image, filename='my_birth_certificate')
540        self.browser.getControl(
541            name='upload_birthcertificateupload').click()
542        self.assertTrue('File name has no extension' in self.browser.contents)
543        ctrl = self.browser.getControl(name='birthcertificateupload')
544        file_ctrl = ctrl.mech_control
545        file_ctrl.add_file(pseudo_image, filename='my_birth_certificate.bmp')
546        self.browser.getControl(
547            name='upload_birthcertificateupload').click()
548        self.assertTrue('Only the following extension are allowed'
549            in self.browser.contents)
550        # Managers can delete files
551        self.browser.getControl(name='delete_birthcertificateupload').click()
552        self.assertTrue(
553            'birth_certificate deleted' in self.browser.contents)
554        # Managers can add and delete second file
555        self.browser.open(self.edit_clearance_student_path)
556        pseudo_image = StringIO('I pretend to be a graphics file')
557        ctrl = self.browser.getControl(name='birthcertificateupload')
558        file_ctrl = ctrl.mech_control
559        file_ctrl.add_file(pseudo_image, filename='my_acceptance_letter.jpg')
560        self.browser.getControl(
561            name='upload_acceptanceletterupload').click()
562        self.assertFalse(
563            '<a target="image" href="acceptance_letter">'
564            in self.browser.contents)
565        ctrl = self.browser.getControl(name='acceptanceletterupload')
566        file_ctrl = ctrl.mech_control
567        file_ctrl.add_file(pseudo_image, filename='my_acceptance_letter.jpg')
568        self.browser.getControl(
569            name='upload_acceptanceletterupload').click()
570        self.assertTrue(
571            '<a target="image" href="acceptance_letter">'
572            in self.browser.contents)
573        self.browser.getControl(
574            name='delete_acceptanceletterupload').click()
575        self.assertTrue(
576            'acceptance_letter deleted'
577            in self.browser.contents)
578        # Managers can upload a file via the StudentBaseManageFormPage
579        self.browser.open(self.manage_student_path)
580        pseudo_image = StringIO('I pretend to be a graphics file')
581        ctrl = self.browser.getControl(name='passportuploadmanage')
582        file_ctrl = ctrl.mech_control
583        file_ctrl.add_file(pseudo_image, filename='my_photo.bmp')
584        self.browser.getControl(
585            name='upload_passportuploadmanage').click()
586        self.assertTrue('jpg file extension expected'
587            in self.browser.contents)
588        ctrl = self.browser.getControl(name='passportuploadmanage')
589        file_ctrl = ctrl.mech_control
590        file_ctrl.add_file(pseudo_image, filename='my_photo.jpg')
591        self.browser.getControl(
592            name='upload_passportuploadmanage').click()
593        self.assertTrue(
594            '<img align="middle" height="125px" src="passport.jpg" />'
595            in self.browser.contents)
596        # The clearance slip can't be opened because it requires a proper
597        # passport jpg file.
598        self.browser.open(self.student_path + '/clearance.pdf')
599        self.assertEqual(self.browser.headers['Status'], '200 Ok')
600        self.assertTrue('Error in image file' in self.browser.contents)
601        # We remove the passport file again
602        self.browser.open(self.manage_student_path)
603        self.browser.getControl('Delete').click()
604        self.browser.open(self.student_path + '/clearance.pdf')
605        self.assertEqual(self.browser.headers['Status'], '200 Ok')
606        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
607
608    def test_manage_course_lists(self):
609        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
610        self.browser.open(self.student_path)
611        self.browser.getLink("Study Course").click()
612        self.assertEqual(self.browser.headers['Status'], '200 Ok')
613        self.assertEqual(self.browser.url, self.studycourse_student_path)
614        self.assertTrue('Undergraduate Full Time' in self.browser.contents)
615        self.browser.getLink("Manage").click()
616        self.assertTrue('Manage study course' in self.browser.contents)
617        # Before we can select a level, the certificate must
618        # be selected and saved
619        self.browser.getControl(name="form.certificate").value = ['CERT1']
620        self.browser.getControl(name="form.current_session").value = ['2004']
621        self.browser.getControl(name="form.current_verdict").value = ['A']
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,password
730Aaren,Pieri,1,1990-01-02,100000,aa@aa.ng,1234,mypwd1
731Claus,Finau,2,1990-01-03,100001,aa@aa.ng,1234,mypwd1
732Brit,Berson,3,1990-01-04,100001,aa@aa.ng,1234,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 Importer').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 Importer (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_student_path)
844        self.assertEqual(self.browser.headers['Status'], '200 Ok')
845        self.assertEqual(self.browser.url, self.clearance_student_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_student_path)
857        self.assertFalse('Clear student' in self.browser.contents)
858        IWorkflowInfo(self.student).fireTransition('request_clearance')
859        self.browser.open(self.clearance_student_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_student_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 the 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_student_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_student_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'].core_or_elective = 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_student_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_student_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 + '/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_student_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_student_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_student_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 + '/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_student_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 + '/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_student_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 + '/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_student_path)
1399        self.assertRaises(
1400            LookupError, self.browser.getControl, name='val_id')
1401        self.browser.open(self.payments_student_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_student_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_student_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_student_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 + '/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_student_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_student_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_student_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_student_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 + '/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_student_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_student_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_student_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_student_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/changepw')
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)
Note: See TracBrowser for help on using the repository browser.