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

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

Add methods for approving payments and implement pages for approving payments (work in progress).

  • Property svn:keywords set to Id
File size: 88.8 KB
Line 
1## $Id: test_browser.py 8420 2012-05-11 14:18:47Z 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.browser.open(self.student_path)
475        self.browser.getLink("Send email").click()
476        self.browser.getControl(name="form.subject").value = 'Important subject'
477        self.browser.getControl(name="form.body").value = 'Hello!'
478        self.browser.getControl("Send message now").click()
479        self.assertTrue('Your message has been sent' in self.browser.contents)
480        return
481
482    def test_manage_remove_department(self):
483        # Lazy student is studying CERT1
484        lazystudent = Student()
485        lazystudent.firstname = u'Lazy'
486        lazystudent.lastname = u'Student'
487        self.app['students'].addStudent(lazystudent)
488        student_id = lazystudent.student_id
489        student_path = self.container_path + '/' + student_id
490        lazystudent['studycourse'].certificate = self.certificate
491        notify(grok.ObjectModifiedEvent(lazystudent))
492        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
493        self.browser.open(student_path + '/studycourse')
494        self.assertTrue('CERT1' in self.browser.contents)
495        # After some years the department is removed
496        del self.app['faculties']['fac1']['dep1']
497        # So CERT1 does no longer exist and lazy student's
498        # certificate reference is removed too
499        self.browser.open(student_path + '/studycourse')
500        self.assertEqual(self.browser.headers['Status'], '200 Ok')
501        self.assertEqual(self.browser.url, student_path + '/studycourse')
502        self.assertFalse('CERT1' in self.browser.contents)
503        self.assertMatches('...<div>--</div>...',
504                           self.browser.contents)
505
506    def test_manage_upload_file(self):
507        # Managers can upload a file via the StudentClearanceManageFormPage
508        # The image is stored even if form has errors
509        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
510        self.browser.open(self.manage_clearance_path)
511        # No birth certificate has been uploaded yet
512        # Browsing the link shows a placerholder image
513        self.browser.open('birth_certificate')
514        self.assertEqual(
515            self.browser.headers['content-type'], 'image/jpeg')
516        self.assertEqual(len(self.browser.contents), PH_LEN)
517        # Create a pseudo image file and select it to be uploaded in form
518        # as birth certificate
519        self.browser.open(self.manage_clearance_path)
520        pseudo_image = StringIO('I pretend to be a graphics file')
521        ctrl = self.browser.getControl(name='birthcertificateupload')
522        file_ctrl = ctrl.mech_control
523        file_ctrl.add_file(pseudo_image, filename='my_birth_certificate.jpg')
524        # The Save action does not upload files
525        self.browser.getControl("Save").click() # submit form
526        self.assertFalse(
527            '<a target="image" href="birth_certificate">'
528            in self.browser.contents)
529        # ... but the correct upload submit button does
530        pseudo_image = StringIO('I pretend to be a graphics file')
531        ctrl = self.browser.getControl(name='birthcertificateupload')
532        file_ctrl = ctrl.mech_control
533        file_ctrl.add_file(pseudo_image, filename='my_birth_certificate.jpg')
534        self.browser.getControl(
535            name='upload_birthcertificateupload').click()
536        # There is a correct <img> link included
537        self.assertTrue(
538            '<a target="image" href="birth_certificate">'
539            in self.browser.contents)
540        # Browsing the link shows a real image
541        self.browser.open('birth_certificate')
542        self.assertEqual(
543            self.browser.headers['content-type'], 'image/jpeg')
544        self.assertEqual(len(self.browser.contents), 31)
545        # Reuploading a file which is bigger than 150k will raise an error
546        self.browser.open(self.manage_clearance_path)
547        photo_content = 'A' * 1024 * 151  # A string of 21 KB size
548        pseudo_image = StringIO(photo_content)
549        ctrl = self.browser.getControl(name='birthcertificateupload')
550        file_ctrl = ctrl.mech_control
551        file_ctrl.add_file(pseudo_image, filename='my_birth_certificate.jpg')
552        self.browser.getControl(
553            name='upload_birthcertificateupload').click()
554        self.assertTrue(
555            'Uploaded file is too big' in self.browser.contents)
556        # File names must meet several conditions
557        pseudo_image = StringIO('I pretend to be a graphics file')
558        ctrl = self.browser.getControl(name='birthcertificateupload')
559        file_ctrl = ctrl.mech_control
560        file_ctrl.add_file(pseudo_image, filename='my.photo.jpg')
561        self.browser.getControl(
562            name='upload_birthcertificateupload').click()
563        self.assertTrue('File name contains more than one dot'
564            in self.browser.contents)
565        ctrl = self.browser.getControl(name='birthcertificateupload')
566        file_ctrl = ctrl.mech_control
567        file_ctrl.add_file(pseudo_image, filename='my_birth_certificate')
568        self.browser.getControl(
569            name='upload_birthcertificateupload').click()
570        self.assertTrue('File name has no extension' in self.browser.contents)
571        ctrl = self.browser.getControl(name='birthcertificateupload')
572        file_ctrl = ctrl.mech_control
573        file_ctrl.add_file(pseudo_image, filename='my_birth_certificate.bmp')
574        self.browser.getControl(
575            name='upload_birthcertificateupload').click()
576        self.assertTrue('Only the following extension are allowed'
577            in self.browser.contents)
578        # Managers can delete files
579        self.browser.getControl(name='delete_birthcertificateupload').click()
580        self.assertTrue(
581            'birth_certificate deleted' in self.browser.contents)
582        # Managers can add and delete second file
583        self.browser.open(self.manage_clearance_path)
584        pseudo_image = StringIO('I pretend to be a graphics file')
585        ctrl = self.browser.getControl(name='birthcertificateupload')
586        file_ctrl = ctrl.mech_control
587        file_ctrl.add_file(pseudo_image, filename='my_acceptance_letter.jpg')
588        self.browser.getControl(
589            name='upload_acceptanceletterupload').click()
590        self.assertFalse(
591            '<a target="image" href="acc_let">'
592            in self.browser.contents)
593        ctrl = self.browser.getControl(name='acceptanceletterupload')
594        file_ctrl = ctrl.mech_control
595        file_ctrl.add_file(pseudo_image, filename='my_acceptance_letter.jpg')
596        self.browser.getControl(
597            name='upload_acceptanceletterupload').click()
598        self.assertTrue(
599            '<a target="image" href="acc_let">'
600            in self.browser.contents)
601        self.browser.getControl(
602            name='delete_acceptanceletterupload').click()
603        self.assertTrue(
604            'acc_let deleted'
605            in self.browser.contents)
606        # Managers can upload a file via the StudentBaseManageFormPage
607        self.browser.open(self.manage_student_path)
608        pseudo_image = StringIO('I pretend to be a graphics file')
609        ctrl = self.browser.getControl(name='passportuploadmanage')
610        file_ctrl = ctrl.mech_control
611        file_ctrl.add_file(pseudo_image, filename='my_photo.bmp')
612        self.browser.getControl(
613            name='upload_passportuploadmanage').click()
614        self.assertTrue('jpg file extension expected'
615            in self.browser.contents)
616        ctrl = self.browser.getControl(name='passportuploadmanage')
617        file_ctrl = ctrl.mech_control
618        file_ctrl.add_file(pseudo_image, filename='my_photo.jpg')
619        self.browser.getControl(
620            name='upload_passportuploadmanage').click()
621        self.assertTrue(
622            '<img align="middle" height="125px" src="passport.jpg" />'
623            in self.browser.contents)
624        # We remove the passport file again
625        self.browser.open(self.manage_student_path)
626        self.browser.getControl('Delete').click()
627        self.browser.open(self.student_path + '/clearance.pdf')
628        self.assertEqual(self.browser.headers['Status'], '200 Ok')
629        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
630
631    def test_manage_course_lists(self):
632        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
633        self.browser.open(self.student_path)
634        self.browser.getLink("Study Course").click()
635        self.assertEqual(self.browser.headers['Status'], '200 Ok')
636        self.assertEqual(self.browser.url, self.studycourse_path)
637        self.assertTrue('Undergraduate Full-Time' in self.browser.contents)
638        self.browser.getLink("Manage").click()
639        self.assertTrue('Manage study course' in self.browser.contents)
640        # Before we can select a level, the certificate must
641        # be selected and saved
642        self.browser.getControl(name="form.certificate").value = ['CERT1']
643        self.browser.getControl(name="form.current_session").value = ['2004']
644        self.browser.getControl(name="form.current_verdict").value = ['A']
645        self.browser.getControl(name="form.entry_mode").value = ['ug_ft']
646        self.browser.getControl("Save").click()
647        # Now we can save also the current level which depends on start and end
648        # level of the certificate
649        self.browser.getControl(name="form.current_level").value = ['100']
650        self.browser.getControl("Save").click()
651        # Managers can add and remove any study level (course list)
652        self.browser.getControl(name="addlevel").value = ['100']
653        self.browser.getControl("Add study level").click()
654        self.assertMatches('...<span>100</span>...', self.browser.contents)
655        self.browser.getControl("Add study level").click()
656        self.assertMatches('...This level exists...', self.browser.contents)
657        self.browser.getControl("Remove selected").click()
658        self.assertMatches(
659            '...No study level selected...', self.browser.contents)
660        self.browser.getControl(name="val_id").value = ['100']
661        self.browser.getControl("Remove selected").click()
662        self.assertMatches('...Successfully removed...', self.browser.contents)
663        # Add level again
664        self.browser.getControl(name="addlevel").value = ['100']
665        self.browser.getControl("Add study level").click()
666
667        # Managers can view and manage course lists
668        self.browser.getLink("100").click()
669        self.assertMatches(
670            '...: Study Level 100 (Year 1)...', self.browser.contents)
671        self.browser.getLink("Manage").click()
672        self.browser.getControl(name="form.level_session").value = ['2002']
673        self.browser.getControl("Save").click()
674        self.browser.getControl("Remove selected").click()
675        self.assertMatches('...No ticket selected...', self.browser.contents)
676        ctrl = self.browser.getControl(name='val_id')
677        ctrl.getControl(value='COURSE1').selected = True
678        self.browser.getControl("Remove selected", index=0).click()
679        self.assertTrue('Successfully removed' in self.browser.contents)
680        self.browser.getControl("Add course ticket").click()
681        self.browser.getControl(name="form.course").value = ['COURSE1']
682        self.browser.getControl("Add course ticket").click()
683        self.assertTrue('Successfully added' 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('The ticket exists' in self.browser.contents)
688        self.browser.getControl("Cancel").click()
689        self.browser.getLink("COURSE1").click()
690        self.browser.getLink("Manage").click()
691        self.browser.getControl(name="form.score").value = '10'
692        self.browser.getControl("Save").click()
693        self.assertTrue('Form has been saved' in self.browser.contents)
694        # Carry-over courses will be collected when next level is created
695        self.browser.open(self.student_path + '/studycourse/manage')
696        # Add next level
697        self.browser.getControl(name="addlevel").value = ['200']
698        self.browser.getControl("Add study level").click()
699        self.browser.getLink("200").click()
700        self.assertMatches(
701            '...: Study Level 200 (Year 2)...', self.browser.contents)
702        # COURSE1 has score 0 and thus will become a carry-over course
703        # in level 200
704        self.assertEqual(
705            sorted(self.student['studycourse']['200'].keys()), [u'COURSE1'])
706        self.assertTrue(
707            self.student['studycourse']['200']['COURSE1'].carry_over)
708        return
709
710    def test_manage_workflow(self):
711        # Managers can pass through the whole workflow
712        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
713        student = self.app['students'][self.student_id]
714        self.browser.open(self.manage_student_path)
715        self.assertTrue(student.clearance_locked)
716        self.browser.getControl(name="transition").value = ['admit']
717        self.browser.getControl("Save").click()
718        self.assertTrue(student.clearance_locked)
719        self.browser.getControl(name="transition").value = ['start_clearance']
720        self.browser.getControl("Save").click()
721        self.assertFalse(student.clearance_locked)
722        self.browser.getControl(name="transition").value = ['request_clearance']
723        self.browser.getControl("Save").click()
724        self.assertTrue(student.clearance_locked)
725        self.browser.getControl(name="transition").value = ['clear']
726        self.browser.getControl("Save").click()
727        self.browser.getControl(
728            name="transition").value = ['pay_first_school_fee']
729        self.browser.getControl("Save").click()
730        self.browser.getControl(name="transition").value = ['reset6']
731        self.browser.getControl("Save").click()
732        # In state returning the pay_school_fee transition triggers some
733        # changes of attributes
734        self.browser.getControl(name="transition").value = ['pay_school_fee']
735        self.browser.getControl("Save").click()
736        self.assertEqual(student['studycourse'].current_session, 2005) # +1
737        self.assertEqual(student['studycourse'].current_level, 200) # +100
738        self.assertEqual(student['studycourse'].current_verdict, '0') # 0 = not set
739        self.assertEqual(student['studycourse'].previous_verdict, 'A')
740        self.browser.getControl(name="transition").value = ['register_courses']
741        self.browser.getControl("Save").click()
742        self.browser.getControl(name="transition").value = ['validate_courses']
743        self.browser.getControl("Save").click()
744        self.browser.getControl(name="transition").value = ['return']
745        self.browser.getControl("Save").click()
746        return
747
748    def test_manage_import(self):
749        # Managers can import student data files
750        datacenter_path = 'http://localhost/app/datacenter'
751        # Prepare a csv file for students
752        open('students.csv', 'wb').write(
753"""firstname,lastname,reg_number,date_of_birth,matric_number,email,phone,sex,password
754Aaren,Pieri,1,1990-01-02,100000,aa@aa.ng,1234,m,mypwd1
755Claus,Finau,2,1990-01-03,100001,aa@aa.ng,1234,m,mypwd1
756Brit,Berson,3,1990-01-04,100001,aa@aa.ng,1234,m,mypwd1
757""")
758        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
759        self.browser.open(datacenter_path)
760        self.browser.getLink('Upload CSV file').click()
761        filecontents = StringIO(open('students.csv', 'rb').read())
762        filewidget = self.browser.getControl(name='uploadfile:file')
763        filewidget.add_file(filecontents, 'text/plain', 'students.csv')
764        self.browser.getControl(name='SUBMIT').click()
765        self.browser.getLink('Batch processing').click()
766        button = lookup_submit_value(
767            'select', 'students_zope.mgr.csv', self.browser)
768        button.click()
769        importerselect = self.browser.getControl(name='importer')
770        modeselect = self.browser.getControl(name='mode')
771        importerselect.getControl('Student Processor').selected = True
772        modeselect.getControl(value='create').selected = True
773        self.browser.getControl('Proceed to step 3').click()
774        self.assertTrue('Header fields OK' in self.browser.contents)
775        self.browser.getControl('Perform import').click()
776        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
777        self.assertTrue('Successfully processed 2 rows' in self.browser.contents)
778        self.assertTrue('Batch processing finished' in self.browser.contents)
779        open('studycourses.csv', 'wb').write(
780"""reg_number,matric_number,certificate,current_session,current_level
7811,,CERT1,2008,100
782,100001,CERT1,2008,100
783,100002,CERT1,2008,100
784""")
785        self.browser.open(datacenter_path)
786        self.browser.getLink('Upload CSV file').click()
787        filecontents = StringIO(open('studycourses.csv', 'rb').read())
788        filewidget = self.browser.getControl(name='uploadfile:file')
789        filewidget.add_file(filecontents, 'text/plain', 'studycourses.csv')
790        self.browser.getControl(name='SUBMIT').click()
791        self.browser.getLink('Batch processing').click()
792        button = lookup_submit_value(
793            'select', 'studycourses_zope.mgr.csv', self.browser)
794        button.click()
795        importerselect = self.browser.getControl(name='importer')
796        modeselect = self.browser.getControl(name='mode')
797        importerselect.getControl(
798            'StudentStudyCourse Processor (update only)').selected = True
799        modeselect.getControl(value='create').selected = True
800        self.browser.getControl('Proceed to step 3').click()
801        self.assertTrue('Update mode only' in self.browser.contents)
802        self.browser.getControl('Proceed to step 3').click()
803        self.assertTrue('Header fields OK' in self.browser.contents)
804        self.browser.getControl('Perform import').click()
805        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
806        self.assertTrue('Successfully processed 2 rows'
807                        in self.browser.contents)
808        # The students are properly indexed and we can
809        # thus find a student in  the department
810        self.browser.open(self.manage_container_path)
811        self.browser.getControl(name="searchtype").value = ['depcode']
812        self.browser.getControl(name="searchterm").value = 'dep1'
813        self.browser.getControl("Search").click()
814        self.assertTrue('Aaren Pieri' in self.browser.contents)
815        # We can search for a new student by name ...
816        self.browser.getControl(name="searchtype").value = ['fullname']
817        self.browser.getControl(name="searchterm").value = 'Claus'
818        self.browser.getControl("Search").click()
819        self.assertTrue('Claus Finau' in self.browser.contents)
820        # ... and check if the imported password has been properly set
821        ctrl = self.browser.getControl(name='entries')
822        value = ctrl.options[0]
823        claus = self.app['students'][value]
824        self.assertTrue(IUserAccount(claus).checkPassword('mypwd1'))
825        return
826
827    def test_handle_clearance_by_co(self):
828        # Create clearance officer
829        self.app['users'].addUser('mrclear', 'mrclearsecret')
830        self.app['users']['mrclear'].email = 'mrclear@foo.ng'
831        self.app['users']['mrclear'].title = 'Carlo Pitter'
832        # Clearance officers need not necessarily to get
833        # the StudentsOfficer site role
834        #prmglobal = IPrincipalRoleManager(self.app)
835        #prmglobal.assignRoleToPrincipal('waeup.StudentsOfficer', 'mrclear')
836        # Assign local ClearanceOfficer role
837        department = self.app['faculties']['fac1']['dep1']
838        prmlocal = IPrincipalRoleManager(department)
839        prmlocal.assignRoleToPrincipal('waeup.local.ClearanceOfficer', 'mrclear')
840        IWorkflowState(self.student).setState('clearance started')
841        # Login as clearance officer
842        self.browser.open(self.login_path)
843        self.browser.getControl(name="form.login").value = 'mrclear'
844        self.browser.getControl(name="form.password").value = 'mrclearsecret'
845        self.browser.getControl("Login").click()
846        self.assertMatches('...You logged in...', self.browser.contents)
847        # CO can see his roles
848        self.browser.getLink("My Roles").click()
849        self.assertMatches(
850            '...<div>Academics Officer (view only)</div>...',
851            self.browser.contents)
852        #self.assertMatches(
853        #    '...<div>Students Officer (view only)</div>...',
854        #    self.browser.contents)
855        # But not his local role ...
856        self.assertFalse('Clearance Officer' in self.browser.contents)
857        # ... because we forgot to notify the department that the local role
858        # has changed
859        notify(LocalRoleSetEvent(
860            department, 'waeup.local.ClearanceOfficer', 'mrclear', granted=True))
861        self.browser.open('http://localhost/app/users/mrclear/my_roles')
862        self.assertTrue('Clearance Officer' in self.browser.contents)
863        self.assertMatches(
864            '...<a href="http://localhost/app/faculties/fac1/dep1">...',
865            self.browser.contents)
866        # CO can view the student ...
867        self.browser.open(self.clearance_path)
868        self.assertEqual(self.browser.headers['Status'], '200 Ok')
869        self.assertEqual(self.browser.url, self.clearance_path)
870        # ... but not other students
871        other_student = Student()
872        other_student.firstname = u'Dep2'
873        other_student.lastname = u'Student'
874        self.app['students'].addStudent(other_student)
875        other_student_path = (
876            'http://localhost/app/students/%s' % other_student.student_id)
877        self.assertRaises(
878            Unauthorized, self.browser.open, other_student_path)
879        # Only in state clearance requested the CO does see the 'Clear' button
880        self.browser.open(self.clearance_path)
881        self.assertFalse('Clear student' in self.browser.contents)
882        IWorkflowInfo(self.student).fireTransition('request_clearance')
883        self.browser.open(self.clearance_path)
884        self.assertTrue('Clear student' in self.browser.contents)
885        self.browser.getLink("Clear student").click()
886        self.assertTrue('Student has been cleared' in self.browser.contents)
887        self.assertTrue('cleared' in self.browser.contents)
888        self.browser.getLink("Reject clearance").click()
889        self.assertTrue('Clearance has been annulled' in self.browser.contents)
890        urlmessage = 'Clearance+has+been+annulled.'
891        # CO does now see the contact form
892        self.assertEqual(self.browser.url, self.student_path +
893            '/contactstudent?subject=%s' % urlmessage)
894        self.assertTrue('clearance started' in self.browser.contents)
895        IWorkflowInfo(self.student).fireTransition('request_clearance')
896        self.browser.open(self.clearance_path)
897        self.browser.getLink("Reject clearance").click()
898        self.assertTrue('Clearance request has been rejected'
899            in self.browser.contents)
900        self.assertTrue('clearance started' in self.browser.contents)
901        # CO does now also see the contact form and can send a message
902        self.browser.getControl(name="form.subject").value = 'Important subject'
903        self.browser.getControl(name="form.body").value = 'Clearance rejected'
904        self.browser.getControl("Send message now").click()
905        self.assertTrue('Your message has been sent' in self.browser.contents)
906        # The CO can't clear students if not in state
907        # clearance requested
908        self.browser.open(self.student_path + '/clear')
909        self.assertTrue('Student is in wrong state'
910            in self.browser.contents)
911        # The CO can go to his department throug the my_roles page
912        self.browser.open('http://localhost/app/users/mrclear/my_roles')
913        self.browser.getLink("http://localhost/app/faculties/fac1/dep1").click()
914        # and view the list of students
915        self.browser.getLink("Show students").click()
916        self.assertTrue(self.student_id in self.browser.contents)
917
918    def test_handle_courses_by_ca(self):
919        # Create course adviser
920        self.app['users'].addUser('mrsadvise', 'mrsadvisesecret')
921        self.app['users']['mrsadvise'].email = 'mradvise@foo.ng'
922        self.app['users']['mrsadvise'].title = 'Helen Procter'
923        # Assign local CourseAdviser100 role for a certificate
924        cert = self.app['faculties']['fac1']['dep1'].certificates['CERT1']
925        prmlocal = IPrincipalRoleManager(cert)
926        prmlocal.assignRoleToPrincipal('waeup.local.CourseAdviser100', 'mrsadvise')
927        IWorkflowState(self.student).setState('school fee paid')
928        # Login as course adviser
929        self.browser.open(self.login_path)
930        self.browser.getControl(name="form.login").value = 'mrsadvise'
931        self.browser.getControl(name="form.password").value = 'mrsadvisesecret'
932        self.browser.getControl("Login").click()
933        self.assertMatches('...You logged in...', self.browser.contents)
934        # CO can see his roles
935        self.browser.getLink("My Roles").click()
936        self.assertMatches(
937            '...<div>Academics Officer (view only)</div>...',
938            self.browser.contents)
939        # But not his local role ...
940        self.assertFalse('Course Adviser' in self.browser.contents)
941        # ... because we forgot to notify the certificate that the local role
942        # has changed
943        notify(LocalRoleSetEvent(
944            cert, 'waeup.local.CourseAdviser100', 'mrsadvise', granted=True))
945        self.browser.open('http://localhost/app/users/mrsadvise/my_roles')
946        self.assertTrue('Course Adviser 100L' in self.browser.contents)
947        self.assertMatches(
948            '...<a href="http://localhost/app/faculties/fac1/dep1/certificates/CERT1">...',
949            self.browser.contents)
950        # CA can view the student ...
951        self.browser.open(self.student_path)
952        self.assertEqual(self.browser.headers['Status'], '200 Ok')
953        self.assertEqual(self.browser.url, self.student_path)
954        # ... but not other students
955        other_student = Student()
956        other_student.firstname = u'Dep2'
957        other_student.lastname = u'Student'
958        self.app['students'].addStudent(other_student)
959        other_student_path = (
960            'http://localhost/app/students/%s' % other_student.student_id)
961        self.assertRaises(
962            Unauthorized, self.browser.open, other_student_path)
963        # We add study level 110 to the student's studycourse
964        studylevel = StudentStudyLevel()
965        studylevel.level = 110
966        self.student['studycourse'].addStudentStudyLevel(
967            cert,studylevel)
968        L110_student_path = self.studycourse_path + '/110'
969        # Only in state courses registered and only if the current level
970        # corresponds with the name of the study level object
971        # the 100L CA does see the 'Validate' button
972        self.browser.open(L110_student_path)
973        self.assertFalse('Validate' in self.browser.contents)
974        IWorkflowInfo(self.student).fireTransition('register_courses')
975        self.browser.open(L110_student_path)
976        self.assertFalse('Validate' in self.browser.contents)
977        self.student['studycourse'].current_level = 110
978        self.browser.open(L110_student_path)
979        self.assertTrue('Validate' in self.browser.contents)
980        # ... but a 100L CA does not see the button on other levels
981        studylevel2 = StudentStudyLevel()
982        studylevel2.level = 200
983        self.student['studycourse'].addStudentStudyLevel(
984            cert,studylevel2)
985        L200_student_path = self.studycourse_path + '/200'
986        self.browser.open(L200_student_path)
987        self.assertFalse('Validate' in self.browser.contents)
988        self.browser.open(L110_student_path)
989        self.browser.getLink("Validate courses").click()
990        self.assertTrue('Course list has been validated' in self.browser.contents)
991        self.assertTrue('courses validated' in self.browser.contents)
992        self.browser.getLink("Reject courses").click()
993        self.assertTrue('Course list request has been annulled.'
994            in self.browser.contents)
995        urlmessage = 'Course+list+request+has+been+annulled.'
996        self.assertEqual(self.browser.url, self.student_path +
997            '/contactstudent?subject=%s' % urlmessage)
998        self.assertTrue('school fee paid' in self.browser.contents)
999        IWorkflowInfo(self.student).fireTransition('register_courses')
1000        self.browser.open(L110_student_path)
1001        self.browser.getLink("Reject courses").click()
1002        self.assertTrue('Course list request has been rejected'
1003            in self.browser.contents)
1004        self.assertTrue('school fee paid' in self.browser.contents)
1005        # CA does now see the contact form and can send a message
1006        self.browser.getControl(name="form.subject").value = 'Important subject'
1007        self.browser.getControl(name="form.body").value = 'Course list rejected'
1008        self.browser.getControl("Send message now").click()
1009        self.assertTrue('Your message has been sent' in self.browser.contents)
1010        # The CA can't validate courses if not in state
1011        # courses registered
1012        self.browser.open(L110_student_path + '/validate_courses')
1013        self.assertTrue('Student is in the wrong state'
1014            in self.browser.contents)
1015        # The CA can go to his certificate through the my_roles page
1016        self.browser.open('http://localhost/app/users/mrsadvise/my_roles')
1017        self.browser.getLink(
1018            "http://localhost/app/faculties/fac1/dep1/certificates/CERT1").click()
1019        # and view the list of students
1020        self.browser.getLink("Show students").click()
1021        self.assertTrue(self.student_id in self.browser.contents)
1022
1023    def test_student_change_password(self):
1024        # Students can change the password
1025        self.browser.open(self.login_path)
1026        self.browser.getControl(name="form.login").value = self.student_id
1027        self.browser.getControl(name="form.password").value = 'spwd'
1028        self.browser.getControl("Login").click()
1029        self.assertEqual(self.browser.url, self.student_path)
1030        self.assertTrue('You logged in' in self.browser.contents)
1031        # Change password
1032        self.browser.getLink("Change password").click()
1033        self.browser.getControl(name="change_password").value = 'pw'
1034        self.browser.getControl(
1035            name="change_password_repeat").value = 'pw'
1036        self.browser.getControl("Save").click()
1037        self.assertTrue('Password must have at least' in self.browser.contents)
1038        self.browser.getControl(name="change_password").value = 'new_password'
1039        self.browser.getControl(
1040            name="change_password_repeat").value = 'new_passssword'
1041        self.browser.getControl("Save").click()
1042        self.assertTrue('Passwords do not match' in self.browser.contents)
1043        self.browser.getControl(name="change_password").value = 'new_password'
1044        self.browser.getControl(
1045            name="change_password_repeat").value = 'new_password'
1046        self.browser.getControl("Save").click()
1047        self.assertTrue('Password changed' in self.browser.contents)
1048        # We are still logged in. Changing the password hasn't thrown us out.
1049        self.browser.getLink("Base Data").click()
1050        self.assertEqual(self.browser.url, self.student_path)
1051        # We can logout
1052        self.browser.getLink("Logout").click()
1053        self.assertTrue('You have been logged out' in self.browser.contents)
1054        self.assertEqual(self.browser.url, 'http://localhost/app')
1055        # We can login again with the new password
1056        self.browser.getLink("Login").click()
1057        self.browser.open(self.login_path)
1058        self.browser.getControl(name="form.login").value = self.student_id
1059        self.browser.getControl(name="form.password").value = 'new_password'
1060        self.browser.getControl("Login").click()
1061        self.assertEqual(self.browser.url, self.student_path)
1062        self.assertTrue('You logged in' in self.browser.contents)
1063        return
1064
1065    def test_setpassword(self):
1066        # Set password for first-time access
1067        student = Student()
1068        student.reg_number = u'123456'
1069        student.firstname = u'Klaus'
1070        student.lastname = u'Tester'
1071        self.app['students'].addStudent(student)
1072        setpassword_path = 'http://localhost/app/setpassword'
1073        student_path = 'http://localhost/app/students/%s' % student.student_id
1074        self.browser.open(setpassword_path)
1075        self.browser.getControl(name="ac_series").value = self.existing_pwdseries
1076        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
1077        self.browser.getControl(name="reg_number").value = '223456'
1078        self.browser.getControl("Set").click()
1079        self.assertMatches('...No student found...',
1080                           self.browser.contents)
1081        self.browser.getControl(name="reg_number").value = '123456'
1082        self.browser.getControl(name="ac_number").value = '999999'
1083        self.browser.getControl("Set").click()
1084        self.assertMatches('...Access code is invalid...',
1085                           self.browser.contents)
1086        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
1087        self.browser.getControl("Set").click()
1088        self.assertMatches('...Password has been set. Your Student Id is...',
1089                           self.browser.contents)
1090        self.browser.getControl("Set").click()
1091        self.assertMatches(
1092            '...Password has already been set. Your Student Id is...',
1093            self.browser.contents)
1094        existing_pwdpin = self.pwdpins[1]
1095        parts = existing_pwdpin.split('-')[1:]
1096        existing_pwdseries, existing_pwdnumber = parts
1097        self.browser.getControl(name="ac_series").value = existing_pwdseries
1098        self.browser.getControl(name="ac_number").value = existing_pwdnumber
1099        self.browser.getControl(name="reg_number").value = '123456'
1100        self.browser.getControl("Set").click()
1101        self.assertMatches(
1102            '...You are using the wrong Access Code...',
1103            self.browser.contents)
1104        # The student can login with the new credentials
1105        self.browser.open(self.login_path)
1106        self.browser.getControl(name="form.login").value = student.student_id
1107        self.browser.getControl(
1108            name="form.password").value = self.existing_pwdnumber
1109        self.browser.getControl("Login").click()
1110        self.assertEqual(self.browser.url, student_path)
1111        self.assertTrue('You logged in' in self.browser.contents)
1112        return
1113
1114    def test_student_access(self):
1115        # Students can access their own objects
1116        # and can perform actions
1117        IWorkflowInfo(self.student).fireTransition('admit')
1118        self.browser.open(self.login_path)
1119        self.browser.getControl(name="form.login").value = self.student_id
1120        self.browser.getControl(name="form.password").value = 'spwd'
1121        self.browser.getControl("Login").click()
1122        # Student can upload a passport picture
1123        self.browser.open(self.student_path + '/change_portrait')
1124        ctrl = self.browser.getControl(name='passportuploadedit')
1125        file_obj = open(
1126            os.path.join(os.path.dirname(__file__), 'test_image.jpg'),'rb')
1127        file_ctrl = ctrl.mech_control
1128        file_ctrl.add_file(file_obj, filename='my_photo.jpg')
1129        self.browser.getControl(
1130            name='upload_passportuploadedit').click()
1131        self.assertTrue(
1132            '<img align="middle" height="125px" src="passport.jpg" />'
1133            in self.browser.contents)
1134        # Student can view the clearance data
1135        self.browser.getLink("Clearance Data").click()
1136        # Student can't open clearance edit form before starting clearance
1137        self.browser.open(self.student_path + '/cedit')
1138        self.assertMatches('...The requested form is locked...',
1139                           self.browser.contents)
1140        self.browser.getLink("Clearance Data").click()
1141        self.browser.getLink("Start clearance").click()
1142        self.student.email = None
1143        # Uups, we forgot to fill the email fields
1144        self.browser.getControl("Start clearance").click()
1145        self.assertMatches('...Not all required fields filled...',
1146                           self.browser.contents)
1147        self.student.email = 'aa@aa.ng'
1148        self.browser.open(self.student_path + '/start_clearance')
1149        self.browser.getControl(name="ac_series").value = '3'
1150        self.browser.getControl(name="ac_number").value = '4444444'
1151        self.browser.getControl("Start clearance now").click()
1152        self.assertMatches('...Activation code is invalid...',
1153                           self.browser.contents)
1154        self.browser.getControl(name="ac_series").value = self.existing_clrseries
1155        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
1156        # Owner is Hans Wurst, AC can't be invalidated
1157        self.browser.getControl("Start clearance now").click()
1158        self.assertMatches('...You are not the owner of this access code...',
1159                           self.browser.contents)
1160        # Set the correct owner
1161        self.existing_clrac.owner = self.student_id
1162        self.browser.getControl("Start clearance now").click()
1163        self.assertMatches('...Clearance process has been started...',
1164                           self.browser.contents)
1165        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
1166        self.browser.getControl("Save", index=0).click()
1167        # Student can view the clearance data
1168        self.browser.getLink("Clearance Data").click()
1169        # and go back to the edit form
1170        self.browser.getLink("Edit").click()
1171        # Students can upload documents
1172        ctrl = self.browser.getControl(name='birthcertificateupload')
1173        file_obj = open(
1174            os.path.join(os.path.dirname(__file__), 'test_image.jpg'),'rb')
1175        file_ctrl = ctrl.mech_control
1176        file_ctrl.add_file(file_obj, filename='my_birth_certificate.jpg')
1177        self.browser.getControl(
1178            name='upload_birthcertificateupload').click()
1179        self.assertTrue(
1180            '<a target="image" href="birth_certificate">Birth Certificate Scan</a>'
1181            in self.browser.contents)
1182        # Students can open clearance slip
1183        self.browser.getLink("View").click()
1184        self.browser.getLink("Download clearance slip").click()
1185        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1186        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1187        # Students can request clearance
1188        self.browser.open(self.edit_clearance_path)
1189        self.browser.getControl("Save and request clearance").click()
1190        self.browser.getControl(name="ac_series").value = self.existing_clrseries
1191        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
1192        self.browser.getControl("Request clearance now").click()
1193        self.assertMatches('...Clearance has been requested...',
1194                           self.browser.contents)
1195        # Student can't reopen clearance form after requesting clearance
1196        self.browser.open(self.student_path + '/cedit')
1197        self.assertMatches('...The requested form is locked...',
1198                           self.browser.contents)
1199        # Student can't add study level if not in state 'school fee paid'
1200        self.browser.open(self.student_path + '/studycourse/add')
1201        self.assertMatches('...The requested form is locked...',
1202                           self.browser.contents)
1203        # ... and must be transferred first
1204        IWorkflowInfo(self.student).fireTransition('clear')
1205        IWorkflowInfo(self.student).fireTransition('pay_first_school_fee')
1206        # Now students can add the current study level
1207        self.browser.getLink("Study Course").click()
1208        self.browser.getLink("Add course list").click()
1209        self.assertMatches('...Add current level 100 (Year 1)...',
1210                           self.browser.contents)
1211        self.browser.getControl("Create course list now").click()
1212        self.browser.getLink("100").click()
1213        self.browser.getLink("Add and remove courses").click()
1214        self.browser.getControl("Add course ticket").click()
1215        self.browser.getControl(name="form.course").value = ['COURSE1']
1216        self.browser.getControl("Add course ticket").click()
1217        self.assertMatches('...The ticket exists...',
1218                           self.browser.contents)
1219        self.student['studycourse'].current_level = 200
1220        self.browser.getLink("Study Course").click()
1221        self.browser.getLink("Add course list").click()
1222        self.assertMatches('...Add current level 200 (Year 2)...',
1223                           self.browser.contents)
1224        self.browser.getControl("Create course list now").click()
1225        self.browser.getLink("200").click()
1226        self.browser.getLink("Add and remove courses").click()
1227        self.browser.getControl("Add course ticket").click()
1228        self.browser.getControl(name="form.course").value = ['COURSE1']
1229        self.browser.getControl("Add course ticket").click()
1230        self.assertMatches('...The ticket exists...',
1231                           self.browser.contents)
1232        # Indeed the ticket exists as carry-over course from level 100
1233        # since its score was 0
1234        self.assertTrue(
1235            self.student['studycourse']['200']['COURSE1'].carry_over is True)
1236        # Students can open the pdf course registration slip
1237        self.browser.open(self.student_path + '/studycourse/200')
1238        self.browser.getLink("Download course registration slip").click()
1239        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1240        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1241        # Students can remove course tickets
1242        self.browser.open(self.student_path + '/studycourse/200/edit')
1243        self.browser.getControl("Remove selected", index=0).click()
1244        self.assertTrue('No ticket selected' in self.browser.contents)
1245        # No ticket can be selected since the carry-over course is a core course
1246        self.assertRaises(
1247            LookupError, self.browser.getControl, name='val_id')
1248        self.student['studycourse']['200']['COURSE1'].mandatory = False
1249        self.browser.open(self.student_path + '/studycourse/200/edit')
1250        # Now the student can remove the ticket
1251        ctrl = self.browser.getControl(name='val_id')
1252        ctrl.getControl(value='COURSE1').selected = True
1253        self.browser.getControl("Remove selected", index=0).click()
1254        self.assertTrue('Successfully removed' in self.browser.contents)
1255        self.browser.getControl("Register course list").click()
1256        self.assertTrue('Course list has been registered' in self.browser.contents)
1257        self.assertEqual(self.student.state, 'courses registered')
1258        return
1259
1260    def test_manage_payments(self):
1261        # Managers can add online school fee payment tickets
1262        # if certain requirements are met
1263        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1264        self.browser.open(self.payments_path)
1265        self.browser.getControl("Add online payment ticket").click()
1266        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1267        self.browser.getControl("Create ticket").click()
1268        self.assertMatches('...ticket created...',
1269                           self.browser.contents)
1270        ctrl = self.browser.getControl(name='val_id')
1271        value = ctrl.options[0]
1272        self.browser.getLink(value).click()
1273        self.assertMatches('...Amount Authorized...',
1274                           self.browser.contents)
1275        payment_url = self.browser.url
1276
1277        # The pdf payment slip can't yet be opened
1278        #self.browser.open(payment_url + '/payment_slip.pdf')
1279        #self.assertMatches('...Ticket not yet paid...',
1280        #                   self.browser.contents)
1281
1282        # The same payment (with same p_item, p_session and p_category)
1283        # can be initialized a second time if the former ticket is not yet paid.
1284        self.browser.open(self.payments_path)
1285        self.browser.getControl("Add online payment ticket").click()
1286        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1287        self.browser.getControl("Create ticket").click()
1288        self.assertMatches('...Payment ticket created...',
1289                           self.browser.contents)
1290
1291        # Managers can approve the payment
1292        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
1293        self.browser.open(payment_url)
1294        self.browser.getLink("Approve payment").click()
1295        self.assertMatches('...Valid callback received...',
1296                          self.browser.contents)
1297
1298        # The authorized amount has been stored in the access code
1299        self.assertEqual(
1300            self.app['accesscodes']['SFE-0'].values()[0].cost,40000.0)
1301
1302        # Payments can't be approved twice
1303        self.browser.open(payment_url + '/approve')
1304        self.assertMatches('...This ticket has already been paid...',
1305                          self.browser.contents)
1306
1307        # Now the first ticket is paid and no more ticket of same type
1308        # (with same p_item, p_session and p_category) can be added
1309        self.browser.open(self.payments_path)
1310        self.browser.getControl("Add online payment ticket").click()
1311        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1312        self.browser.getControl("Create ticket").click()
1313        self.assertMatches(
1314            '...This type of payment has already been made...',
1315            self.browser.contents)
1316
1317        # Managers can open the pdf payment slip
1318        self.browser.open(payment_url)
1319        self.browser.getLink("Download payment slip").click()
1320        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1321        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1322
1323        # Managers can remove online school fee payment tickets
1324        self.browser.open(self.payments_path)
1325        self.browser.getControl("Remove selected").click()
1326        self.assertMatches('...No payment selected...', self.browser.contents)
1327        ctrl = self.browser.getControl(name='val_id')
1328        value = ctrl.options[0]
1329        ctrl.getControl(value=value).selected = True
1330        self.browser.getControl("Remove selected", index=0).click()
1331        self.assertTrue('Successfully removed' in self.browser.contents)
1332
1333        # Managers can add online clearance payment tickets
1334        self.browser.open(self.payments_path + '/addop')
1335        self.browser.getControl(name="form.p_category").value = ['clearance']
1336        self.browser.getControl("Create ticket").click()
1337        self.assertMatches('...ticket created...',
1338                           self.browser.contents)
1339
1340        # Managers can approve the payment
1341        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
1342        ctrl = self.browser.getControl(name='val_id')
1343        value = ctrl.options[1] # The clearance payment is the second in the table
1344        self.browser.getLink(value).click()
1345        self.browser.open(self.browser.url + '/approve')
1346        self.assertMatches('...Valid callback received...',
1347                          self.browser.contents)
1348        expected = '''...
1349        <td>
1350          <span>Paid</span>
1351        </td>...'''
1352        self.assertMatches(expected,self.browser.contents)
1353        # The new CLR-0 pin has been created
1354        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
1355        pin = self.app['accesscodes']['CLR-0'].keys()[0]
1356        ac = self.app['accesscodes']['CLR-0'][pin]
1357        self.assertEqual(ac.owner, self.student_id)
1358        self.assertEqual(ac.cost, 3456.0)
1359        return
1360
1361    def test_student_payments(self):
1362        # Login
1363        self.browser.open(self.login_path)
1364        self.browser.getControl(name="form.login").value = self.student_id
1365        self.browser.getControl(name="form.password").value = 'spwd'
1366        self.browser.getControl("Login").click()
1367
1368        # Students can add online clearance payment tickets
1369        self.browser.open(self.payments_path + '/addop')
1370        self.browser.getControl(name="form.p_category").value = ['clearance']
1371        self.browser.getControl("Create ticket").click()
1372        self.assertMatches('...ticket created...',
1373                           self.browser.contents)
1374
1375        # Students can't approve the payment
1376        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
1377        ctrl = self.browser.getControl(name='val_id')
1378        value = ctrl.options[0]
1379        self.browser.getLink(value).click()
1380        payment_url = self.browser.url
1381        self.assertRaises(
1382            Unauthorized, self.browser.open, payment_url + '/approve')
1383        # In the base package they can 'use' a fake approval view
1384        self.browser.open(payment_url + '/fake_approve')
1385        self.assertMatches('...Valid callback received...',
1386                          self.browser.contents)
1387        expected = '''...
1388        <td>
1389          <span>Paid</span>
1390        </td>...'''
1391        expected = '''...
1392        <td>
1393          <span>Paid</span>
1394        </td>...'''
1395        self.assertMatches(expected,self.browser.contents)
1396        payment_id = self.student['payments'].keys()[0]
1397        payment = self.student['payments'][payment_id]
1398        self.assertEqual(payment.p_state, 'paid')
1399        self.assertEqual(payment.r_amount_approved, 3456.0)
1400        self.assertEqual(payment.r_code, 'AP')
1401        # The new CLR-0 pin has been created
1402        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
1403        pin = self.app['accesscodes']['CLR-0'].keys()[0]
1404        ac = self.app['accesscodes']['CLR-0'][pin]
1405        self.assertEqual(ac.owner, self.student_id)
1406        self.assertEqual(ac.cost, 3456.0)
1407
1408        # Students can open the pdf payment slip
1409        self.browser.open(payment_url + '/payment_slip.pdf')
1410        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1411        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1412
1413        # The new CLR-0 pin can be used for starting clearance
1414        # but they have to upload a passport picture first
1415        # which is only possible in state admitted
1416        self.browser.open(self.student_path + '/change_portrait')
1417        self.assertMatches('...form is locked...',
1418                          self.browser.contents)
1419        IWorkflowInfo(self.student).fireTransition('admit')
1420        self.browser.open(self.student_path + '/change_portrait')
1421        pseudo_image = StringIO('I pretend to be a graphics file')
1422        ctrl = self.browser.getControl(name='passportuploadedit')
1423        file_ctrl = ctrl.mech_control
1424        file_ctrl.add_file(pseudo_image, filename='my_photo.jpg')
1425        self.browser.getControl(
1426            name='upload_passportuploadedit').click()
1427        self.browser.open(self.student_path + '/start_clearance')
1428        parts = pin.split('-')[1:]
1429        clrseries, clrnumber = parts
1430        self.browser.getControl(name="ac_series").value = clrseries
1431        self.browser.getControl(name="ac_number").value = clrnumber
1432        self.browser.getControl("Start clearance now").click()
1433        self.assertMatches('...Clearance process has been started...',
1434                           self.browser.contents)
1435
1436        # Students can add online school fee payment tickets.
1437        IWorkflowState(self.student).setState('returning')
1438        self.browser.open(self.payments_path)
1439        self.browser.getControl("Add online payment ticket").click()
1440        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1441        self.browser.getControl("Create ticket").click()
1442        self.assertMatches('...ticket created...',
1443                           self.browser.contents)
1444        ctrl = self.browser.getControl(name='val_id')
1445        value = ctrl.options[0]
1446        self.browser.getLink(value).click()
1447        self.assertMatches('...Amount Authorized...',
1448                           self.browser.contents)
1449        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
1450        # Payment session and will be calculated as defined
1451        # in w.k.students.utils because we set changed the state
1452        # to returning
1453        self.assertEqual(self.student['payments'][value].p_session, 2005)
1454        self.assertEqual(self.student['payments'][value].p_level, 200)
1455
1456        # We simulate the approval
1457        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
1458        self.browser.open(self.browser.url + '/fake_approve')
1459        self.assertMatches('...Valid callback received...',
1460                          self.browser.contents)
1461
1462        # Students can remove only online payment tickets which have
1463        # not received a valid callback
1464        self.browser.open(self.payments_path)
1465        self.assertRaises(
1466            LookupError, self.browser.getControl, name='val_id')
1467        self.browser.open(self.payments_path + '/addop')
1468        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1469        self.browser.getControl("Create ticket").click()
1470        self.browser.open(self.payments_path)
1471        ctrl = self.browser.getControl(name='val_id')
1472        value = ctrl.options[0]
1473        ctrl.getControl(value=value).selected = True
1474        self.browser.getControl("Remove selected", index=0).click()
1475        self.assertTrue('Successfully removed' in self.browser.contents)
1476
1477        # The new SFE-0 pin can be used for starting course registration
1478        self.browser.open(self.studycourse_path)
1479        self.browser.getLink('Start course registration').click()
1480        pin = self.app['accesscodes']['SFE-0'].keys()[0]
1481        parts = pin.split('-')[1:]
1482        sfeseries, sfenumber = parts
1483        self.browser.getControl(name="ac_series").value = sfeseries
1484        self.browser.getControl(name="ac_number").value = sfenumber
1485        self.browser.getControl("Start course registration now").click()
1486        self.assertMatches('...Course registration has been started...',
1487                           self.browser.contents)
1488        self.assertTrue(self.student.state == 'school fee paid')
1489        return
1490
1491    def test_manage_accommodation(self):
1492        # Managers can add online booking fee payment tickets and open the
1493        # callback view (see test_manage_payments)
1494        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1495        self.browser.open(self.payments_path)
1496        self.browser.getControl("Add online payment ticket").click()
1497        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1498        # If student is not in accommodation session, payment cannot be processed
1499        self.app['configuration'].accommodation_session = 2011
1500        self.browser.getControl("Create ticket").click()
1501        self.assertMatches('...Your current session does not match...',
1502                           self.browser.contents)
1503        self.app['configuration'].accommodation_session = 2004
1504        self.browser.getControl("Add online payment ticket").click()
1505        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1506        self.browser.getControl("Create ticket").click()
1507        ctrl = self.browser.getControl(name='val_id')
1508        value = ctrl.options[0]
1509        self.browser.getLink(value).click()
1510        self.browser.open(self.browser.url + '/approve')
1511        # The new HOS-0 pin has been created
1512        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
1513        pin = self.app['accesscodes']['HOS-0'].keys()[0]
1514        ac = self.app['accesscodes']['HOS-0'][pin]
1515        self.assertEqual(ac.owner, self.student_id)
1516        parts = pin.split('-')[1:]
1517        sfeseries, sfenumber = parts
1518        # Managers can use HOS code and book a bed space with it
1519        self.browser.open(self.acco_path)
1520        self.browser.getLink("Book accommodation").click()
1521        self.assertMatches('...You are in the wrong...',
1522                           self.browser.contents)
1523        IWorkflowInfo(self.student).fireTransition('admit')
1524        # An existing HOS code can only be used if students
1525        # are in accommodation session
1526        self.student['studycourse'].current_session = 2003
1527        self.browser.getLink("Book accommodation").click()
1528        self.assertMatches('...Your current session does not match...',
1529                           self.browser.contents)
1530        self.student['studycourse'].current_session = 2004
1531        # All requirements are met and ticket can be created
1532        self.browser.getLink("Book accommodation").click()
1533        self.assertMatches('...Activation Code:...',
1534                           self.browser.contents)
1535        self.browser.getControl(name="ac_series").value = sfeseries
1536        self.browser.getControl(name="ac_number").value = sfenumber
1537        self.browser.getControl("Create bed ticket").click()
1538        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1539                           self.browser.contents)
1540        # Bed has been allocated
1541        bed1 = self.app['hostels']['hall-1']['hall-1_A_101_A']
1542        self.assertTrue(bed1.owner == self.student_id)
1543        # BedTicketAddPage is now blocked
1544        self.browser.getLink("Book accommodation").click()
1545        self.assertMatches('...You already booked a bed space...',
1546            self.browser.contents)
1547        # The bed ticket displays the data correctly
1548        self.browser.open(self.acco_path + '/2004')
1549        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1550                           self.browser.contents)
1551        self.assertMatches('...2004/2005...', self.browser.contents)
1552        self.assertMatches('...regular_male_fr...', self.browser.contents)
1553        self.assertMatches('...%s...' % pin, self.browser.contents)
1554        # Managers can relocate students if the student's bed_type has changed
1555        self.browser.getLink("Relocate student").click()
1556        self.assertMatches(
1557            "...Student can't be relocated...", self.browser.contents)
1558        self.student.sex = u'f'
1559        self.browser.getLink("Relocate student").click()
1560        self.assertMatches(
1561            "...Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
1562        self.assertTrue(bed1.owner == NOT_OCCUPIED)
1563        bed2 = self.app['hostels']['hall-1']['hall-1_A_101_B']
1564        self.assertTrue(bed2.owner == self.student_id)
1565        self.assertTrue(self.student['accommodation'][
1566            '2004'].bed_type == u'regular_female_fr')
1567        # The payment object still shows the original payment item
1568        payment_id = self.student['payments'].keys()[0]
1569        payment = self.student['payments'][payment_id]
1570        self.assertTrue(payment.p_item == u'regular_male_fr')
1571        # Managers can relocate students if the bed's bed_type has changed
1572        bed1.bed_type = u'regular_female_fr'
1573        bed2.bed_type = u'regular_male_fr'
1574        notify(grok.ObjectModifiedEvent(bed1))
1575        notify(grok.ObjectModifiedEvent(bed2))
1576        self.browser.getLink("Relocate student").click()
1577        self.assertMatches(
1578            "...Student relocated...", self.browser.contents)
1579        self.assertMatches(
1580            "... Hall 1, Block A, Room 101, Bed A...", self.browser.contents)
1581        self.assertMatches(bed1.owner, self.student_id)
1582        self.assertMatches(bed2.owner, NOT_OCCUPIED)
1583        # Managers can't relocate students if bed is reserved
1584        self.student.sex = u'm'
1585        bed1.bed_type = u'regular_female_reserved'
1586        notify(grok.ObjectModifiedEvent(bed1))
1587        self.browser.getLink("Relocate student").click()
1588        self.assertMatches(
1589            "...Students in reserved beds can't be relocated...",
1590            self.browser.contents)
1591        # Managers can relocate students if booking has been cancelled but
1592        # other bed space has been manually allocated after cancellation
1593        old_owner = bed1.releaseBed()
1594        self.assertMatches(old_owner, self.student_id)
1595        bed2.owner = self.student_id
1596        self.browser.open(self.acco_path + '/2004')
1597        self.assertMatches(
1598            "...booking cancelled...", self.browser.contents)
1599        self.browser.getLink("Relocate student").click()
1600        # We didn't informed the catalog therefore the new owner is not found
1601        self.assertMatches(
1602            "...There is no free bed in your category regular_male_fr...",
1603            self.browser.contents)
1604        # Now we fire the event properly
1605        notify(grok.ObjectModifiedEvent(bed2))
1606        self.browser.getLink("Relocate student").click()
1607        self.assertMatches(
1608            "...Student relocated...", self.browser.contents)
1609        self.assertMatches(
1610            "... Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
1611          # Managers can delete bed tickets
1612        self.browser.open(self.acco_path)
1613        ctrl = self.browser.getControl(name='val_id')
1614        value = ctrl.options[0]
1615        ctrl.getControl(value=value).selected = True
1616        self.browser.getControl("Remove selected", index=0).click()
1617        self.assertMatches('...Successfully removed...', self.browser.contents)
1618        # The bed has been properly released by the event handler
1619        self.assertMatches(bed1.owner, NOT_OCCUPIED)
1620        self.assertMatches(bed2.owner, NOT_OCCUPIED)
1621        return
1622
1623    def xx_test_student_accommodation(self):
1624        # Login
1625        self.browser.open(self.login_path)
1626        self.browser.getControl(name="form.login").value = self.student_id
1627        self.browser.getControl(name="form.password").value = 'spwd'
1628        self.browser.getControl("Login").click()
1629
1630        # Students can add online booking fee payment tickets and open the
1631        # callback view (see test_manage_payments)
1632        self.browser.getLink("Payments").click()
1633        self.browser.getControl("Add online payment ticket").click()
1634        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1635        self.browser.getControl("Create ticket").click()
1636        ctrl = self.browser.getControl(name='val_id')
1637        value = ctrl.options[0]
1638        self.browser.getLink(value).click()
1639        self.browser.open(self.browser.url + '/fake_approve')
1640        # The new HOS-0 pin has been created
1641        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
1642        pin = self.app['accesscodes']['HOS-0'].keys()[0]
1643        ac = self.app['accesscodes']['HOS-0'][pin]
1644        parts = pin.split('-')[1:]
1645        sfeseries, sfenumber = parts
1646
1647        # Students can use HOS code and book a bed space with it
1648        self.browser.open(self.acco_path)
1649        self.browser.getLink("Book accommodation").click()
1650        self.assertMatches('...You are in the wrong...',
1651                           self.browser.contents)
1652        IWorkflowInfo(self.student).fireTransition('admit')
1653        self.browser.getLink("Book accommodation").click()
1654        self.assertMatches('...Activation Code:...',
1655                           self.browser.contents)
1656        self.browser.getControl(name="ac_series").value = u'nonsense'
1657        self.browser.getControl(name="ac_number").value = sfenumber
1658        self.browser.getControl("Create bed ticket").click()
1659        self.assertMatches('...Activation code is invalid...',
1660                           self.browser.contents)
1661        ac.owner = u'Anybody'
1662        self.browser.getControl(name="ac_series").value = sfeseries
1663        self.browser.getControl(name="ac_number").value = sfenumber
1664        self.browser.getControl("Create bed ticket").click()
1665        self.assertMatches('...You are not the owner of this access code...',
1666                           self.browser.contents)
1667        ac.owner = self.student_id
1668        self.browser.getControl(name="ac_series").value = sfeseries
1669        self.browser.getControl(name="ac_number").value = sfenumber
1670        self.browser.getControl("Create bed ticket").click()
1671        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1672                           self.browser.contents)
1673
1674        # Bed has been allocated
1675        bed = self.app['hostels']['hall-1']['hall-1_A_101_A']
1676        self.assertTrue(bed.owner == self.student_id)
1677
1678        # BedTicketAddPage is now blocked
1679        self.browser.getLink("Book accommodation").click()
1680        self.assertMatches('...You already booked a bed space...',
1681            self.browser.contents)
1682
1683        # The bed ticket displays the data correctly
1684        self.browser.open(self.acco_path + '/2004')
1685        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1686                           self.browser.contents)
1687        self.assertMatches('...2004/2005...', self.browser.contents)
1688        self.assertMatches('...regular_male_fr...', self.browser.contents)
1689        self.assertMatches('...%s...' % pin, self.browser.contents)
1690
1691        # Students can open the pdf slip
1692        self.browser.open(self.browser.url + '/bed_allocation.pdf')
1693        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1694        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1695
1696        # Students can't relocate themselves
1697        self.assertFalse('Relocate' in self.browser.contents)
1698        relocate_path = self.acco_path + '/2004/relocate'
1699        self.assertRaises(
1700            Unauthorized, self.browser.open, relocate_path)
1701
1702        # Students can't the Remove button and check boxes
1703        self.browser.open(self.acco_path)
1704        self.assertFalse('Remove' in self.browser.contents)
1705        self.assertFalse('val_id' in self.browser.contents)
1706        return
1707
1708    def test_change_password_request(self):
1709        self.browser.open('http://localhost/app/sendpw')
1710        self.browser.getControl(name="form.identifier").value = '123'
1711        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
1712        self.browser.getControl("Get login credentials").click()
1713        self.assertTrue('An email with' in self.browser.contents)
1714
1715    def test_reindex(self):
1716        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1717        self.browser.open('http://localhost/app/reindex')
1718        self.assertTrue('No catalog name provided' in self.browser.contents)
1719        self.browser.open('http://localhost/app/reindex?ctlg=xyz')
1720        self.assertTrue('xyz_catalog does not exist' in self.browser.contents)
1721        cat = queryUtility(ICatalog, name='students_catalog')
1722        results = cat.searchResults(student_id=(None, None))
1723        self.assertEqual(len(results),1)
1724        cat.clear()
1725        results = cat.searchResults(student_id=(None, None))
1726        self.assertEqual(len(results),0)
1727        self.browser.open('http://localhost/app/reindex?ctlg=students')
1728        self.assertTrue('1 students re-indexed' in self.browser.contents)
1729        results = cat.searchResults(student_id=(None, None))
1730        self.assertEqual(len(results),1)
1731
1732    def test_change_current_mode(self):
1733        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1734        self.browser.open(self.clearance_path)
1735        self.assertFalse('Employer' in self.browser.contents)
1736        self.browser.open(self.manage_clearance_path)
1737        self.assertFalse('Employer' in self.browser.contents)
1738        self.student.clearance_locked = False
1739        self.browser.open(self.edit_clearance_path)
1740        self.assertFalse('Employer' in self.browser.contents)
1741        # Now we change the study mode of the certificate and a different
1742        # interface is used by clearance views
1743        self.certificate.study_mode = 'pg_ft'
1744        self.browser.open(self.clearance_path)
1745        self.assertTrue('Employer' in self.browser.contents)
1746        self.browser.open(self.manage_clearance_path)
1747        self.assertTrue('Employer' in self.browser.contents)
1748        self.browser.open(self.edit_clearance_path)
1749        self.assertTrue('Employer' in self.browser.contents)
Note: See TracBrowser for help on using the repository browser.