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

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

Part 1: Add tzinfo to all persistent datetime objects.

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