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

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

Add view buttons and fix test.

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