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

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

Catch error when sending the contact form but no email address is provided.

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