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

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

Implement a ChangePasswordRequestPage? for all portal users (more tests will follow).

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