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

Last change on this file since 8642 was 8642, checked in by Henrik Bettermann, 12 years ago

Implement max_credits limit for course registration.

  • Property svn:keywords set to Id
File size: 93.9 KB
Line 
1## $Id: test_browser.py 8642 2012-06-07 10:06:02Z 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        # Course list can't be registered if total_credits exceeds max_credits
1258        self.student['studycourse']['200']['COURSE1'].credits = 60
1259        self.browser.getControl("Register course list").click()
1260        self.assertTrue('Maximum credits of 50 exceeded' in self.browser.contents)
1261        # Student can now remove the ticket
1262        ctrl = self.browser.getControl(name='val_id')
1263        ctrl.getControl(value='COURSE1').selected = True
1264        self.browser.getControl("Remove selected", index=0).click()
1265        self.assertTrue('Successfully removed' in self.browser.contents)
1266        # Course list can be registered, even if it's empty
1267        self.browser.getControl("Register course list").click()
1268        self.assertTrue('Course list has been registered' in self.browser.contents)
1269        self.assertEqual(self.student.state, 'courses registered')
1270        return
1271
1272    def test_manage_payments(self):
1273        # Managers can add online school fee payment tickets
1274        # if certain requirements are met
1275        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1276        self.browser.open(self.payments_path)
1277        IWorkflowState(self.student).setState('cleared')
1278        self.browser.getControl("Add online payment ticket").click()
1279        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1280        self.browser.getControl("Create ticket").click()
1281        self.assertMatches('...ticket created...',
1282                           self.browser.contents)
1283        ctrl = self.browser.getControl(name='val_id')
1284        value = ctrl.options[0]
1285        self.browser.getLink(value).click()
1286        self.assertMatches('...Amount Authorized...',
1287                           self.browser.contents)
1288        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
1289        payment_url = self.browser.url
1290
1291        # The pdf payment slip can't yet be opened
1292        #self.browser.open(payment_url + '/payment_slip.pdf')
1293        #self.assertMatches('...Ticket not yet paid...',
1294        #                   self.browser.contents)
1295
1296        # The same payment (with same p_item, p_session and p_category)
1297        # can be initialized a second time if the former ticket is not yet paid.
1298        self.browser.open(self.payments_path)
1299        self.browser.getControl("Add online payment ticket").click()
1300        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1301        self.browser.getControl("Create ticket").click()
1302        self.assertMatches('...Payment ticket created...',
1303                           self.browser.contents)
1304
1305        # Managers can approve the payment
1306        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
1307        self.browser.open(payment_url)
1308        self.browser.getLink("Approve payment").click()
1309        self.assertMatches('...Successful payment...',
1310                          self.browser.contents)
1311
1312        # The authorized amount has been stored in the access code
1313        self.assertEqual(
1314            self.app['accesscodes']['SFE-0'].values()[0].cost,40000.0)
1315
1316        # Payments can't be approved twice
1317        self.browser.open(payment_url + '/approve')
1318        self.assertMatches('...This ticket has already been paid...',
1319                          self.browser.contents)
1320
1321        # Now the first ticket is paid and no more ticket of same type
1322        # (with same p_item, p_session and p_category) can be added
1323        self.browser.open(self.payments_path)
1324        self.browser.getControl("Add online payment ticket").click()
1325        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1326        self.browser.getControl("Create ticket").click()
1327        self.assertMatches(
1328            '...This type of payment has already been made...',
1329            self.browser.contents)
1330
1331        # Managers can open the pdf payment slip
1332        self.browser.open(payment_url)
1333        self.browser.getLink("Download payment slip").click()
1334        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1335        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1336
1337        # Managers can remove online school fee payment tickets
1338        self.browser.open(self.payments_path)
1339        self.browser.getControl("Remove selected").click()
1340        self.assertMatches('...No payment selected...', self.browser.contents)
1341        ctrl = self.browser.getControl(name='val_id')
1342        value = ctrl.options[0]
1343        ctrl.getControl(value=value).selected = True
1344        self.browser.getControl("Remove selected", index=0).click()
1345        self.assertTrue('Successfully removed' in self.browser.contents)
1346
1347        # Managers can add online clearance payment tickets
1348        self.browser.open(self.payments_path + '/addop')
1349        self.browser.getControl(name="form.p_category").value = ['clearance']
1350        self.browser.getControl("Create ticket").click()
1351        self.assertMatches('...ticket created...',
1352                           self.browser.contents)
1353
1354        # Managers can approve the payment
1355        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
1356        ctrl = self.browser.getControl(name='val_id')
1357        value = ctrl.options[1] # The clearance payment is the second in the table
1358        self.browser.getLink(value).click()
1359        self.browser.open(self.browser.url + '/approve')
1360        self.assertMatches('...Successful payment...',
1361                          self.browser.contents)
1362        expected = '''...
1363        <td>
1364          <span>Paid</span>
1365        </td>...'''
1366        self.assertMatches(expected,self.browser.contents)
1367        # The new CLR-0 pin has been created
1368        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
1369        pin = self.app['accesscodes']['CLR-0'].keys()[0]
1370        ac = self.app['accesscodes']['CLR-0'][pin]
1371        self.assertEqual(ac.owner, self.student_id)
1372        self.assertEqual(ac.cost, 3456.0)
1373        return
1374
1375    def test_student_payments(self):
1376        # Login
1377        self.browser.open(self.login_path)
1378        self.browser.getControl(name="form.login").value = self.student_id
1379        self.browser.getControl(name="form.password").value = 'spwd'
1380        self.browser.getControl("Login").click()
1381
1382        # Students can add online clearance payment tickets
1383        self.browser.open(self.payments_path + '/addop')
1384        self.browser.getControl(name="form.p_category").value = ['clearance']
1385        self.browser.getControl("Create ticket").click()
1386        self.assertMatches('...ticket created...',
1387                           self.browser.contents)
1388
1389        # Students can't approve the payment
1390        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
1391        ctrl = self.browser.getControl(name='val_id')
1392        value = ctrl.options[0]
1393        self.browser.getLink(value).click()
1394        payment_url = self.browser.url
1395        self.assertRaises(
1396            Unauthorized, self.browser.open, payment_url + '/approve')
1397        # In the base package they can 'use' a fake approval view
1398        self.browser.open(payment_url + '/fake_approve')
1399        self.assertMatches('...Successful payment...',
1400                          self.browser.contents)
1401        expected = '''...
1402        <td>
1403          <span>Paid</span>
1404        </td>...'''
1405        expected = '''...
1406        <td>
1407          <span>Paid</span>
1408        </td>...'''
1409        self.assertMatches(expected,self.browser.contents)
1410        payment_id = self.student['payments'].keys()[0]
1411        payment = self.student['payments'][payment_id]
1412        self.assertEqual(payment.p_state, 'paid')
1413        self.assertEqual(payment.r_amount_approved, 3456.0)
1414        self.assertEqual(payment.r_code, 'AP')
1415        self.assertEqual(payment.r_desc, u'Payment approved by Anna Tester')
1416        # The new CLR-0 pin has been created
1417        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
1418        pin = self.app['accesscodes']['CLR-0'].keys()[0]
1419        ac = self.app['accesscodes']['CLR-0'][pin]
1420        self.assertEqual(ac.owner, self.student_id)
1421        self.assertEqual(ac.cost, 3456.0)
1422
1423        # Students can open the pdf payment slip
1424        self.browser.open(payment_url + '/payment_slip.pdf')
1425        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1426        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1427
1428        # The new CLR-0 pin can be used for starting clearance
1429        # but they have to upload a passport picture first
1430        # which is only possible in state admitted
1431        self.browser.open(self.student_path + '/change_portrait')
1432        self.assertMatches('...form is locked...',
1433                          self.browser.contents)
1434        IWorkflowInfo(self.student).fireTransition('admit')
1435        self.browser.open(self.student_path + '/change_portrait')
1436        image = open(SAMPLE_IMAGE, 'rb')
1437        ctrl = self.browser.getControl(name='passportuploadedit')
1438        file_ctrl = ctrl.mech_control
1439        file_ctrl.add_file(image, filename='my_photo.jpg')
1440        self.browser.getControl(
1441            name='upload_passportuploadedit').click()
1442        self.browser.open(self.student_path + '/start_clearance')
1443        parts = pin.split('-')[1:]
1444        clrseries, clrnumber = parts
1445        self.browser.getControl(name="ac_series").value = clrseries
1446        self.browser.getControl(name="ac_number").value = clrnumber
1447        self.browser.getControl("Start clearance now").click()
1448        self.assertMatches('...Clearance process has been started...',
1449                           self.browser.contents)
1450
1451        # Students can add online school fee payment tickets.
1452        IWorkflowState(self.student).setState('returning')
1453        self.browser.open(self.payments_path)
1454        self.browser.getControl("Add online payment ticket").click()
1455        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1456        self.browser.getControl("Create ticket").click()
1457        self.assertMatches('...ticket created...',
1458                           self.browser.contents)
1459        ctrl = self.browser.getControl(name='val_id')
1460        value = ctrl.options[0]
1461        self.browser.getLink(value).click()
1462        self.assertMatches('...Amount Authorized...',
1463                           self.browser.contents)
1464        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
1465        # Payment session and will be calculated as defined
1466        # in w.k.students.utils because we set changed the state
1467        # to returning
1468        self.assertEqual(self.student['payments'][value].p_session, 2005)
1469        self.assertEqual(self.student['payments'][value].p_level, 200)
1470
1471        # We simulate the approval
1472        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
1473        self.browser.open(self.browser.url + '/fake_approve')
1474        self.assertMatches('...Successful payment...',
1475                          self.browser.contents)
1476
1477        # Students can remove only online payment tickets which have
1478        # not received a valid callback
1479        self.browser.open(self.payments_path)
1480        self.assertRaises(
1481            LookupError, self.browser.getControl, name='val_id')
1482        self.browser.open(self.payments_path + '/addop')
1483        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1484        self.browser.getControl("Create ticket").click()
1485        self.browser.open(self.payments_path)
1486        ctrl = self.browser.getControl(name='val_id')
1487        value = ctrl.options[0]
1488        ctrl.getControl(value=value).selected = True
1489        self.browser.getControl("Remove selected", index=0).click()
1490        self.assertTrue('Successfully removed' in self.browser.contents)
1491
1492        # The new SFE-0 pin can be used for starting new session
1493        self.browser.open(self.studycourse_path)
1494        self.browser.getLink('Start session').click()
1495        pin = self.app['accesscodes']['SFE-0'].keys()[0]
1496        parts = pin.split('-')[1:]
1497        sfeseries, sfenumber = parts
1498        self.browser.getControl(name="ac_series").value = sfeseries
1499        self.browser.getControl(name="ac_number").value = sfenumber
1500        self.browser.getControl("Start now").click()
1501        self.assertMatches('...Session started...',
1502                           self.browser.contents)
1503        self.assertTrue(self.student.state == 'school fee paid')
1504        return
1505
1506    def test_postgraduate_payments(self):
1507        self.certificate.study_mode = 'pg_ft'
1508        self.certificate.start_level = 999
1509        self.certificate.end_level = 999
1510        self.student['studycourse'].current_level = 999
1511        # Login
1512        self.browser.open(self.login_path)
1513        self.browser.getControl(name="form.login").value = self.student_id
1514        self.browser.getControl(name="form.password").value = 'spwd'
1515        self.browser.getControl("Login").click()
1516        # Students can add online school fee payment tickets.
1517        IWorkflowState(self.student).setState('cleared')
1518        self.browser.open(self.payments_path)
1519        self.browser.getControl("Add online payment ticket").click()
1520        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1521        self.browser.getControl("Create ticket").click()
1522        self.assertMatches('...ticket created...',
1523                           self.browser.contents)
1524        ctrl = self.browser.getControl(name='val_id')
1525        value = ctrl.options[0]
1526        self.browser.getLink(value).click()
1527        self.assertMatches('...Amount Authorized...',
1528                           self.browser.contents)
1529        # Payment session and level are current ones.
1530        # Postgrads have to school_fee_1.
1531        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
1532        self.assertEqual(self.student['payments'][value].p_session, 2004)
1533        self.assertEqual(self.student['payments'][value].p_level, 999)
1534
1535        # We simulate the approval
1536        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
1537        self.browser.open(self.browser.url + '/fake_approve')
1538        self.assertMatches('...Successful payment...',
1539                          self.browser.contents)
1540
1541        # The new SFE-0 pin can be used for starting session
1542        self.browser.open(self.studycourse_path)
1543        self.browser.getLink('Start session').click()
1544        pin = self.app['accesscodes']['SFE-0'].keys()[0]
1545        parts = pin.split('-')[1:]
1546        sfeseries, sfenumber = parts
1547        self.browser.getControl(name="ac_series").value = sfeseries
1548        self.browser.getControl(name="ac_number").value = sfenumber
1549        self.browser.getControl("Start now").click()
1550        self.assertMatches('...Session started...',
1551                           self.browser.contents)
1552        self.assertTrue(self.student.state == 'school fee paid')
1553
1554        # Postgrad students do not need to register courses the
1555        # can just pay for the next session.
1556        self.browser.open(self.payments_path)
1557        # Remove first payment to be sure that we access the right ticket
1558        del self.student['payments'][value]
1559        self.browser.getControl("Add online payment ticket").click()
1560        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1561        self.browser.getControl("Create ticket").click()
1562        ctrl = self.browser.getControl(name='val_id')
1563        value = ctrl.options[0]
1564        self.browser.getLink(value).click()
1565        # Payment session has increased by one, payment level remains the same.
1566        # Returning Postgraduates have to pay school_fee_2.
1567        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
1568        self.assertEqual(self.student['payments'][value].p_session, 2005)
1569        self.assertEqual(self.student['payments'][value].p_level, 999)
1570
1571        # Student is still in old session
1572        self.assertEqual(self.student.current_session, 2004)
1573
1574        # We do not need to pay the ticket if any other
1575        # SFE pin is provided
1576        pin_container = self.app['accesscodes']
1577        pin_container.createBatch(
1578            datetime.utcnow(), 'some_userid', 'SFE', 9.99, 1)
1579        pin = pin_container['SFE-1'].values()[0].representation
1580        sfeseries, sfenumber = pin.split('-')[1:]
1581        # The new SFE-1 pin can be used for starting new session
1582        self.browser.open(self.studycourse_path)
1583        self.browser.getLink('Start session').click()
1584        self.browser.getControl(name="ac_series").value = sfeseries
1585        self.browser.getControl(name="ac_number").value = sfenumber
1586        self.browser.getControl("Start now").click()
1587        self.assertMatches('...Session started...',
1588                           self.browser.contents)
1589        self.assertTrue(self.student.state == 'school fee paid')
1590        # Student is in new session
1591        self.assertEqual(self.student.current_session, 2005)
1592        self.assertEqual(self.student['studycourse'].current_level, 999)
1593        return
1594
1595    def test_manage_accommodation(self):
1596        # Managers can add online booking fee payment tickets and open the
1597        # callback view (see test_manage_payments)
1598        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1599        self.browser.open(self.payments_path)
1600        self.browser.getControl("Add online payment ticket").click()
1601        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1602        # If student is not in accommodation session, payment cannot be processed
1603        self.app['configuration'].accommodation_session = 2011
1604        self.browser.getControl("Create ticket").click()
1605        self.assertMatches('...Your current session does not match...',
1606                           self.browser.contents)
1607        self.app['configuration'].accommodation_session = 2004
1608        self.browser.getControl("Add online payment ticket").click()
1609        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1610        self.browser.getControl("Create ticket").click()
1611        ctrl = self.browser.getControl(name='val_id')
1612        value = ctrl.options[0]
1613        self.browser.getLink(value).click()
1614        self.browser.open(self.browser.url + '/approve')
1615        # The new HOS-0 pin has been created
1616        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
1617        pin = self.app['accesscodes']['HOS-0'].keys()[0]
1618        ac = self.app['accesscodes']['HOS-0'][pin]
1619        self.assertEqual(ac.owner, self.student_id)
1620        parts = pin.split('-')[1:]
1621        sfeseries, sfenumber = parts
1622        # Managers can use HOS code and book a bed space with it
1623        self.browser.open(self.acco_path)
1624        self.browser.getLink("Book accommodation").click()
1625        self.assertMatches('...You are in the wrong...',
1626                           self.browser.contents)
1627        IWorkflowInfo(self.student).fireTransition('admit')
1628        # An existing HOS code can only be used if students
1629        # are in accommodation session
1630        self.student['studycourse'].current_session = 2003
1631        self.browser.getLink("Book accommodation").click()
1632        self.assertMatches('...Your current session does not match...',
1633                           self.browser.contents)
1634        self.student['studycourse'].current_session = 2004
1635        # All requirements are met and ticket can be created
1636        self.browser.getLink("Book accommodation").click()
1637        self.assertMatches('...Activation Code:...',
1638                           self.browser.contents)
1639        self.browser.getControl(name="ac_series").value = sfeseries
1640        self.browser.getControl(name="ac_number").value = sfenumber
1641        self.browser.getControl("Create bed ticket").click()
1642        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1643                           self.browser.contents)
1644        # Bed has been allocated
1645        bed1 = self.app['hostels']['hall-1']['hall-1_A_101_A']
1646        self.assertTrue(bed1.owner == self.student_id)
1647        # BedTicketAddPage is now blocked
1648        self.browser.getLink("Book accommodation").click()
1649        self.assertMatches('...You already booked a bed space...',
1650            self.browser.contents)
1651        # The bed ticket displays the data correctly
1652        self.browser.open(self.acco_path + '/2004')
1653        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1654                           self.browser.contents)
1655        self.assertMatches('...2004/2005...', self.browser.contents)
1656        self.assertMatches('...regular_male_fr...', self.browser.contents)
1657        self.assertMatches('...%s...' % pin, self.browser.contents)
1658        # Managers can relocate students if the student's bed_type has changed
1659        self.browser.getLink("Relocate student").click()
1660        self.assertMatches(
1661            "...Student can't be relocated...", self.browser.contents)
1662        self.student.sex = u'f'
1663        self.browser.getLink("Relocate student").click()
1664        self.assertMatches(
1665            "...Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
1666        self.assertTrue(bed1.owner == NOT_OCCUPIED)
1667        bed2 = self.app['hostels']['hall-1']['hall-1_A_101_B']
1668        self.assertTrue(bed2.owner == self.student_id)
1669        self.assertTrue(self.student['accommodation'][
1670            '2004'].bed_type == u'regular_female_fr')
1671        # The payment object still shows the original payment item
1672        payment_id = self.student['payments'].keys()[0]
1673        payment = self.student['payments'][payment_id]
1674        self.assertTrue(payment.p_item == u'regular_male_fr')
1675        # Managers can relocate students if the bed's bed_type has changed
1676        bed1.bed_type = u'regular_female_fr'
1677        bed2.bed_type = u'regular_male_fr'
1678        notify(grok.ObjectModifiedEvent(bed1))
1679        notify(grok.ObjectModifiedEvent(bed2))
1680        self.browser.getLink("Relocate student").click()
1681        self.assertMatches(
1682            "...Student relocated...", self.browser.contents)
1683        self.assertMatches(
1684            "... Hall 1, Block A, Room 101, Bed A...", self.browser.contents)
1685        self.assertMatches(bed1.owner, self.student_id)
1686        self.assertMatches(bed2.owner, NOT_OCCUPIED)
1687        # Managers can't relocate students if bed is reserved
1688        self.student.sex = u'm'
1689        bed1.bed_type = u'regular_female_reserved'
1690        notify(grok.ObjectModifiedEvent(bed1))
1691        self.browser.getLink("Relocate student").click()
1692        self.assertMatches(
1693            "...Students in reserved beds can't be relocated...",
1694            self.browser.contents)
1695        # Managers can relocate students if booking has been cancelled but
1696        # other bed space has been manually allocated after cancellation
1697        old_owner = bed1.releaseBed()
1698        self.assertMatches(old_owner, self.student_id)
1699        bed2.owner = self.student_id
1700        self.browser.open(self.acco_path + '/2004')
1701        self.assertMatches(
1702            "...booking cancelled...", self.browser.contents)
1703        self.browser.getLink("Relocate student").click()
1704        # We didn't informed the catalog therefore the new owner is not found
1705        self.assertMatches(
1706            "...There is no free bed in your category regular_male_fr...",
1707            self.browser.contents)
1708        # Now we fire the event properly
1709        notify(grok.ObjectModifiedEvent(bed2))
1710        self.browser.getLink("Relocate student").click()
1711        self.assertMatches(
1712            "...Student relocated...", self.browser.contents)
1713        self.assertMatches(
1714            "... Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
1715          # Managers can delete bed tickets
1716        self.browser.open(self.acco_path)
1717        ctrl = self.browser.getControl(name='val_id')
1718        value = ctrl.options[0]
1719        ctrl.getControl(value=value).selected = True
1720        self.browser.getControl("Remove selected", index=0).click()
1721        self.assertMatches('...Successfully removed...', self.browser.contents)
1722        # The bed has been properly released by the event handler
1723        self.assertMatches(bed1.owner, NOT_OCCUPIED)
1724        self.assertMatches(bed2.owner, NOT_OCCUPIED)
1725        return
1726
1727    def test_student_accommodation(self):
1728        # Login
1729        self.browser.open(self.login_path)
1730        self.browser.getControl(name="form.login").value = self.student_id
1731        self.browser.getControl(name="form.password").value = 'spwd'
1732        self.browser.getControl("Login").click()
1733
1734        # Students can add online booking fee payment tickets and open the
1735        # callback view (see test_manage_payments)
1736        self.browser.getLink("Payments").click()
1737        self.browser.getControl("Add online payment ticket").click()
1738        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1739        self.browser.getControl("Create ticket").click()
1740        ctrl = self.browser.getControl(name='val_id')
1741        value = ctrl.options[0]
1742        self.browser.getLink(value).click()
1743        self.browser.open(self.browser.url + '/fake_approve')
1744        # The new HOS-0 pin has been created
1745        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
1746        pin = self.app['accesscodes']['HOS-0'].keys()[0]
1747        ac = self.app['accesscodes']['HOS-0'][pin]
1748        parts = pin.split('-')[1:]
1749        sfeseries, sfenumber = parts
1750
1751        # Students can use HOS code and book a bed space with it
1752        self.browser.open(self.acco_path)
1753        self.browser.getLink("Book accommodation").click()
1754        self.assertMatches('...You are in the wrong...',
1755                           self.browser.contents)
1756        IWorkflowInfo(self.student).fireTransition('admit')
1757        self.browser.getLink("Book accommodation").click()
1758        self.assertMatches('...Activation Code:...',
1759                           self.browser.contents)
1760        self.browser.getControl(name="ac_series").value = u'nonsense'
1761        self.browser.getControl(name="ac_number").value = sfenumber
1762        self.browser.getControl("Create bed ticket").click()
1763        self.assertMatches('...Activation code is invalid...',
1764                           self.browser.contents)
1765        ac.owner = u'Anybody'
1766        self.browser.getControl(name="ac_series").value = sfeseries
1767        self.browser.getControl(name="ac_number").value = sfenumber
1768        self.browser.getControl("Create bed ticket").click()
1769        self.assertMatches('...You are not the owner of this access code...',
1770                           self.browser.contents)
1771        ac.owner = self.student_id
1772        self.browser.getControl(name="ac_series").value = sfeseries
1773        self.browser.getControl(name="ac_number").value = sfenumber
1774        self.browser.getControl("Create bed ticket").click()
1775        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1776                           self.browser.contents)
1777
1778        # Bed has been allocated
1779        bed = self.app['hostels']['hall-1']['hall-1_A_101_A']
1780        self.assertTrue(bed.owner == self.student_id)
1781
1782        # BedTicketAddPage is now blocked
1783        self.browser.getLink("Book accommodation").click()
1784        self.assertMatches('...You already booked a bed space...',
1785            self.browser.contents)
1786
1787        # The bed ticket displays the data correctly
1788        self.browser.open(self.acco_path + '/2004')
1789        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1790                           self.browser.contents)
1791        self.assertMatches('...2004/2005...', self.browser.contents)
1792        self.assertMatches('...regular_male_fr...', self.browser.contents)
1793        self.assertMatches('...%s...' % pin, self.browser.contents)
1794
1795        # Students can open the pdf slip
1796        self.browser.open(self.browser.url + '/bed_allocation.pdf')
1797        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1798        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1799
1800        # Students can't relocate themselves
1801        self.assertFalse('Relocate' in self.browser.contents)
1802        relocate_path = self.acco_path + '/2004/relocate'
1803        self.assertRaises(
1804            Unauthorized, self.browser.open, relocate_path)
1805
1806        # Students can't the Remove button and check boxes
1807        self.browser.open(self.acco_path)
1808        self.assertFalse('Remove' in self.browser.contents)
1809        self.assertFalse('val_id' in self.browser.contents)
1810        return
1811
1812    def test_change_password_request(self):
1813        self.browser.open('http://localhost/app/sendpw')
1814        self.browser.getControl(name="form.identifier").value = '123'
1815        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
1816        self.browser.getControl("Get login credentials").click()
1817        self.assertTrue('An email with' in self.browser.contents)
1818
1819    def test_reindex(self):
1820        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1821        self.browser.open('http://localhost/app/reindex')
1822        self.assertTrue('No catalog name provided' in self.browser.contents)
1823        self.browser.open('http://localhost/app/reindex?ctlg=xyz')
1824        self.assertTrue('xyz_catalog does not exist' in self.browser.contents)
1825        cat = queryUtility(ICatalog, name='students_catalog')
1826        results = cat.searchResults(student_id=(None, None))
1827        self.assertEqual(len(results),1)
1828        cat.clear()
1829        results = cat.searchResults(student_id=(None, None))
1830        self.assertEqual(len(results),0)
1831        self.browser.open('http://localhost/app/reindex?ctlg=students')
1832        self.assertTrue('1 students re-indexed' in self.browser.contents)
1833        results = cat.searchResults(student_id=(None, None))
1834        self.assertEqual(len(results),1)
1835
1836    def test_change_current_mode(self):
1837        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1838        self.browser.open(self.clearance_path)
1839        self.assertFalse('Employer' in self.browser.contents)
1840        self.browser.open(self.manage_clearance_path)
1841        self.assertFalse('Employer' in self.browser.contents)
1842        self.student.clearance_locked = False
1843        self.browser.open(self.edit_clearance_path)
1844        self.assertFalse('Employer' in self.browser.contents)
1845        # Now we change the study mode of the certificate and a different
1846        # interface is used by clearance views.
1847        self.certificate.study_mode = 'pg_ft'
1848        # Invariants are not being checked here?!
1849        self.certificate.end_level = 100
1850        self.browser.open(self.clearance_path)
1851        self.assertTrue('Employer' in self.browser.contents)
1852        self.browser.open(self.manage_clearance_path)
1853        self.assertTrue('Employer' in self.browser.contents)
1854        self.browser.open(self.edit_clearance_path)
1855        self.assertTrue('Employer' in self.browser.contents)
Note: See TracBrowser for help on using the repository browser.