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

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

Add more attributes to webservice adapter.

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