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

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

Add tests for changes in previous revision and remove additional bug found with these tests.

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