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

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

Replace default value of verdicts. Use 'NY' (not yet) instead of '0' (zero).

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