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

Last change on this file since 10258 was 10258, checked in by Henrik Bettermann, 11 years ago

Ensure that all exports are being discarded in tests.

  • Property svn:keywords set to Id
File size: 161.4 KB
Line 
1## $Id: test_browser.py 10258 2013-05-30 21:37:46Z 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
24import base64
25from datetime import datetime, timedelta, date
26from StringIO import StringIO
27import os
28import grok
29from zc.async.testing import wait_for_result
30from zope.event import notify
31from zope.component import createObject, queryUtility, getUtility
32from zope.component.hooks import setSite, clearSite
33from zope.catalog.interfaces import ICatalog
34from zope.security.interfaces import Unauthorized
35from zope.securitypolicy.interfaces import IPrincipalRoleManager
36from zope.testbrowser.testing import Browser
37from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
38from waeup.kofa.testing import FunctionalLayer, FunctionalTestCase
39from waeup.kofa.app import University
40from waeup.kofa.payments.interfaces import IPayer
41from waeup.kofa.students.interfaces import IStudentsUtils
42from waeup.kofa.students.student import Student
43from waeup.kofa.students.studylevel import StudentStudyLevel
44from waeup.kofa.university.faculty import Faculty
45from waeup.kofa.university.department import Department
46from waeup.kofa.interfaces import IUserAccount, IJobManager
47from waeup.kofa.authentication import LocalRoleSetEvent
48from waeup.kofa.hostels.hostel import Hostel, Bed, NOT_OCCUPIED
49from waeup.kofa.tests.test_async import FunctionalAsyncTestCase
50from waeup.kofa.browser.tests.test_pdf import samples_dir
51
52PH_LEN = 15911  # Length of placeholder file
53
54SAMPLE_IMAGE = os.path.join(os.path.dirname(__file__), 'test_image.jpg')
55SAMPLE_IMAGE_BMP = os.path.join(os.path.dirname(__file__), 'test_image.bmp')
56
57def lookup_submit_value(name, value, browser):
58    """Find a button with a certain value."""
59    for num in range(0, 100):
60        try:
61            button = browser.getControl(name=name, index=num)
62            if button.value.endswith(value):
63                return button
64        except IndexError:
65            break
66    return None
67
68class StudentsFullSetup(FunctionalTestCase):
69    # A test case that only contains a setup and teardown
70    #
71    # Complete setup for students handlings is rather complex and
72    # requires lots of things created before we can start. This is a
73    # setup that does all this, creates a university, creates PINs,
74    # etc.  so that we do not have to bother with that in different
75    # test cases.
76
77    layer = FunctionalLayer
78
79    def setUp(self):
80        super(StudentsFullSetup, self).setUp()
81
82        # Setup a sample site for each test
83        app = University()
84        self.dc_root = tempfile.mkdtemp()
85        app['datacenter'].setStoragePath(self.dc_root)
86
87        # Prepopulate the ZODB...
88        self.getRootFolder()['app'] = app
89        # we add the site immediately after creation to the
90        # ZODB. Catalogs and other local utilities are not setup
91        # before that step.
92        self.app = self.getRootFolder()['app']
93        # Set site here. Some of the following setup code might need
94        # to access grok.getSite() and should get our new app then
95        setSite(app)
96
97        # Add student with subobjects
98        student = createObject('waeup.Student')
99        student.firstname = u'Anna'
100        student.lastname = u'Tester'
101        student.reg_number = u'123'
102        student.matric_number = u'234'
103        student.sex = u'm'
104        student.email = 'aa@aa.ng'
105        student.phone = u'1234'
106        self.app['students'].addStudent(student)
107        self.student_id = student.student_id
108        self.student = self.app['students'][self.student_id]
109
110        # Set password
111        IUserAccount(
112            self.app['students'][self.student_id]).setPassword('spwd')
113
114        self.login_path = 'http://localhost/app/login'
115        self.container_path = 'http://localhost/app/students'
116        self.manage_container_path = self.container_path + '/@@manage'
117        self.add_student_path = self.container_path + '/addstudent'
118        self.student_path = self.container_path + '/' + self.student_id
119        self.manage_student_path = self.student_path + '/manage_base'
120        self.trigtrans_path = self.student_path + '/trigtrans'
121        self.clearance_path = self.student_path + '/view_clearance'
122        self.personal_path = self.student_path + '/view_personal'
123        self.edit_clearance_path = self.student_path + '/cedit'
124        self.manage_clearance_path = self.student_path + '/manage_clearance'
125        self.edit_personal_path = self.student_path + '/edit_personal'
126        self.manage_personal_path = self.student_path + '/manage_personal'
127        self.studycourse_path = self.student_path + '/studycourse'
128        self.payments_path = self.student_path + '/payments'
129        self.acco_path = self.student_path + '/accommodation'
130        self.history_path = self.student_path + '/history'
131
132        # Create 5 access codes with prefix'PWD'
133        pin_container = self.app['accesscodes']
134        pin_container.createBatch(
135            datetime.utcnow(), 'some_userid', 'PWD', 9.99, 5)
136        pins = pin_container['PWD-1'].values()
137        self.pwdpins = [x.representation for x in pins]
138        self.existing_pwdpin = self.pwdpins[0]
139        parts = self.existing_pwdpin.split('-')[1:]
140        self.existing_pwdseries, self.existing_pwdnumber = parts
141        # Create 5 access codes with prefix 'CLR'
142        pin_container.createBatch(
143            datetime.now(), 'some_userid', 'CLR', 9.99, 5)
144        pins = pin_container['CLR-1'].values()
145        pins[0].owner = u'Hans Wurst'
146        self.existing_clrac = pins[0]
147        self.existing_clrpin = pins[0].representation
148        parts = self.existing_clrpin.split('-')[1:]
149        self.existing_clrseries, self.existing_clrnumber = parts
150        # Create 2 access codes with prefix 'HOS'
151        pin_container.createBatch(
152            datetime.now(), 'some_userid', 'HOS', 9.99, 2)
153        pins = pin_container['HOS-1'].values()
154        self.existing_hosac = pins[0]
155        self.existing_hospin = pins[0].representation
156        parts = self.existing_hospin.split('-')[1:]
157        self.existing_hosseries, self.existing_hosnumber = parts
158
159        # Populate university
160        self.certificate = createObject('waeup.Certificate')
161        self.certificate.code = u'CERT1'
162        self.certificate.application_category = 'basic'
163        self.certificate.study_mode = 'ug_ft'
164        self.certificate.start_level = 100
165        self.certificate.end_level = 500
166        self.certificate.school_fee_1 = 40000.0
167        self.certificate.school_fee_2 = 20000.0
168        self.app['faculties']['fac1'] = Faculty(code=u'fac1')
169        self.app['faculties']['fac1']['dep1'] = Department(code=u'dep1')
170        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
171            self.certificate)
172        self.course = createObject('waeup.Course')
173        self.course.code = 'COURSE1'
174        self.course.semester = 1
175        self.course.credits = 10
176        self.course.passmark = 40
177        self.app['faculties']['fac1']['dep1'].courses.addCourse(
178            self.course)
179        self.app['faculties']['fac1']['dep1'].certificates['CERT1'].addCertCourse(
180            self.course, level=100)
181
182        # Configure university and hostels
183        self.app['hostels'].accommodation_states = ['admitted']
184        self.app['hostels'].accommodation_session = 2004
185        delta = timedelta(days=10)
186        self.app['hostels'].startdate = datetime.now(pytz.utc) - delta
187        self.app['hostels'].enddate = datetime.now(pytz.utc) + delta
188        self.app['configuration'].carry_over = True
189        configuration = createObject('waeup.SessionConfiguration')
190        configuration.academic_session = 2004
191        configuration.clearance_fee = 3456.0
192        configuration.booking_fee = 123.4
193        configuration.maint_fee = 987.0
194        self.app['configuration'].addSessionConfiguration(configuration)
195
196        # Create a hostel with two beds
197        hostel = Hostel()
198        hostel.hostel_id = u'hall-1'
199        hostel.hostel_name = u'Hall 1'
200        self.app['hostels'].addHostel(hostel)
201        bed = Bed()
202        bed.bed_id = u'hall-1_A_101_A'
203        bed.bed_number = 1
204        bed.owner = NOT_OCCUPIED
205        bed.bed_type = u'regular_male_fr'
206        self.app['hostels'][hostel.hostel_id].addBed(bed)
207        bed = Bed()
208        bed.bed_id = u'hall-1_A_101_B'
209        bed.bed_number = 2
210        bed.owner = NOT_OCCUPIED
211        bed.bed_type = u'regular_female_fr'
212        self.app['hostels'][hostel.hostel_id].addBed(bed)
213
214        # Set study course attributes of test student
215        self.student['studycourse'].certificate = self.certificate
216        self.student['studycourse'].current_session = 2004
217        self.student['studycourse'].entry_session = 2004
218        self.student['studycourse'].current_verdict = 'A'
219        self.student['studycourse'].current_level = 100
220        # Update the catalog
221        notify(grok.ObjectModifiedEvent(self.student))
222
223        # Put the prepopulated site into test ZODB and prepare test
224        # browser
225        self.browser = Browser()
226        self.browser.handleErrors = False
227
228    def tearDown(self):
229        super(StudentsFullSetup, self).tearDown()
230        clearSite()
231        shutil.rmtree(self.dc_root)
232
233
234
235class StudentsContainerUITests(StudentsFullSetup):
236    # Tests for StudentsContainer class views and pages
237
238    layer = FunctionalLayer
239
240    def test_anonymous_access(self):
241        # Anonymous users can't access students containers
242        self.assertRaises(
243            Unauthorized, self.browser.open, self.container_path)
244        self.assertRaises(
245            Unauthorized, self.browser.open, self.manage_container_path)
246        return
247
248    def test_manage_access(self):
249        # Managers can access the view page of students
250        # containers and can perform actions
251        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
252        self.browser.open(self.container_path)
253        self.assertEqual(self.browser.headers['Status'], '200 Ok')
254        self.assertEqual(self.browser.url, self.container_path)
255        self.browser.getLink("Manage student section").click()
256        self.assertEqual(self.browser.headers['Status'], '200 Ok')
257        self.assertEqual(self.browser.url, self.manage_container_path)
258        return
259
260    def test_add_search_delete_students(self):
261        # Managers can add search and remove students
262        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
263        self.browser.open(self.manage_container_path)
264        self.browser.getLink("Add student").click()
265        self.assertEqual(self.browser.headers['Status'], '200 Ok')
266        self.assertEqual(self.browser.url, self.add_student_path)
267        self.browser.getControl(name="form.firstname").value = 'Bob'
268        self.browser.getControl(name="form.lastname").value = 'Tester'
269        self.browser.getControl(name="form.reg_number").value = '123'
270        self.browser.getControl("Create student record").click()
271        self.assertTrue('Registration number exists already'
272            in self.browser.contents)
273        self.browser.getControl(name="form.reg_number").value = '1234'
274        self.browser.getControl("Create student record").click()
275        self.assertTrue('Student record created' in self.browser.contents)
276
277        # Registration and matric numbers must be unique
278        self.browser.getLink("Manage").click()
279        self.browser.getControl(name="form.reg_number").value = '123'
280        self.browser.getControl("Save").click()
281        self.assertMatches('...Registration number exists...',
282                           self.browser.contents)
283        self.browser.getControl(name="form.reg_number").value = '789'
284        self.browser.getControl(name="form.matric_number").value = '234'
285        self.browser.getControl("Save").click()
286        self.assertMatches('...Matriculation number exists...',
287                           self.browser.contents)
288
289        # We can find a student with a certain student_id
290        self.browser.open(self.container_path)
291        self.browser.getControl("Search").click()
292        self.assertTrue('Empty search string' in self.browser.contents)
293        self.browser.getControl(name="searchtype").value = ['student_id']
294        self.browser.getControl(name="searchterm").value = self.student_id
295        self.browser.getControl("Search").click()
296        self.assertTrue('Anna Tester' in self.browser.contents)
297
298        # We can find a student in a certain session
299        self.browser.open(self.container_path)
300        self.browser.getControl(name="searchtype").value = ['current_session']
301        self.browser.getControl(name="searchterm").value = '2004'
302        self.browser.getControl("Search").click()
303        self.assertTrue('Anna Tester' in self.browser.contents)
304        # Session fileds require integer values
305        self.browser.open(self.container_path)
306        self.browser.getControl(name="searchtype").value = ['current_session']
307        self.browser.getControl(name="searchterm").value = '2004/2005'
308        self.browser.getControl("Search").click()
309        self.assertTrue('Only year dates allowed' in self.browser.contents)
310        self.browser.open(self.manage_container_path)
311        self.browser.getControl(name="searchtype").value = ['current_session']
312        self.browser.getControl(name="searchterm").value = '2004/2005'
313        self.browser.getControl("Search").click()
314        self.assertTrue('Only year dates allowed' in self.browser.contents)
315
316        # We can find a student in a certain study_mode
317        self.browser.open(self.container_path)
318        self.browser.getControl(name="searchtype").value = ['current_mode']
319        self.browser.getControl(name="searchterm").value = 'ug_ft'
320        self.browser.getControl("Search").click()
321        self.assertTrue('Anna Tester' in self.browser.contents)
322
323        # We can find a student in a certain department
324        self.browser.open(self.container_path)
325        self.browser.getControl(name="searchtype").value = ['depcode']
326        self.browser.getControl(name="searchterm").value = 'dep1'
327        self.browser.getControl("Search").click()
328        self.assertTrue('Anna Tester' in self.browser.contents)
329
330        # We can find a student by searching for all kind of name parts
331        self.browser.open(self.manage_container_path)
332        self.browser.getControl("Search").click()
333        self.assertTrue('Empty search string' in self.browser.contents)
334        self.browser.getControl(name="searchtype").value = ['fullname']
335        self.browser.getControl(name="searchterm").value = 'Anna Tester'
336        self.browser.getControl("Search").click()
337        self.assertTrue('Anna Tester' in self.browser.contents)
338        self.browser.open(self.manage_container_path)
339        self.browser.getControl(name="searchtype").value = ['fullname']
340        self.browser.getControl(name="searchterm").value = 'Anna'
341        self.browser.getControl("Search").click()
342        self.assertTrue('Anna Tester' in self.browser.contents)
343        self.browser.open(self.manage_container_path)
344        self.browser.getControl(name="searchtype").value = ['fullname']
345        self.browser.getControl(name="searchterm").value = 'Tester'
346        self.browser.getControl("Search").click()
347        self.assertTrue('Anna Tester' in self.browser.contents)
348        self.browser.open(self.manage_container_path)
349        self.browser.getControl(name="searchtype").value = ['fullname']
350        self.browser.getControl(name="searchterm").value = 'An'
351        self.browser.getControl("Search").click()
352        self.assertFalse('Anna Tester' in self.browser.contents)
353        self.browser.open(self.manage_container_path)
354        self.browser.getControl(name="searchtype").value = ['fullname']
355        self.browser.getControl(name="searchterm").value = 'An*'
356        self.browser.getControl("Search").click()
357        self.assertTrue('Anna Tester' in self.browser.contents)
358        self.browser.open(self.manage_container_path)
359        self.browser.getControl(name="searchtype").value = ['fullname']
360        self.browser.getControl(name="searchterm").value = 'tester'
361        self.browser.getControl("Search").click()
362        self.assertTrue('Anna Tester' in self.browser.contents)
363        self.browser.open(self.manage_container_path)
364        self.browser.getControl(name="searchtype").value = ['fullname']
365        self.browser.getControl(name="searchterm").value = 'Tester Ana'
366        self.browser.getControl("Search").click()
367        self.assertFalse('Anna Tester' in self.browser.contents)
368        self.browser.open(self.manage_container_path)
369        self.browser.getControl(name="searchtype").value = ['fullname']
370        self.browser.getControl(name="searchterm").value = 'Tester Anna'
371        self.browser.getControl("Search").click()
372        self.assertTrue('Anna Tester' in self.browser.contents)
373        # The old searchterm will be used again
374        self.browser.getControl("Search").click()
375        self.assertTrue('Anna Tester' in self.browser.contents)
376
377        # We can find suspended students
378        self.student.suspended = True
379        notify(grok.ObjectModifiedEvent(self.student))
380        self.browser.open(self.manage_container_path)
381        self.browser.getControl(name="searchtype").value = ['suspended']
382        self.browser.getControl("Search").click()
383        self.assertTrue('Anna Tester' in self.browser.contents)
384        self.browser.open(self.container_path)
385        self.browser.getControl(name="searchtype").value = ['suspended']
386        self.browser.getControl("Search").click()
387        self.assertTrue('Anna Tester' in self.browser.contents)
388
389        # The catalog is informed when studycourse objects have been
390        # edited
391        self.browser.open(self.studycourse_path + '/manage')
392        self.browser.getControl(name="form.current_session").value = ['2010']
393        self.browser.getControl(name="form.entry_session").value = ['2010']
394        self.browser.getControl(name="form.entry_mode").value = ['ug_ft']
395        self.browser.getControl("Save").click()
396
397        # We can find the student in the new session
398        self.browser.open(self.manage_container_path)
399        self.browser.getControl(name="searchtype").value = ['current_session']
400        self.browser.getControl(name="searchterm").value = '2010'
401        self.browser.getControl("Search").click()
402        self.assertTrue('Anna Tester' in self.browser.contents)
403
404        ctrl = self.browser.getControl(name='entries')
405        ctrl.getControl(value=self.student_id).selected = True
406        self.browser.getControl("Remove selected", index=0).click()
407        self.assertTrue('Successfully removed' in self.browser.contents)
408        self.browser.getControl(name="searchtype").value = ['student_id']
409        self.browser.getControl(name="searchterm").value = self.student_id
410        self.browser.getControl("Search").click()
411        self.assertTrue('No student found' in self.browser.contents)
412
413        self.browser.open(self.container_path)
414        self.browser.getControl(name="searchtype").value = ['student_id']
415        self.browser.getControl(name="searchterm").value = self.student_id
416        self.browser.getControl("Search").click()
417        self.assertTrue('No student found' in self.browser.contents)
418        return
419
420class OfficerUITests(StudentsFullSetup):
421    # Tests for Student class views and pages
422
423    def test_student_properties(self):
424        self.student['studycourse'].current_level = 100
425        self.assertEqual(self.student.current_level, 100)
426        self.student['studycourse'].current_session = 2011
427        self.assertEqual(self.student.current_session, 2011)
428        self.student['studycourse'].current_verdict = 'A'
429        self.assertEqual(self.student.current_verdict, 'A')
430        return
431
432    def test_studylevelmanagepage(self):
433        studylevel = StudentStudyLevel()
434        studylevel.level = 100
435        cert = self.app['faculties']['fac1']['dep1'].certificates['CERT1']
436        self.student['studycourse'].addStudentStudyLevel(
437            cert,studylevel)
438        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
439        self.browser.open(self.studycourse_path + '/100/manage')
440        self.assertEqual(self.browser.url, self.studycourse_path + '/100/manage')
441        self.assertEqual(self.browser.headers['Status'], '200 Ok')
442
443    def test_basic_auth(self):
444        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
445        self.browser.open('http://localhost/app')
446        self.browser.getLink("Logout").click()
447        self.assertTrue('You have been logged out' in self.browser.contents)
448        # But we are still logged in since we've used basic authentication here.
449        # Wikipedia says: Existing browsers retain authentication information
450        # until the tab or browser is closed or the user clears the history.
451        # HTTP does not provide a method for a server to direct clients to
452        # discard these cached credentials. This means that there is no
453        # effective way for a server to "log out" the user without closing
454        # the browser. This is a significant defect that requires browser
455        # manufacturers to support a "logout" user interface element ...
456        self.assertTrue('Manager' in self.browser.contents)
457
458    def test_basic_auth_base64(self):
459        auth_token = base64.b64encode('mgr:mgrpw')
460        self.browser.addHeader('Authorization', 'Basic %s' % auth_token)
461        self.browser.open(self.manage_container_path)
462        self.assertEqual(self.browser.headers['Status'], '200 Ok')
463
464    def test_manage_access(self):
465        # Managers can access the pages of students
466        # and can perform actions
467        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
468        self.browser.open(self.student_path)
469        self.assertEqual(self.browser.headers['Status'], '200 Ok')
470        self.assertEqual(self.browser.url, self.student_path)
471        self.browser.getLink("Trigger").click()
472        self.assertEqual(self.browser.headers['Status'], '200 Ok')
473        # Managers can trigger transitions
474        self.browser.getControl(name="transition").value = ['admit']
475        self.browser.getControl("Save").click()
476        # Managers can edit base
477        self.browser.open(self.student_path)
478        self.browser.getLink("Manage").click()
479        self.assertEqual(self.browser.url, self.manage_student_path)
480        self.assertEqual(self.browser.headers['Status'], '200 Ok')
481        self.browser.getControl(name="form.firstname").value = 'John'
482        self.browser.getControl(name="form.lastname").value = 'Tester'
483        self.browser.getControl(name="form.reg_number").value = '345'
484        self.browser.getControl(name="password").value = 'secret'
485        self.browser.getControl(name="control_password").value = 'secret'
486        self.browser.getControl("Save").click()
487        self.assertMatches('...Form has been saved...',
488                           self.browser.contents)
489        self.browser.open(self.student_path)
490        self.browser.getLink("Clearance Data").click()
491        self.assertEqual(self.browser.headers['Status'], '200 Ok')
492        self.assertEqual(self.browser.url, self.clearance_path)
493        self.browser.getLink("Manage").click()
494        self.assertEqual(self.browser.headers['Status'], '200 Ok')
495        self.assertEqual(self.browser.url, self.manage_clearance_path)
496        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
497        self.browser.getControl("Save").click()
498        self.assertMatches('...Form has been saved...',
499                           self.browser.contents)
500
501        self.browser.open(self.student_path)
502        self.browser.getLink("Personal Data").click()
503        self.assertEqual(self.browser.headers['Status'], '200 Ok')
504        self.assertEqual(self.browser.url, self.personal_path)
505        self.browser.getLink("Manage").click()
506        self.assertEqual(self.browser.headers['Status'], '200 Ok')
507        self.assertEqual(self.browser.url, self.manage_personal_path)
508        self.browser.open(self.personal_path)
509        self.assertTrue('Updated' in self.browser.contents)
510        self.browser.getLink("Edit").click()
511        self.assertEqual(self.browser.headers['Status'], '200 Ok')
512        self.assertEqual(self.browser.url, self.edit_personal_path)
513        self.browser.getControl("Save").click()
514        # perm_address is required in IStudentPersonalEdit
515        self.assertMatches('...Required input is missing...',
516                           self.browser.contents)
517        self.browser.getControl(name="form.perm_address").value = 'My address!'
518        self.browser.getControl("Save").click()
519        self.assertMatches('...Form has been saved...',
520                           self.browser.contents)
521
522        # Managers can browse all subobjects
523        self.browser.open(self.student_path)
524        self.browser.getLink("Payments").click()
525        self.assertEqual(self.browser.headers['Status'], '200 Ok')
526        self.assertEqual(self.browser.url, self.payments_path)
527        self.browser.open(self.student_path)
528        self.browser.getLink("Accommodation").click()
529        self.assertEqual(self.browser.headers['Status'], '200 Ok')
530        self.assertEqual(self.browser.url, self.acco_path)
531        self.browser.open(self.student_path)
532        self.browser.getLink("History").click()
533        self.assertEqual(self.browser.headers['Status'], '200 Ok')
534        self.assertEqual(self.browser.url, self.history_path)
535        self.assertMatches('...Admitted by Manager...',
536                           self.browser.contents)
537        # Only the Application Slip does not exist
538        self.assertFalse('Application Slip' in self.browser.contents)
539        return
540
541    def test_manage_contact_student(self):
542        # Managers can contact student
543        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
544        self.student.email = None
545        self.browser.open(self.student_path)
546        self.browser.getLink("Send email").click()
547        self.browser.getControl(name="form.subject").value = 'Important subject'
548        self.browser.getControl(name="form.body").value = 'Hello!'
549        self.browser.getControl("Send message now").click()
550        self.assertTrue('An smtp server error occurred' in self.browser.contents)
551        self.student.email = 'xx@yy.zz'
552        self.browser.getControl("Send message now").click()
553        self.assertTrue('Your message has been sent' in self.browser.contents)
554        return
555
556    def test_manage_remove_department(self):
557        # Lazy student is studying CERT1
558        lazystudent = Student()
559        lazystudent.firstname = u'Lazy'
560        lazystudent.lastname = u'Student'
561        self.app['students'].addStudent(lazystudent)
562        student_id = lazystudent.student_id
563        student_path = self.container_path + '/' + student_id
564        lazystudent['studycourse'].certificate = self.certificate
565        notify(grok.ObjectModifiedEvent(lazystudent))
566        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
567        self.browser.open(student_path + '/studycourse')
568        self.assertTrue('CERT1' in self.browser.contents)
569        # After some years the department is removed
570        del self.app['faculties']['fac1']['dep1']
571        # So CERT1 does no longer exist and lazy student's
572        # certificate reference is removed too
573        self.browser.open(student_path + '/studycourse')
574        self.assertEqual(self.browser.headers['Status'], '200 Ok')
575        self.assertEqual(self.browser.url, student_path + '/studycourse')
576        self.assertFalse('CERT1' in self.browser.contents)
577        self.assertMatches('...<div>--</div>...',
578                           self.browser.contents)
579
580    def test_manage_upload_file(self):
581        # Managers can upload a file via the StudentClearanceManageFormPage
582        # The image is stored even if form has errors
583        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
584        self.browser.open(self.manage_clearance_path)
585        # No birth certificate has been uploaded yet
586        # Browsing the link shows a placerholder image
587        self.browser.open('birth_certificate')
588        self.assertEqual(
589            self.browser.headers['content-type'], 'image/jpeg')
590        self.assertEqual(len(self.browser.contents), PH_LEN)
591        # Create a pseudo image file and select it to be uploaded in form
592        # as birth certificate
593        self.browser.open(self.manage_clearance_path)
594        image = open(SAMPLE_IMAGE, 'rb')
595        ctrl = self.browser.getControl(name='birthcertificateupload')
596        file_ctrl = ctrl.mech_control
597        file_ctrl.add_file(image, filename='my_birth_certificate.jpg')
598        # The Save action does not upload files
599        self.browser.getControl("Save").click() # submit form
600        self.assertFalse(
601            '<a target="image" href="birth_certificate">'
602            in self.browser.contents)
603        # ... but the correct upload submit button does
604        image = open(SAMPLE_IMAGE)
605        ctrl = self.browser.getControl(name='birthcertificateupload')
606        file_ctrl = ctrl.mech_control
607        file_ctrl.add_file(image, filename='my_birth_certificate.jpg')
608        self.browser.getControl(
609            name='upload_birthcertificateupload').click()
610        # There is a correct <img> link included
611        self.assertTrue(
612            '<a target="image" href="birth_certificate">'
613            in self.browser.contents)
614        # Browsing the link shows a real image
615        self.browser.open('birth_certificate')
616        self.assertEqual(
617            self.browser.headers['content-type'], 'image/jpeg')
618        self.assertEqual(len(self.browser.contents), 2787)
619        # Reuploading a file which is bigger than 150k will raise an error
620        self.browser.open(self.manage_clearance_path)
621        # An image > 150K
622        big_image = StringIO(open(SAMPLE_IMAGE, 'rb').read() * 75)
623        ctrl = self.browser.getControl(name='birthcertificateupload')
624        file_ctrl = ctrl.mech_control
625        file_ctrl.add_file(big_image, filename='my_birth_certificate.jpg')
626        self.browser.getControl(
627            name='upload_birthcertificateupload').click()
628        self.assertTrue(
629            'Uploaded file is too big' in self.browser.contents)
630        # we do not rely on filename extensions given by uploaders
631        image = open(SAMPLE_IMAGE, 'rb') # a jpg-file
632        ctrl = self.browser.getControl(name='birthcertificateupload')
633        file_ctrl = ctrl.mech_control
634        # tell uploaded file is bmp
635        file_ctrl.add_file(image, filename='my_birth_certificate.bmp')
636        self.browser.getControl(
637            name='upload_birthcertificateupload').click()
638        self.assertTrue(
639            # jpg file was recognized
640            'File birth_certificate.jpg uploaded.' in self.browser.contents)
641        # File names must meet several conditions
642        bmp_image = open(SAMPLE_IMAGE_BMP, 'rb')
643        ctrl = self.browser.getControl(name='birthcertificateupload')
644        file_ctrl = ctrl.mech_control
645        file_ctrl.add_file(bmp_image, filename='my_birth_certificate.bmp')
646        self.browser.getControl(
647            name='upload_birthcertificateupload').click()
648        self.assertTrue('Only the following extensions are allowed'
649            in self.browser.contents)
650        # Managers can delete files
651        self.browser.getControl(name='delete_birthcertificateupload').click()
652        self.assertTrue(
653            'birth_certificate deleted' in self.browser.contents)
654
655        # Managers can upload a file via the StudentBaseManageFormPage
656        self.browser.open(self.manage_student_path)
657        image = open(SAMPLE_IMAGE_BMP, 'rb')
658        ctrl = self.browser.getControl(name='passportuploadmanage')
659        file_ctrl = ctrl.mech_control
660        file_ctrl.add_file(image, filename='my_photo.bmp')
661        self.browser.getControl(
662            name='upload_passportuploadmanage').click()
663        self.assertTrue('jpg file extension expected'
664            in self.browser.contents)
665        ctrl = self.browser.getControl(name='passportuploadmanage')
666        file_ctrl = ctrl.mech_control
667        image = open(SAMPLE_IMAGE, 'rb')
668        file_ctrl.add_file(image, filename='my_photo.jpg')
669        self.browser.getControl(
670            name='upload_passportuploadmanage').click()
671        self.assertTrue(
672            '<img align="middle" height="125px" src="passport.jpg" />'
673            in self.browser.contents)
674        # We remove the passport file again
675        self.browser.open(self.manage_student_path)
676        self.browser.getControl('Delete').click()
677        self.browser.open(self.student_path + '/clearance_slip.pdf')
678        self.assertEqual(self.browser.headers['Status'], '200 Ok')
679        self.assertEqual(self.browser.headers['Content-Type'],
680                         'application/pdf')
681
682    def test_manage_course_lists(self):
683        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
684        self.browser.open(self.student_path)
685        self.browser.getLink("Study Course").click()
686        self.assertEqual(self.browser.headers['Status'], '200 Ok')
687        self.assertEqual(self.browser.url, self.studycourse_path)
688        self.assertTrue('Undergraduate Full-Time' in self.browser.contents)
689        self.browser.getLink("Manage").click()
690        self.assertTrue('Manage study course' in self.browser.contents)
691        # Before we can select a level, the certificate must
692        # be selected and saved
693        self.browser.getControl(name="form.certificate").value = ['CERT1']
694        self.browser.getControl(name="form.current_session").value = ['2004']
695        self.browser.getControl(name="form.current_verdict").value = ['A']
696        self.browser.getControl(name="form.entry_mode").value = ['ug_ft']
697        self.browser.getControl("Save").click()
698        # Now we can save also the current level which depends on start and end
699        # level of the certificate
700        self.browser.getControl(name="form.current_level").value = ['100']
701        self.browser.getControl("Save").click()
702        # Managers can add and remove any study level (course list)
703        self.browser.getControl(name="addlevel").value = ['100']
704        self.browser.getControl("Add study level").click()
705        self.assertMatches(
706            '...You must select a session...', self.browser.contents)
707        self.browser.getControl(name="addlevel").value = ['100']
708        self.browser.getControl(name="level_session").value = ['2004']
709        self.browser.getControl("Add study level").click()
710        self.assertMatches('...<span>100</span>...', self.browser.contents)
711        self.assertEqual(self.student['studycourse']['100'].level, 100)
712        self.assertEqual(self.student['studycourse']['100'].level_session, 2004)
713        self.browser.getControl(name="addlevel").value = ['100']
714        self.browser.getControl(name="level_session").value = ['2004']
715        self.browser.getControl("Add study level").click()
716        self.assertMatches('...This level exists...', self.browser.contents)
717        self.browser.getControl("Remove selected").click()
718        self.assertMatches(
719            '...No study level selected...', self.browser.contents)
720        self.browser.getControl(name="val_id").value = ['100']
721        self.browser.getControl(name="level_session").value = ['2004']
722        self.browser.getControl("Remove selected").click()
723        self.assertMatches('...Successfully removed...', self.browser.contents)
724        # Removing levels is properly logged
725        logfile = os.path.join(
726            self.app['datacenter'].storage, 'logs', 'students.log')
727        logcontent = open(logfile).read()
728        self.assertTrue('zope.mgr - students.browser.StudyCourseManageFormPage '
729                        '- K1000000 - removed: 100' in logcontent)
730        # Add level again
731        self.browser.getControl(name="addlevel").value = ['100']
732        self.browser.getControl(name="level_session").value = ['2004']
733        self.browser.getControl("Add study level").click()
734
735        # Managers can view and manage course lists
736        self.browser.getLink("100").click()
737        self.assertMatches(
738            '...: Study Level 100 (Year 1)...', self.browser.contents)
739        self.browser.getLink("Manage").click()
740        self.browser.getControl(name="form.level_session").value = ['2002']
741        self.browser.getControl("Save").click()
742        self.browser.getControl("Remove selected").click()
743        self.assertMatches('...No ticket selected...', self.browser.contents)
744        ctrl = self.browser.getControl(name='val_id')
745        ctrl.getControl(value='COURSE1').selected = True
746        self.browser.getControl("Remove selected", index=0).click()
747        self.assertTrue('Successfully removed' in self.browser.contents)
748        # Removing course tickets is properly logged
749        logfile = os.path.join(
750            self.app['datacenter'].storage, 'logs', 'students.log')
751        logcontent = open(logfile).read()
752        self.assertTrue('zope.mgr - students.browser.StudyLevelManageFormPage '
753        '- K1000000 - removed: COURSE1 at 100' in logcontent)
754        self.browser.getLink("here").click()
755        self.browser.getControl(name="form.course").value = ['COURSE1']
756        self.course.credits = 100
757        self.browser.getControl("Add course ticket").click()
758        self.assertMatches(
759            '...Total credits exceed 50...', self.browser.contents)
760        self.course.credits = 10
761        self.browser.getControl("Add course ticket").click()
762        self.assertTrue('Successfully added' in self.browser.contents)
763        # We can do the same by adding the course on the manage page directly
764        del self.student['studycourse']['100']['COURSE1']
765        self.browser.getControl(name="course").value = 'COURSE1'
766        self.browser.getControl("Add course ticket").click()
767        self.assertTrue('Successfully added' in self.browser.contents)
768        self.browser.getLink("here").click()
769        self.browser.getControl(name="form.course").value = ['COURSE1']
770        self.browser.getControl("Add course ticket").click()
771        self.assertTrue('The ticket exists' in self.browser.contents)
772        self.browser.getControl("Cancel").click()
773        self.browser.getLink("COURSE1").click()
774        self.browser.getLink("Manage").click()
775        self.browser.getControl("Save").click()
776        self.assertTrue('Form has been saved' in self.browser.contents)
777        # Grade and weight have been determined
778        self.browser.open(self.studycourse_path + '/100/COURSE1')
779        self.assertFalse('Grade' in self.browser.contents)
780        self.assertFalse('Weight' in self.browser.contents)
781        self.student['studycourse']['100']['COURSE1'].score = 55
782        self.browser.open(self.studycourse_path + '/100/COURSE1')
783        self.assertTrue('Grade' in self.browser.contents)
784        self.assertTrue('Weight' in self.browser.contents)
785        self.assertEqual(self.student['studycourse']['100']['COURSE1'].grade, 'C')
786        self.assertEqual(self.student['studycourse']['100']['COURSE1'].weight, 3)
787        # We add another ticket to check if GPA will be correctly calculated
788        # (and rounded)
789        courseticket = createObject('waeup.CourseTicket')
790        courseticket.code = 'ANYCODE'
791        courseticket.title = u'Any TITLE'
792        courseticket.credits = 13
793        courseticket.score = 66
794        courseticket.semester = 1
795        courseticket.dcode = u'ANYDCODE'
796        courseticket.fcode = u'ANYFCODE'
797        self.student['studycourse']['100']['COURSE2'] = courseticket
798        self.browser.open(self.student_path + '/studycourse/100')
799        self.assertTrue('<div>3.57</div>' in self.browser.contents)
800        self.assertEqual(self.student['studycourse']['100'].gpa, 3.57)
801        # Carry-over courses will be collected when next level is created
802        self.browser.open(self.student_path + '/studycourse/manage')
803        # Add next level
804        self.student['studycourse']['100']['COURSE1'].score = 10
805        self.browser.getControl(name="addlevel").value = ['200']
806        self.browser.getControl(name="level_session").value = ['2005']
807        self.browser.getControl("Add study level").click()
808        self.browser.getLink("200").click()
809        self.assertMatches(
810            '...: Study Level 200 (Year 2)...', self.browser.contents)
811        # If COURSE1 has score 10 it becomes a carry-over course
812        # in level 200
813        self.assertEqual(
814            sorted(self.student['studycourse']['200'].keys()), [u'COURSE1'])
815        self.assertTrue(
816            self.student['studycourse']['200']['COURSE1'].carry_over)
817        return
818
819    def test_manage_payments(self):
820        # Managers can add online school fee payment tickets
821        # if certain requirements are met
822        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
823        self.browser.open(self.payments_path)
824        IWorkflowState(self.student).setState('cleared')
825        self.browser.getLink("Add current session payment ticket").click()
826        self.browser.getControl(name="form.p_category").value = ['schoolfee']
827        self.browser.getControl("Create ticket").click()
828        self.assertMatches('...ticket created...',
829                           self.browser.contents)
830        ctrl = self.browser.getControl(name='val_id')
831        value = ctrl.options[0]
832        self.browser.getLink(value).click()
833        self.assertMatches('...Amount Authorized...',
834                           self.browser.contents)
835        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
836        payment_url = self.browser.url
837
838        # The pdf payment slip can't yet be opened
839        #self.browser.open(payment_url + '/payment_slip.pdf')
840        #self.assertMatches('...Ticket not yet paid...',
841        #                   self.browser.contents)
842
843        # The same payment (with same p_item, p_session and p_category)
844        # can be initialized a second time if the former ticket is not yet paid.
845        self.browser.open(self.payments_path)
846        self.browser.getLink("Add current session payment ticket").click()
847        self.browser.getControl(name="form.p_category").value = ['schoolfee']
848        self.browser.getControl("Create ticket").click()
849        self.assertMatches('...Payment ticket created...',
850                           self.browser.contents)
851
852        # The ticket can be found in the payments_catalog
853        cat = queryUtility(ICatalog, name='payments_catalog')
854        results = list(cat.searchResults(p_state=('unpaid', 'unpaid')))
855        self.assertTrue(len(results), 1)
856        self.assertTrue(results[0] is self.student['payments'][value])
857
858        # Managers can approve the payment
859        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
860        self.browser.open(payment_url)
861        self.browser.getLink("Approve payment").click()
862        self.assertMatches('...Payment approved...',
863                          self.browser.contents)
864        # Approval is logged in students.log ...
865        logfile = os.path.join(
866            self.app['datacenter'].storage, 'logs', 'students.log')
867        logcontent = open(logfile).read()
868        self.assertTrue(
869            'zope.mgr - students.browser.OnlinePaymentApprovePage '
870            '- K1000000 - schoolfee payment approved'
871            in logcontent)
872        # ... and in payments.log
873        logfile = os.path.join(
874            self.app['datacenter'].storage, 'logs', 'payments.log')
875        logcontent = open(logfile).read()
876        self.assertTrue(
877            '"zope.mgr",K1000000,%s,schoolfee,40000.0,AP,,,,,,\n' % value
878            in logcontent)
879
880        # The authorized amount has been stored in the access code
881        self.assertEqual(
882            self.app['accesscodes']['SFE-0'].values()[0].cost,40000.0)
883
884        # The catalog has been updated
885        results = list(cat.searchResults(p_state=('unpaid', 'unpaid')))
886        self.assertTrue(len(results), 0)
887        results = list(cat.searchResults(p_state=('paid', 'paid')))
888        self.assertTrue(len(results), 1)
889        self.assertTrue(results[0] is self.student['payments'][value])
890
891        # Payments can't be approved twice
892        self.browser.open(payment_url + '/approve')
893        self.assertMatches('...This ticket has already been paid...',
894                          self.browser.contents)
895
896        # Now the first ticket is paid and no more ticket of same type
897        # (with same p_item, p_session and p_category) can be added
898        self.browser.open(self.payments_path)
899        self.browser.getLink("Add current session payment ticket").click()
900        self.browser.getControl(name="form.p_category").value = ['schoolfee']
901        self.browser.getControl("Create ticket").click()
902        self.assertMatches(
903            '...This type of payment has already been made...',
904            self.browser.contents)
905
906        # Managers can open the pdf payment slip
907        self.browser.open(payment_url)
908        self.browser.getLink("Download payment slip").click()
909        self.assertEqual(self.browser.headers['Status'], '200 Ok')
910        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
911
912        # Managers can remove online school fee payment tickets
913        self.browser.open(self.payments_path)
914        self.browser.getControl("Remove selected").click()
915        self.assertMatches('...No payment selected...', self.browser.contents)
916        ctrl = self.browser.getControl(name='val_id')
917        value = ctrl.options[0]
918        ctrl.getControl(value=value).selected = True
919        self.browser.getControl("Remove selected", index=0).click()
920        self.assertTrue('Successfully removed' in self.browser.contents)
921
922        # Managers can add online clearance payment tickets
923        self.browser.open(self.payments_path + '/addop')
924        self.browser.getControl(name="form.p_category").value = ['clearance']
925        self.browser.getControl("Create ticket").click()
926        self.assertMatches('...ticket created...',
927                           self.browser.contents)
928
929        # Managers can approve the payment
930        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
931        ctrl = self.browser.getControl(name='val_id')
932        value = ctrl.options[1] # The clearance payment is the second in the table
933        self.browser.getLink(value).click()
934        self.browser.open(self.browser.url + '/approve')
935        self.assertMatches('...Payment approved...',
936                          self.browser.contents)
937        expected = '''...
938        <td>
939          <span>Paid</span>
940        </td>...'''
941        self.assertMatches(expected,self.browser.contents)
942        # The new CLR-0 pin has been created
943        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
944        pin = self.app['accesscodes']['CLR-0'].keys()[0]
945        ac = self.app['accesscodes']['CLR-0'][pin]
946        self.assertEqual(ac.owner, self.student_id)
947        self.assertEqual(ac.cost, 3456.0)
948        return
949
950    def test_manage_balance_payments(self):
951
952        # Login
953        #self.browser.open(self.login_path)
954        #self.browser.getControl(name="form.login").value = self.student_id
955        #self.browser.getControl(name="form.password").value = 'spwd'
956        #self.browser.getControl("Login").click()
957
958        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
959        self.browser.open(self.payments_path)
960
961        # Managers can add previous school fee payment tickets in any state.
962        IWorkflowState(self.student).setState('courses registered')
963        self.browser.open(self.payments_path)
964        self.browser.getLink("Add balance payment ticket").click()
965
966        # Previous session payment form is provided
967        self.assertEqual(self.student.current_session, 2004)
968        self.browser.getControl(name="form.p_category").value = ['schoolfee']
969        self.browser.getControl(name="form.balance_session").value = ['2004']
970        self.browser.getControl(name="form.balance_level").value = ['300']
971        self.browser.getControl(name="form.balance_amount").value = '-567.8'
972        self.browser.getControl("Create ticket").click()
973        self.assertMatches('...Amount must be greater than 0...',
974                           self.browser.contents)
975        self.browser.getControl(name="form.balance_amount").value = '0'
976        self.browser.getControl("Create ticket").click()
977        self.assertMatches('...Amount must be greater than 0...',
978                           self.browser.contents)
979        self.browser.getControl(name="form.balance_amount").value = '567.8'
980        self.browser.getControl("Create ticket").click()
981        self.assertMatches('...ticket created...',
982                           self.browser.contents)
983        ctrl = self.browser.getControl(name='val_id')
984        value = ctrl.options[0]
985        self.browser.getLink(value).click()
986        self.assertMatches('...Amount Authorized...',
987                           self.browser.contents)
988        self.assertEqual(self.student['payments'][value].amount_auth, 567.8)
989        # Payment attributes are properly set
990        self.assertEqual(self.student['payments'][value].p_session, 2004)
991        self.assertEqual(self.student['payments'][value].p_level, 300)
992        self.assertEqual(self.student['payments'][value].p_item, u'Balance')
993        self.assertEqual(self.student['payments'][value].p_category, 'schoolfee')
994
995    def test_manage_accommodation(self):
996        logfile = os.path.join(
997            self.app['datacenter'].storage, 'logs', 'students.log')
998        # Managers can add online booking fee payment tickets and open the
999        # callback view (see test_manage_payments)
1000        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1001        self.browser.open(self.payments_path)
1002        self.browser.getLink("Add current session payment ticket").click()
1003        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1004        # If student is not in accommodation session, payment cannot be processed
1005        self.app['hostels'].accommodation_session = 2011
1006        self.browser.getControl("Create ticket").click()
1007        self.assertMatches('...Your current session does not match...',
1008                           self.browser.contents)
1009        self.app['hostels'].accommodation_session = 2004
1010        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1011        self.browser.getControl("Create ticket").click()
1012        ctrl = self.browser.getControl(name='val_id')
1013        value = ctrl.options[0]
1014        self.browser.getLink(value).click()
1015        self.browser.open(self.browser.url + '/approve')
1016        # The new HOS-0 pin has been created
1017        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
1018        pin = self.app['accesscodes']['HOS-0'].keys()[0]
1019        ac = self.app['accesscodes']['HOS-0'][pin]
1020        self.assertEqual(ac.owner, self.student_id)
1021        parts = pin.split('-')[1:]
1022        sfeseries, sfenumber = parts
1023        # Managers can use HOS code and book a bed space with it
1024        self.browser.open(self.acco_path)
1025        self.browser.getLink("Book accommodation").click()
1026        self.assertMatches('...You are in the wrong...',
1027                           self.browser.contents)
1028        IWorkflowInfo(self.student).fireTransition('admit')
1029        # An existing HOS code can only be used if students
1030        # are in accommodation session
1031        self.student['studycourse'].current_session = 2003
1032        self.browser.getLink("Book accommodation").click()
1033        self.assertMatches('...Your current session does not match...',
1034                           self.browser.contents)
1035        self.student['studycourse'].current_session = 2004
1036        # All requirements are met and ticket can be created
1037        self.browser.getLink("Book accommodation").click()
1038        self.assertMatches('...Activation Code:...',
1039                           self.browser.contents)
1040        self.browser.getControl(name="ac_series").value = sfeseries
1041        self.browser.getControl(name="ac_number").value = sfenumber
1042        self.browser.getControl("Create bed ticket").click()
1043        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1044                           self.browser.contents)
1045        # Bed has been allocated
1046        bed1 = self.app['hostels']['hall-1']['hall-1_A_101_A']
1047        self.assertTrue(bed1.owner == self.student_id)
1048        # BedTicketAddPage is now blocked
1049        self.browser.getLink("Book accommodation").click()
1050        self.assertMatches('...You already booked a bed space...',
1051            self.browser.contents)
1052        # The bed ticket displays the data correctly
1053        self.browser.open(self.acco_path + '/2004')
1054        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1055                           self.browser.contents)
1056        self.assertMatches('...2004/2005...', self.browser.contents)
1057        self.assertMatches('...regular_male_fr...', self.browser.contents)
1058        self.assertMatches('...%s...' % pin, self.browser.contents)
1059        # Booking is properly logged
1060        logcontent = open(logfile).read()
1061        self.assertTrue('zope.mgr - students.browser.BedTicketAddPage '
1062            '- K1000000 - booked: hall-1_A_101_A' in logcontent)
1063        # Managers can relocate students if the student's bed_type has changed
1064        self.browser.getLink("Relocate student").click()
1065        self.assertMatches(
1066            "...Student can't be relocated...", self.browser.contents)
1067        self.student.sex = u'f'
1068        self.browser.getLink("Relocate student").click()
1069        self.assertMatches(
1070            "...Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
1071        self.assertTrue(bed1.owner == NOT_OCCUPIED)
1072        bed2 = self.app['hostels']['hall-1']['hall-1_A_101_B']
1073        self.assertTrue(bed2.owner == self.student_id)
1074        self.assertTrue(self.student['accommodation'][
1075            '2004'].bed_type == u'regular_female_fr')
1076        # Relocation is properly logged
1077        logcontent = open(logfile).read()
1078        self.assertTrue('zope.mgr - students.browser.BedTicketRelocationPage '
1079            '- K1000000 - relocated: hall-1_A_101_B' in logcontent)
1080        # The payment object still shows the original payment item
1081        payment_id = self.student['payments'].keys()[0]
1082        payment = self.student['payments'][payment_id]
1083        self.assertTrue(payment.p_item == u'regular_male_fr')
1084        # Managers can relocate students if the bed's bed_type has changed
1085        bed1.bed_type = u'regular_female_fr'
1086        bed2.bed_type = u'regular_male_fr'
1087        notify(grok.ObjectModifiedEvent(bed1))
1088        notify(grok.ObjectModifiedEvent(bed2))
1089        self.browser.getLink("Relocate student").click()
1090        self.assertMatches(
1091            "...Student relocated...", self.browser.contents)
1092        self.assertMatches(
1093            "... Hall 1, Block A, Room 101, Bed A...", self.browser.contents)
1094        self.assertMatches(bed1.owner, self.student_id)
1095        self.assertMatches(bed2.owner, NOT_OCCUPIED)
1096        # Managers can't relocate students if bed is reserved
1097        self.student.sex = u'm'
1098        bed1.bed_type = u'regular_female_reserved'
1099        notify(grok.ObjectModifiedEvent(bed1))
1100        self.browser.getLink("Relocate student").click()
1101        self.assertMatches(
1102            "...Students in reserved beds can't be relocated...",
1103            self.browser.contents)
1104        # Managers can relocate students if booking has been cancelled but
1105        # other bed space has been manually allocated after cancellation
1106        old_owner = bed1.releaseBed()
1107        self.assertMatches(old_owner, self.student_id)
1108        bed2.owner = self.student_id
1109        self.browser.open(self.acco_path + '/2004')
1110        self.assertMatches(
1111            "...booking cancelled...", self.browser.contents)
1112        self.browser.getLink("Relocate student").click()
1113        # We didn't informed the catalog therefore the new owner is not found
1114        self.assertMatches(
1115            "...There is no free bed in your category regular_male_fr...",
1116            self.browser.contents)
1117        # Now we fire the event properly
1118        notify(grok.ObjectModifiedEvent(bed2))
1119        self.browser.getLink("Relocate student").click()
1120        self.assertMatches(
1121            "...Student relocated...", self.browser.contents)
1122        self.assertMatches(
1123            "... Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
1124          # Managers can delete bed tickets
1125        self.browser.open(self.acco_path)
1126        ctrl = self.browser.getControl(name='val_id')
1127        value = ctrl.options[0]
1128        ctrl.getControl(value=value).selected = True
1129        self.browser.getControl("Remove selected", index=0).click()
1130        self.assertMatches('...Successfully removed...', self.browser.contents)
1131        # The bed has been properly released by the event handler
1132        self.assertMatches(bed1.owner, NOT_OCCUPIED)
1133        self.assertMatches(bed2.owner, NOT_OCCUPIED)
1134        return
1135
1136    def test_manage_workflow(self):
1137        # Managers can pass through the whole workflow
1138        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1139        student = self.app['students'][self.student_id]
1140        self.browser.open(self.trigtrans_path)
1141        self.assertTrue(student.clearance_locked)
1142        self.browser.getControl(name="transition").value = ['admit']
1143        self.browser.getControl("Save").click()
1144        self.assertTrue(student.clearance_locked)
1145        self.browser.getControl(name="transition").value = ['start_clearance']
1146        self.browser.getControl("Save").click()
1147        self.assertFalse(student.clearance_locked)
1148        self.browser.getControl(name="transition").value = ['request_clearance']
1149        self.browser.getControl("Save").click()
1150        self.assertTrue(student.clearance_locked)
1151        self.browser.getControl(name="transition").value = ['clear']
1152        self.browser.getControl("Save").click()
1153        # Managers approve payment, they do not pay
1154        self.assertFalse('pay_first_school_fee' in self.browser.contents)
1155        self.browser.getControl(
1156            name="transition").value = ['approve_first_school_fee']
1157        self.browser.getControl("Save").click()
1158        self.browser.getControl(name="transition").value = ['reset6']
1159        self.browser.getControl("Save").click()
1160        # In state returning the pay_school_fee transition triggers some
1161        # changes of attributes
1162        self.browser.getControl(name="transition").value = ['approve_school_fee']
1163        self.browser.getControl("Save").click()
1164        self.assertEqual(student['studycourse'].current_session, 2005) # +1
1165        self.assertEqual(student['studycourse'].current_level, 200) # +100
1166        self.assertEqual(student['studycourse'].current_verdict, '0') # 0 = Zero = not set
1167        self.assertEqual(student['studycourse'].previous_verdict, 'A')
1168        self.browser.getControl(name="transition").value = ['register_courses']
1169        self.browser.getControl("Save").click()
1170        self.browser.getControl(name="transition").value = ['validate_courses']
1171        self.browser.getControl("Save").click()
1172        self.browser.getControl(name="transition").value = ['return']
1173        self.browser.getControl("Save").click()
1174        return
1175
1176    def test_manage_pg_workflow(self):
1177        # Managers can pass through the whole workflow
1178        IWorkflowState(self.student).setState('school fee paid')
1179        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1180        student = self.app['students'][self.student_id]
1181        self.browser.open(self.trigtrans_path)
1182        self.assertTrue('<option value="reset6">' in self.browser.contents)
1183        self.assertTrue('<option value="register_courses">' in self.browser.contents)
1184        self.assertTrue('<option value="reset5">' in self.browser.contents)
1185        self.certificate.study_mode = 'pg_ft'
1186        self.browser.open(self.trigtrans_path)
1187        self.assertFalse('<option value="reset6">' in self.browser.contents)
1188        self.assertFalse('<option value="register_courses">' in self.browser.contents)
1189        self.assertTrue('<option value="reset5">' in self.browser.contents)
1190        return
1191
1192    def test_manage_import(self):
1193        # Managers can import student data files
1194        datacenter_path = 'http://localhost/app/datacenter'
1195        # Prepare a csv file for students
1196        open('students.csv', 'wb').write(
1197"""firstname,lastname,reg_number,date_of_birth,matric_number,email,phone,sex,password
1198Aaren,Pieri,1,1990-01-02,100000,aa@aa.ng,1234,m,mypwd1
1199Claus,Finau,2,1990-01-03,100001,aa@aa.ng,1234,m,mypwd1
1200Brit,Berson,3,1990-01-04,100001,aa@aa.ng,1234,m,mypwd1
1201""")
1202        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1203        self.browser.open(datacenter_path)
1204        self.browser.getLink('Upload data').click()
1205        filecontents = StringIO(open('students.csv', 'rb').read())
1206        filewidget = self.browser.getControl(name='uploadfile:file')
1207        filewidget.add_file(filecontents, 'text/plain', 'students.csv')
1208        self.browser.getControl(name='SUBMIT').click()
1209        self.browser.getLink('Process data').click()
1210        button = lookup_submit_value(
1211            'select', 'students_zope.mgr.csv', self.browser)
1212        button.click()
1213        importerselect = self.browser.getControl(name='importer')
1214        modeselect = self.browser.getControl(name='mode')
1215        importerselect.getControl('Student Processor').selected = True
1216        modeselect.getControl(value='create').selected = True
1217        self.browser.getControl('Proceed to step 3').click()
1218        self.assertTrue('Header fields OK' in self.browser.contents)
1219        self.browser.getControl('Perform import').click()
1220        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
1221        self.assertTrue('Successfully processed 2 rows' in self.browser.contents)
1222        self.assertTrue('Batch processing finished' in self.browser.contents)
1223        open('studycourses.csv', 'wb').write(
1224"""reg_number,matric_number,certificate,current_session,current_level
12251,,CERT1,2008,100
1226,100001,CERT1,2008,100
1227,100002,CERT1,2008,100
1228""")
1229        self.browser.open(datacenter_path)
1230        self.browser.getLink('Upload data').click()
1231        filecontents = StringIO(open('studycourses.csv', 'rb').read())
1232        filewidget = self.browser.getControl(name='uploadfile:file')
1233        filewidget.add_file(filecontents, 'text/plain', 'studycourses.csv')
1234        self.browser.getControl(name='SUBMIT').click()
1235        self.browser.getLink('Process data').click()
1236        button = lookup_submit_value(
1237            'select', 'studycourses_zope.mgr.csv', self.browser)
1238        button.click()
1239        importerselect = self.browser.getControl(name='importer')
1240        modeselect = self.browser.getControl(name='mode')
1241        importerselect.getControl(
1242            'StudentStudyCourse Processor (update only)').selected = True
1243        modeselect.getControl(value='create').selected = True
1244        self.browser.getControl('Proceed to step 3').click()
1245        self.assertTrue('Update mode only' in self.browser.contents)
1246        self.browser.getControl('Proceed to step 3').click()
1247        self.assertTrue('Header fields OK' in self.browser.contents)
1248        self.browser.getControl('Perform import').click()
1249        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
1250        self.assertTrue('Successfully processed 2 rows'
1251                        in self.browser.contents)
1252        # The students are properly indexed and we can
1253        # thus find a student in  the department
1254        self.browser.open(self.manage_container_path)
1255        self.browser.getControl(name="searchtype").value = ['depcode']
1256        self.browser.getControl(name="searchterm").value = 'dep1'
1257        self.browser.getControl("Search").click()
1258        self.assertTrue('Aaren Pieri' in self.browser.contents)
1259        # We can search for a new student by name ...
1260        self.browser.getControl(name="searchtype").value = ['fullname']
1261        self.browser.getControl(name="searchterm").value = 'Claus'
1262        self.browser.getControl("Search").click()
1263        self.assertTrue('Claus Finau' in self.browser.contents)
1264        # ... and check if the imported password has been properly set
1265        ctrl = self.browser.getControl(name='entries')
1266        value = ctrl.options[0]
1267        claus = self.app['students'][value]
1268        self.assertTrue(IUserAccount(claus).checkPassword('mypwd1'))
1269        return
1270
1271    def test_handle_clearance_by_co(self):
1272        # Create clearance officer
1273        self.app['users'].addUser('mrclear', 'mrclearsecret')
1274        self.app['users']['mrclear'].email = 'mrclear@foo.ng'
1275        self.app['users']['mrclear'].title = 'Carlo Pitter'
1276        # Clearance officers need not necessarily to get
1277        # the StudentsOfficer site role
1278        #prmglobal = IPrincipalRoleManager(self.app)
1279        #prmglobal.assignRoleToPrincipal('waeup.StudentsOfficer', 'mrclear')
1280        # Assign local ClearanceOfficer role
1281        department = self.app['faculties']['fac1']['dep1']
1282        prmlocal = IPrincipalRoleManager(department)
1283        prmlocal.assignRoleToPrincipal('waeup.local.ClearanceOfficer', 'mrclear')
1284        IWorkflowState(self.student).setState('clearance started')
1285        # Login as clearance officer
1286        self.browser.open(self.login_path)
1287        self.browser.getControl(name="form.login").value = 'mrclear'
1288        self.browser.getControl(name="form.password").value = 'mrclearsecret'
1289        self.browser.getControl("Login").click()
1290        self.assertMatches('...You logged in...', self.browser.contents)
1291        # CO can see his roles
1292        self.browser.getLink("My Roles").click()
1293        self.assertMatches(
1294            '...<div>Academics Officer (view only)</div>...',
1295            self.browser.contents)
1296        #self.assertMatches(
1297        #    '...<div>Students Officer (view only)</div>...',
1298        #    self.browser.contents)
1299        # But not his local role ...
1300        self.assertFalse('Clearance Officer' in self.browser.contents)
1301        # ... because we forgot to notify the department that the local role
1302        # has changed
1303        notify(LocalRoleSetEvent(
1304            department, 'waeup.local.ClearanceOfficer', 'mrclear', granted=True))
1305        self.browser.open('http://localhost/app/users/mrclear/my_roles')
1306        self.assertTrue('Clearance Officer' in self.browser.contents)
1307        self.assertMatches(
1308            '...<a href="http://localhost/app/faculties/fac1/dep1">...',
1309            self.browser.contents)
1310        # CO can view the student ...
1311        self.browser.open(self.clearance_path)
1312        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1313        self.assertEqual(self.browser.url, self.clearance_path)
1314        # ... but not other students
1315        other_student = Student()
1316        other_student.firstname = u'Dep2'
1317        other_student.lastname = u'Student'
1318        self.app['students'].addStudent(other_student)
1319        other_student_path = (
1320            'http://localhost/app/students/%s' % other_student.student_id)
1321        self.assertRaises(
1322            Unauthorized, self.browser.open, other_student_path)
1323        # Clearance is disabled for this session
1324        self.browser.open(self.clearance_path)
1325        self.assertFalse('Clear student' in self.browser.contents)
1326        self.browser.open(self.student_path + '/clear')
1327        self.assertTrue('Clearance is disabled for this session'
1328            in self.browser.contents)
1329        self.app['configuration']['2004'].clearance_enabled = True
1330        # Only in state clearance requested the CO does see the 'Clear' button
1331        self.browser.open(self.clearance_path)
1332        self.assertFalse('Clear student' in self.browser.contents)
1333        IWorkflowInfo(self.student).fireTransition('request_clearance')
1334        self.browser.open(self.clearance_path)
1335        self.assertTrue('Clear student' in self.browser.contents)
1336        self.browser.getLink("Clear student").click()
1337        self.assertTrue('Student has been cleared' in self.browser.contents)
1338        self.assertTrue('cleared' in self.browser.contents)
1339        self.browser.open(self.history_path)
1340        self.assertTrue('Cleared by Carlo Pitter' in self.browser.contents)
1341        # Hide real name.
1342        self.app['users']['mrclear'].public_name = 'My Public Name'
1343        self.browser.open(self.clearance_path)
1344        self.browser.getLink("Reject clearance").click()
1345        self.assertEqual(
1346            self.browser.url, self.student_path + '/reject_clearance')
1347        # Type comment why
1348        self.browser.getControl(name="form.officer_comment").value = """Dear Student,
1349You did not fill properly.
1350"""
1351        self.browser.getControl("Save comment").click()
1352        self.assertTrue('Clearance has been annulled' in self.browser.contents)
1353        url = ('http://localhost/app/students/K1000000/'
1354              'contactstudent?body=Dear+Student%2C%0AYou+did+not+fill+properly.'
1355              '%0A&subject=Clearance+has+been+annulled.')
1356        # CO does now see the prefilled contact form and can send a message
1357        self.assertEqual(self.browser.url, url)
1358        self.assertTrue('clearance started' in self.browser.contents)
1359        self.assertTrue('name="form.subject" size="20" type="text" '
1360            'value="Clearance has been annulled."'
1361            in self.browser.contents)
1362        self.assertTrue('name="form.body" rows="10" >Dear Student,'
1363            in self.browser.contents)
1364        self.browser.getControl("Send message now").click()
1365        self.assertTrue('Your message has been sent' in self.browser.contents)
1366        # The comment has been stored ...
1367        self.assertEqual(self.student.officer_comment,
1368            u'Dear Student,\nYou did not fill properly.\n')
1369        # ... and logged
1370        logfile = os.path.join(
1371            self.app['datacenter'].storage, 'logs', 'students.log')
1372        logcontent = open(logfile).read()
1373        self.assertTrue(
1374            'INFO - mrclear - students.browser.StudentRejectClearancePage - '
1375            'K1000000 - comment: Dear Student,<br>You did not fill '
1376            'properly.<br>\n' in logcontent)
1377        self.browser.open(self.history_path)
1378        self.assertTrue("Reset to 'clearance started' by My Public Name" in
1379            self.browser.contents)
1380        IWorkflowInfo(self.student).fireTransition('request_clearance')
1381        self.browser.open(self.clearance_path)
1382        self.browser.getLink("Reject clearance").click()
1383        self.browser.getControl("Save comment").click()
1384        self.assertTrue('Clearance request has been rejected'
1385            in self.browser.contents)
1386        self.assertTrue('clearance started' in self.browser.contents)
1387        # The CO can't clear students if not in state
1388        # clearance requested
1389        self.browser.open(self.student_path + '/clear')
1390        self.assertTrue('Student is in wrong state'
1391            in self.browser.contents)
1392        # The CO can go to his department throug the my_roles page ...
1393        self.browser.open('http://localhost/app/users/mrclear/my_roles')
1394        self.browser.getLink("http://localhost/app/faculties/fac1/dep1").click()
1395        # ... and view the list of students
1396        self.browser.getLink("Show students").click()
1397        self.browser.getControl(name="session").value = ['2004']
1398        self.browser.getControl(name="level").value = ['200']
1399        self.browser.getControl("Show").click()
1400        self.assertFalse(self.student_id in self.browser.contents)
1401        self.browser.getControl(name="session").value = ['2004']
1402        self.browser.getControl(name="level").value = ['100']
1403        self.browser.getControl("Show").click()
1404        self.assertTrue(self.student_id in self.browser.contents)
1405        # The comment is indicated by 'yes'
1406        self.assertTrue('<td><span>yes</span></td>' in self.browser.contents)
1407        # Check if the enquiries form is not pre-filled with officer_comment
1408        # (regression test)
1409        self.browser.getLink("Logout").click()
1410        self.browser.open('http://localhost/app/enquiries')
1411        self.assertFalse(
1412            'You did not fill properly'
1413            in self.browser.contents)
1414        # When a student is cleared the comment is automatically deleted
1415        IWorkflowInfo(self.student).fireTransition('request_clearance')
1416        IWorkflowInfo(self.student).fireTransition('clear')
1417        self.assertEqual(self.student.officer_comment, None)
1418
1419    def test_handle_courses_by_ca(self):
1420        # Create course adviser
1421        self.app['users'].addUser('mrsadvise', 'mrsadvisesecret')
1422        self.app['users']['mrsadvise'].email = 'mradvise@foo.ng'
1423        self.app['users']['mrsadvise'].title = u'Helen Procter'
1424        # Assign local CourseAdviser100 role for a certificate
1425        cert = self.app['faculties']['fac1']['dep1'].certificates['CERT1']
1426        prmlocal = IPrincipalRoleManager(cert)
1427        prmlocal.assignRoleToPrincipal('waeup.local.CourseAdviser100', 'mrsadvise')
1428        IWorkflowState(self.student).setState('school fee paid')
1429        # Login as course adviser
1430        self.browser.open(self.login_path)
1431        self.browser.getControl(name="form.login").value = 'mrsadvise'
1432        self.browser.getControl(name="form.password").value = 'mrsadvisesecret'
1433        self.browser.getControl("Login").click()
1434        self.assertMatches('...You logged in...', self.browser.contents)
1435        # CO can see his roles
1436        self.browser.getLink("My Roles").click()
1437        self.assertMatches(
1438            '...<div>Academics Officer (view only)</div>...',
1439            self.browser.contents)
1440        # But not his local role ...
1441        self.assertFalse('Course Adviser' in self.browser.contents)
1442        # ... because we forgot to notify the certificate that the local role
1443        # has changed
1444        notify(LocalRoleSetEvent(
1445            cert, 'waeup.local.CourseAdviser100', 'mrsadvise', granted=True))
1446        self.browser.open('http://localhost/app/users/mrsadvise/my_roles')
1447        self.assertTrue('Course Adviser 100L' in self.browser.contents)
1448        self.assertMatches(
1449            '...<a href="http://localhost/app/faculties/fac1/dep1/certificates/CERT1">...',
1450            self.browser.contents)
1451        # CA can view the student ...
1452        self.browser.open(self.student_path)
1453        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1454        self.assertEqual(self.browser.url, self.student_path)
1455        # ... but not other students
1456        other_student = Student()
1457        other_student.firstname = u'Dep2'
1458        other_student.lastname = u'Student'
1459        self.app['students'].addStudent(other_student)
1460        other_student_path = (
1461            'http://localhost/app/students/%s' % other_student.student_id)
1462        self.assertRaises(
1463            Unauthorized, self.browser.open, other_student_path)
1464        # We add study level 110 to the student's studycourse
1465        studylevel = StudentStudyLevel()
1466        studylevel.level = 110
1467        self.student['studycourse'].addStudentStudyLevel(
1468            cert,studylevel)
1469        L110_student_path = self.studycourse_path + '/110'
1470        # The CA can neither see the Validate nor the Edit button
1471        self.browser.open(L110_student_path)
1472        self.assertFalse('Validate courses' in self.browser.contents)
1473        self.assertFalse('Edit' in self.browser.contents)
1474        IWorkflowInfo(self.student).fireTransition('register_courses')
1475        self.browser.open(L110_student_path)
1476        self.assertFalse('Validate courses' in self.browser.contents)
1477        self.assertFalse('Edit' in self.browser.contents)
1478        # Only in state courses registered and only if the current level
1479        # corresponds with the name of the study level object
1480        # the 100L CA does see the 'Validate' button but not
1481        # the edit button
1482        self.student['studycourse'].current_level = 110
1483        self.browser.open(L110_student_path)
1484        self.assertFalse('Edit' in self.browser.contents)
1485        self.assertTrue('Validate courses' in self.browser.contents)
1486        # But a 100L CA does not see the button at other levels
1487        studylevel2 = StudentStudyLevel()
1488        studylevel2.level = 200
1489        self.student['studycourse'].addStudentStudyLevel(
1490            cert,studylevel2)
1491        L200_student_path = self.studycourse_path + '/200'
1492        self.browser.open(L200_student_path)
1493        self.assertFalse('Edit' in self.browser.contents)
1494        self.assertFalse('Validate courses' in self.browser.contents)
1495        self.browser.open(L110_student_path)
1496        self.browser.getLink("Validate courses").click()
1497        self.assertTrue('Course list has been validated' in self.browser.contents)
1498        self.assertTrue('courses validated' in self.browser.contents)
1499        self.assertEqual(self.student['studycourse']['110'].validated_by,
1500            'Helen Procter')
1501        self.assertMatches(
1502            '<YYYY-MM-DD hh:mm:ss>',
1503            self.student['studycourse']['110'].validation_date.strftime(
1504                "%Y-%m-%d %H:%M:%S"))
1505        self.browser.getLink("Reject courses").click()
1506        self.assertTrue('Course list request has been annulled.'
1507            in self.browser.contents)
1508        urlmessage = 'Course+list+request+has+been+annulled.'
1509        self.assertEqual(self.browser.url, self.student_path +
1510            '/contactstudent?subject=%s' % urlmessage)
1511        self.assertTrue('school fee paid' in self.browser.contents)
1512        self.assertTrue(self.student['studycourse']['110'].validated_by is None)
1513        self.assertTrue(self.student['studycourse']['110'].validation_date is None)
1514        IWorkflowInfo(self.student).fireTransition('register_courses')
1515        self.browser.open(L110_student_path)
1516        self.browser.getLink("Reject courses").click()
1517        self.assertTrue('Course list request has been rejected'
1518            in self.browser.contents)
1519        self.assertTrue('school fee paid' in self.browser.contents)
1520        # CA does now see the contact form and can send a message
1521        self.browser.getControl(name="form.subject").value = 'Important subject'
1522        self.browser.getControl(name="form.body").value = 'Course list rejected'
1523        self.browser.getControl("Send message now").click()
1524        self.assertTrue('Your message has been sent' in self.browser.contents)
1525        # The CA does now see the Edit button and can edit
1526        # current study level
1527        self.browser.open(L110_student_path)
1528        self.browser.getLink("Edit").click()
1529        self.assertTrue('Edit course list of 100 (Year 1) on 1st probation'
1530            in self.browser.contents)
1531        # The CA can't validate courses if not in state
1532        # courses registered
1533        self.browser.open(L110_student_path + '/validate_courses')
1534        self.assertTrue('Student is in the wrong state'
1535            in self.browser.contents)
1536        # The CA can go to his certificate through the my_roles page
1537        self.browser.open('http://localhost/app/users/mrsadvise/my_roles')
1538        self.browser.getLink(
1539            "http://localhost/app/faculties/fac1/dep1/certificates/CERT1").click()
1540        # and view the list of students
1541        self.browser.getLink("Show students").click()
1542        self.browser.getControl(name="session").value = ['2004']
1543        self.browser.getControl(name="level").value = ['100']
1544        self.browser.getControl("Show").click()
1545        self.assertTrue(self.student_id in self.browser.contents)
1546
1547    def test_handle_courses_by_lecturer(self):
1548        # Create course lecturer
1549        self.app['users'].addUser('mrslecturer', 'mrslecturersecret')
1550        self.app['users']['mrslecturer'].email = 'mrslecturer@foo.ng'
1551        self.app['users']['mrslecturer'].title = u'Mercedes Benz'
1552        # Assign local Courselecturer100 role for a certificate
1553        course = self.app['faculties']['fac1']['dep1'].courses['COURSE1']
1554        prmlocal = IPrincipalRoleManager(course)
1555        prmlocal.assignRoleToPrincipal('waeup.local.Lecturer', 'mrslecturer')
1556        # Login as lecturer
1557        self.browser.open(self.login_path)
1558        self.browser.getControl(name="form.login").value = 'mrslecturer'
1559        self.browser.getControl(name="form.password").value = 'mrslecturersecret'
1560        self.browser.getControl("Login").click()
1561        self.assertMatches('...You logged in...', self.browser.contents)
1562        # CO can see her roles
1563        self.browser.getLink("My Roles").click()
1564        self.assertMatches(
1565            '...<div>Academics Officer (view only)</div>...',
1566            self.browser.contents)
1567        # But not her local role ...
1568        self.assertFalse('Lecturer' in self.browser.contents)
1569        # ... because we forgot to notify the course that the local role
1570        # has changed
1571        notify(LocalRoleSetEvent(
1572            course, 'waeup.local.Lecturer', 'mrslecturer', granted=True))
1573        self.browser.open('http://localhost/app/users/mrslecturer/my_roles')
1574        self.assertTrue('Lecturer' in self.browser.contents)
1575        self.assertMatches(
1576            '...<a href="http://localhost/app/faculties/fac1/dep1/courses/COURSE1">...',
1577            self.browser.contents)
1578        # The lecturer can go to her course
1579        self.browser.getLink(
1580            "http://localhost/app/faculties/fac1/dep1/courses/COURSE1").click()
1581        # and view the list of students
1582        self.browser.getLink("Show students").click()
1583        self.browser.getControl(name="session").value = ['2004']
1584        self.browser.getControl(name="level").value = ['100']
1585        self.browser.getControl("Show").click()
1586        self.assertTrue('No student found.' in self.browser.contents)
1587        # No student in course so far
1588        self.assertFalse(self.student_id in self.browser.contents)
1589        studylevel = createObject(u'waeup.StudentStudyLevel')
1590        studylevel.level = 100
1591        studylevel.level_session = 2004
1592        self.student['studycourse'].addStudentStudyLevel(
1593            self.certificate, studylevel)
1594        # Now the student has registered the course and can
1595        # be seen by the lecturer.
1596        self.browser.open("http://localhost/app/faculties/fac1/dep1/courses/COURSE1/students")
1597        self.browser.getControl(name="session").value = ['2004']
1598        self.browser.getControl(name="level").value = ['100']
1599        self.browser.getControl("Show").click()
1600        self.assertTrue(self.student_id in self.browser.contents)
1601        # XXX: So far the lecturer can neither access ths student ...
1602        self.assertRaises(
1603            Unauthorized, self.browser.open, self.student_path)
1604        # ... nor the respective course ticket since a
1605        # CourseTicketPrincipalRoleManager does not yet exist.
1606        self.assertTrue('COURSE1' in self.student['studycourse']['100'].keys())
1607        course_ticket_path = self.student_path + '/studycourse/100/COURSE1'
1608        self.assertRaises(
1609            Unauthorized, self.browser.open, course_ticket_path)
1610
1611    def test_change_current_mode(self):
1612        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1613        self.browser.open(self.clearance_path)
1614        self.assertFalse('Employer' in self.browser.contents)
1615        self.browser.open(self.manage_clearance_path)
1616        self.assertFalse('Employer' in self.browser.contents)
1617        self.student.clearance_locked = False
1618        self.browser.open(self.edit_clearance_path)
1619        self.assertFalse('Employer' in self.browser.contents)
1620        # Now we change the study mode of the certificate and a different
1621        # interface is used by clearance views.
1622        self.certificate.study_mode = 'pg_ft'
1623        # Invariants are not being checked here?!
1624        self.certificate.end_level = 100
1625        self.browser.open(self.clearance_path)
1626        self.assertTrue('Employer' in self.browser.contents)
1627        self.browser.open(self.manage_clearance_path)
1628        self.assertTrue('Employer' in self.browser.contents)
1629        self.browser.open(self.edit_clearance_path)
1630        self.assertTrue('Employer' in self.browser.contents)
1631
1632    def test_activate_deactivate_buttons(self):
1633        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1634        self.browser.open(self.student_path)
1635        self.browser.getLink("Deactivate").click()
1636        self.assertTrue(
1637            'Student account has been deactivated.' in self.browser.contents)
1638        self.assertTrue(
1639            'Base Data (account deactivated)' in self.browser.contents)
1640        self.assertTrue(self.student.suspended)
1641        self.browser.getLink("Activate").click()
1642        self.assertTrue(
1643            'Student account has been activated.' in self.browser.contents)
1644        self.assertFalse(
1645            'Base Data (account deactivated)' in self.browser.contents)
1646        self.assertFalse(self.student.suspended)
1647        # History messages have been added ...
1648        self.browser.getLink("History").click()
1649        self.assertTrue(
1650            'Student account deactivated by Manager<br />' in self.browser.contents)
1651        self.assertTrue(
1652            'Student account activated by Manager<br />' in self.browser.contents)
1653        # ... and actions have been logged.
1654        logfile = os.path.join(
1655            self.app['datacenter'].storage, 'logs', 'students.log')
1656        logcontent = open(logfile).read()
1657        self.assertTrue('zope.mgr - students.browser.StudentDeactivatePage - '
1658                        'K1000000 - account deactivated' in logcontent)
1659        self.assertTrue('zope.mgr - students.browser.StudentActivatePage - '
1660                        'K1000000 - account activated' in logcontent)
1661
1662    def test_manage_student_transfer(self):
1663        # Add second certificate
1664        self.certificate2 = createObject('waeup.Certificate')
1665        self.certificate2.code = u'CERT2'
1666        self.certificate2.study_mode = 'ug_ft'
1667        self.certificate2.start_level = 999
1668        self.certificate2.end_level = 999
1669        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
1670            self.certificate2)
1671
1672        # Add study level to old study course
1673        studylevel = createObject(u'waeup.StudentStudyLevel')
1674        studylevel.level = 200
1675        self.student['studycourse'].addStudentStudyLevel(
1676            self.certificate, studylevel)
1677        studylevel = createObject(u'waeup.StudentStudyLevel')
1678        studylevel.level = 999
1679        self.student['studycourse'].addStudentStudyLevel(
1680            self.certificate, studylevel)
1681
1682        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1683        self.browser.open(self.student_path)
1684        self.browser.getLink("Transfer").click()
1685        self.browser.getControl(name="form.certificate").value = ['CERT2']
1686        self.browser.getControl(name="form.current_session").value = ['2011']
1687        self.browser.getControl(name="form.current_level").value = ['200']
1688        self.browser.getControl("Transfer").click()
1689        self.assertTrue(
1690            'Current level does not match certificate levels'
1691            in self.browser.contents)
1692        self.browser.getControl(name="form.current_level").value = ['999']
1693        self.browser.getControl("Transfer").click()
1694        self.assertTrue('Successfully transferred' in self.browser.contents)
1695        # The catalog has been updated
1696        cat = queryUtility(ICatalog, name='students_catalog')
1697        results = list(
1698            cat.searchResults(
1699            certcode=('CERT2', 'CERT2')))
1700        self.assertTrue(results[0] is self.student)
1701        results = list(
1702            cat.searchResults(
1703            current_session=(2011, 2011)))
1704        self.assertTrue(results[0] is self.student)
1705        # Add study level to new study course
1706        studylevel = createObject(u'waeup.StudentStudyLevel')
1707        studylevel.level = 999
1708        self.student['studycourse'].addStudentStudyLevel(
1709            self.certificate, studylevel)
1710
1711        # Edit and add pages are locked for old study courses
1712        self.browser.open(self.student_path + '/studycourse/manage')
1713        self.assertFalse('The requested form is locked' in self.browser.contents)
1714        self.browser.open(self.student_path + '/studycourse_1/manage')
1715        self.assertTrue('The requested form is locked' in self.browser.contents)
1716
1717        self.browser.open(self.student_path + '/studycourse/start_session')
1718        self.assertFalse('The requested form is locked' in self.browser.contents)
1719        self.browser.open(self.student_path + '/studycourse_1/start_session')
1720        self.assertTrue('The requested form is locked' in self.browser.contents)
1721
1722        IWorkflowState(self.student).setState('school fee paid')
1723        self.browser.open(self.student_path + '/studycourse/add')
1724        self.assertFalse('The requested form is locked' in self.browser.contents)
1725        self.browser.open(self.student_path + '/studycourse_1/add')
1726        self.assertTrue('The requested form is locked' in self.browser.contents)
1727
1728        self.browser.open(self.student_path + '/studycourse/999/manage')
1729        self.assertFalse('The requested form is locked' in self.browser.contents)
1730        self.browser.open(self.student_path + '/studycourse_1/999/manage')
1731        self.assertTrue('The requested form is locked' in self.browser.contents)
1732
1733        self.browser.open(self.student_path + '/studycourse/999/validate_courses')
1734        self.assertFalse('The requested form is locked' in self.browser.contents)
1735        self.browser.open(self.student_path + '/studycourse_1/999/validate_courses')
1736        self.assertTrue('The requested form is locked' in self.browser.contents)
1737
1738        self.browser.open(self.student_path + '/studycourse/999/reject_courses')
1739        self.assertFalse('The requested form is locked' in self.browser.contents)
1740        self.browser.open(self.student_path + '/studycourse_1/999/reject_courses')
1741        self.assertTrue('The requested form is locked' in self.browser.contents)
1742
1743        self.browser.open(self.student_path + '/studycourse/999/add')
1744        self.assertFalse('The requested form is locked' in self.browser.contents)
1745        self.browser.open(self.student_path + '/studycourse_1/999/add')
1746        self.assertTrue('The requested form is locked' in self.browser.contents)
1747
1748        self.browser.open(self.student_path + '/studycourse/999/edit')
1749        self.assertFalse('The requested form is locked' in self.browser.contents)
1750        self.browser.open(self.student_path + '/studycourse_1/999/edit')
1751        self.assertTrue('The requested form is locked' in self.browser.contents)
1752
1753        # Revert transfer
1754        self.browser.open(self.student_path + '/studycourse_1')
1755        self.browser.getLink("Reactivate").click()
1756        self.browser.getControl("Revert now").click()
1757        self.assertTrue('Previous transfer reverted' in self.browser.contents)
1758        results = list(
1759            cat.searchResults(
1760            certcode=('CERT1', 'CERT1')))
1761        self.assertTrue(results[0] is self.student)
1762        self.assertEqual([i for i in self.student.keys()],
1763            [u'accommodation', u'payments', u'studycourse'])
1764
1765    def test_login_as_student(self):
1766        # StudentImpersonators can login as student
1767        # Create clearance officer
1768        self.app['users'].addUser('mrofficer', 'mrofficersecret')
1769        self.app['users']['mrofficer'].email = 'mrofficer@foo.ng'
1770        self.app['users']['mrofficer'].title = 'Harry Actor'
1771        prmglobal = IPrincipalRoleManager(self.app)
1772        prmglobal.assignRoleToPrincipal('waeup.StudentImpersonator', 'mrofficer')
1773        prmglobal.assignRoleToPrincipal('waeup.StudentsManager', 'mrofficer')
1774        # Login as student impersonator
1775        self.browser.open(self.login_path)
1776        self.browser.getControl(name="form.login").value = 'mrofficer'
1777        self.browser.getControl(name="form.password").value = 'mrofficersecret'
1778        self.browser.getControl("Login").click()
1779        self.assertMatches('...You logged in...', self.browser.contents)
1780        self.browser.open(self.student_path)
1781        self.browser.getLink("Login as").click()
1782        self.browser.getControl("Set password now").click()
1783        temp_password = self.browser.getControl(name='form.password').value
1784        self.browser.getControl("Login now").click()
1785        self.assertMatches(
1786            '...You successfully logged in as...', self.browser.contents)
1787        # We are logged in as student and can see the 'My Data' tab
1788        self.assertMatches(
1789            '...<a href="#" class="dropdown-toggle">My Data</a>...',
1790            self.browser.contents)
1791        self.browser.getLink("Logout").click()
1792        # The student can't login with the original password ...
1793        self.browser.open(self.login_path)
1794        self.browser.getControl(name="form.login").value = self.student_id
1795        self.browser.getControl(name="form.password").value = 'spwd'
1796        self.browser.getControl("Login").click()
1797        self.assertMatches(
1798            '...Your account has been temporarily deactivated...',
1799            self.browser.contents)
1800        # ... but with the temporary password
1801        self.browser.open(self.login_path)
1802        self.browser.getControl(name="form.login").value = self.student_id
1803        self.browser.getControl(name="form.password").value = temp_password
1804        self.browser.getControl("Login").click()
1805        self.assertMatches('...You logged in...', self.browser.contents)
1806        # Creation of temp_password is properly logged
1807        logfile = os.path.join(
1808            self.app['datacenter'].storage, 'logs', 'students.log')
1809        logcontent = open(logfile).read()
1810        self.assertTrue(
1811            'mrofficer - students.browser.LoginAsStudentStep1 - K1000000 - '
1812            'temp_password generated: %s' % temp_password in logcontent)
1813
1814    def test_transcrtipts(self):
1815        self.student.date_of_birth = date(1981, 2, 4)
1816        studylevel = createObject(u'waeup.StudentStudyLevel')
1817        studylevel.level = 100
1818        studylevel.level_session = 2005
1819        self.student['studycourse'].entry_mode = 'ug_ft'
1820        self.student['studycourse'].addStudentStudyLevel(
1821            self.certificate, studylevel)
1822        # Add second course
1823        courseticket = createObject('waeup.CourseTicket')
1824        courseticket.code = 'ANYCODE'
1825        courseticket.title = u'Any TITLE'
1826        courseticket.credits = 13
1827        courseticket.score = 66
1828        courseticket.semester = 1
1829        courseticket.dcode = u'ANYDCODE'
1830        courseticket.fcode = u'ANYFCODE'
1831        self.student['studycourse']['100']['COURSE2'] = courseticket
1832        self.student['studycourse']['100']['COURSE1'].score = 55
1833        self.assertEqual(self.student['studycourse']['100'].gpa, 3.57)
1834        # Get transcript data
1835        td = self.student['studycourse'].getTranscriptData()
1836        self.assertEqual(td[0][0]['level_key'], '100')
1837        self.assertEqual(td[0][0]['level'].level, 100)
1838        self.assertEqual(td[0][0]['level'].level_session, 2005)
1839        self.assertEqual(td[0][0]['tickets_1'][0].code, 'ANYCODE')
1840        self.assertEqual(td[0][0]['tickets_1'][1].code, 'COURSE1')
1841        self.assertEqual(td[1], 3.57)
1842        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1843        self.browser.open(self.student_path + '/studycourse/transcript')
1844        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1845        self.assertTrue('Transcript' in self.browser.contents)
1846        # Officers can open the pdf transcript
1847        self.browser.open(self.student_path + '/studycourse/transcript.pdf')
1848        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1849        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1850        path = os.path.join(samples_dir(), 'transcript.pdf')
1851        open(path, 'wb').write(self.browser.contents)
1852
1853class StudentUITests(StudentsFullSetup):
1854    # Tests for Student class views and pages
1855
1856    def test_student_change_password(self):
1857        # Students can change the password
1858        self.student.personal_updated = datetime.utcnow()
1859        self.browser.open(self.login_path)
1860        self.browser.getControl(name="form.login").value = self.student_id
1861        self.browser.getControl(name="form.password").value = 'spwd'
1862        self.browser.getControl("Login").click()
1863        self.assertEqual(self.browser.url, self.student_path)
1864        self.assertTrue('You logged in' in self.browser.contents)
1865        # Change password
1866        self.browser.getLink("Change password").click()
1867        self.browser.getControl(name="change_password").value = 'pw'
1868        self.browser.getControl(
1869            name="change_password_repeat").value = 'pw'
1870        self.browser.getControl("Save").click()
1871        self.assertTrue('Password must have at least' in self.browser.contents)
1872        self.browser.getControl(name="change_password").value = 'new_password'
1873        self.browser.getControl(
1874            name="change_password_repeat").value = 'new_passssword'
1875        self.browser.getControl("Save").click()
1876        self.assertTrue('Passwords do not match' in self.browser.contents)
1877        self.browser.getControl(name="change_password").value = 'new_password'
1878        self.browser.getControl(
1879            name="change_password_repeat").value = 'new_password'
1880        self.browser.getControl("Save").click()
1881        self.assertTrue('Password changed' in self.browser.contents)
1882        # We are still logged in. Changing the password hasn't thrown us out.
1883        self.browser.getLink("Base Data").click()
1884        self.assertEqual(self.browser.url, self.student_path)
1885        # We can logout
1886        self.browser.getLink("Logout").click()
1887        self.assertTrue('You have been logged out' in self.browser.contents)
1888        self.assertEqual(self.browser.url, 'http://localhost/app')
1889        # We can login again with the new password
1890        self.browser.getLink("Login").click()
1891        self.browser.open(self.login_path)
1892        self.browser.getControl(name="form.login").value = self.student_id
1893        self.browser.getControl(name="form.password").value = 'new_password'
1894        self.browser.getControl("Login").click()
1895        self.assertEqual(self.browser.url, self.student_path)
1896        self.assertTrue('You logged in' in self.browser.contents)
1897        return
1898
1899    def test_setpassword(self):
1900        # Set password for first-time access
1901        student = Student()
1902        student.reg_number = u'123456'
1903        student.firstname = u'Klaus'
1904        student.lastname = u'Tester'
1905        self.app['students'].addStudent(student)
1906        setpassword_path = 'http://localhost/app/setpassword'
1907        student_path = 'http://localhost/app/students/%s' % student.student_id
1908        self.browser.open(setpassword_path)
1909        self.browser.getControl(name="ac_series").value = self.existing_pwdseries
1910        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
1911        self.browser.getControl(name="reg_number").value = '223456'
1912        self.browser.getControl("Set").click()
1913        self.assertMatches('...No student found...',
1914                           self.browser.contents)
1915        self.browser.getControl(name="reg_number").value = '123456'
1916        self.browser.getControl(name="ac_number").value = '999999'
1917        self.browser.getControl("Set").click()
1918        self.assertMatches('...Access code is invalid...',
1919                           self.browser.contents)
1920        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
1921        self.browser.getControl("Set").click()
1922        self.assertMatches('...Password has been set. Your Student Id is...',
1923                           self.browser.contents)
1924        self.browser.getControl("Set").click()
1925        self.assertMatches(
1926            '...Password has already been set. Your Student Id is...',
1927            self.browser.contents)
1928        existing_pwdpin = self.pwdpins[1]
1929        parts = existing_pwdpin.split('-')[1:]
1930        existing_pwdseries, existing_pwdnumber = parts
1931        self.browser.getControl(name="ac_series").value = existing_pwdseries
1932        self.browser.getControl(name="ac_number").value = existing_pwdnumber
1933        self.browser.getControl(name="reg_number").value = '123456'
1934        self.browser.getControl("Set").click()
1935        self.assertMatches(
1936            '...You are using the wrong Access Code...',
1937            self.browser.contents)
1938        # The student can login with the new credentials
1939        self.browser.open(self.login_path)
1940        self.browser.getControl(name="form.login").value = student.student_id
1941        self.browser.getControl(
1942            name="form.password").value = self.existing_pwdnumber
1943        self.browser.getControl("Login").click()
1944        self.assertEqual(self.browser.url, student_path)
1945        self.assertTrue('You logged in' in self.browser.contents)
1946        return
1947
1948    def test_student_login(self):
1949        # Student cant login if their password is not set
1950        self.student.password = None
1951        self.browser.open(self.login_path)
1952        self.browser.getControl(name="form.login").value = self.student_id
1953        self.browser.getControl(name="form.password").value = 'spwd'
1954        self.browser.getControl("Login").click()
1955        self.assertTrue(
1956            'You entered invalid credentials.' in self.browser.contents)
1957        # We set the password again
1958        IUserAccount(
1959            self.app['students'][self.student_id]).setPassword('spwd')
1960        # Students can't login if their account is suspended/deactivated
1961        self.student.suspended = True
1962        self.browser.open(self.login_path)
1963        self.browser.getControl(name="form.login").value = self.student_id
1964        self.browser.getControl(name="form.password").value = 'spwd'
1965        self.browser.getControl("Login").click()
1966        self.assertMatches(
1967            '...<div class="alert-message warning">'
1968            'Your account has been deactivated.</div>...', self.browser.contents)
1969        # If suspended_comment is set this message will be flashed instead
1970        self.student.suspended_comment = u'Aetsch baetsch!'
1971        self.browser.getControl(name="form.login").value = self.student_id
1972        self.browser.getControl(name="form.password").value = 'spwd'
1973        self.browser.getControl("Login").click()
1974        self.assertMatches(
1975            '...<div class="alert-message warning">Aetsch baetsch!</div>...',
1976            self.browser.contents)
1977        self.student.suspended = False
1978        # Students can't login if a temporary password has been set and
1979        # is not expired
1980        self.app['students'][self.student_id].setTempPassword(
1981            'anybody', 'temp_spwd')
1982        self.browser.open(self.login_path)
1983        self.browser.getControl(name="form.login").value = self.student_id
1984        self.browser.getControl(name="form.password").value = 'spwd'
1985        self.browser.getControl("Login").click()
1986        self.assertMatches(
1987            '...Your account has been temporarily deactivated...',
1988            self.browser.contents)
1989        # The student can login with the temporary password
1990        self.browser.open(self.login_path)
1991        self.browser.getControl(name="form.login").value = self.student_id
1992        self.browser.getControl(name="form.password").value = 'temp_spwd'
1993        self.browser.getControl("Login").click()
1994        self.assertMatches(
1995            '...You logged in...', self.browser.contents)
1996        # Student can view the base data
1997        self.browser.open(self.student_path)
1998        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1999        self.assertEqual(self.browser.url, self.student_path)
2000        # When the password expires ...
2001        delta = timedelta(minutes=11)
2002        self.app['students'][self.student_id].temp_password[
2003            'timestamp'] = datetime.utcnow() - delta
2004        self.app['students'][self.student_id]._p_changed = True
2005        # ... the student will be automatically logged out
2006        self.assertRaises(
2007            Unauthorized, self.browser.open, self.student_path)
2008        # Then the student can login with the original password
2009        self.browser.open(self.login_path)
2010        self.browser.getControl(name="form.login").value = self.student_id
2011        self.browser.getControl(name="form.password").value = 'spwd'
2012        self.browser.getControl("Login").click()
2013        self.assertMatches(
2014            '...You logged in...', self.browser.contents)
2015
2016    def test_student_clearance(self):
2017        # Student cant login if their password is not set
2018        IWorkflowInfo(self.student).fireTransition('admit')
2019        self.browser.open(self.login_path)
2020        self.browser.getControl(name="form.login").value = self.student_id
2021        self.browser.getControl(name="form.password").value = 'spwd'
2022        self.browser.getControl("Login").click()
2023        self.assertMatches(
2024            '...You logged in...', self.browser.contents)
2025        # Admitted student can upload a passport picture
2026        self.browser.open(self.student_path + '/change_portrait')
2027        ctrl = self.browser.getControl(name='passportuploadedit')
2028        file_obj = open(SAMPLE_IMAGE, 'rb')
2029        file_ctrl = ctrl.mech_control
2030        file_ctrl.add_file(file_obj, filename='my_photo.jpg')
2031        self.browser.getControl(
2032            name='upload_passportuploadedit').click()
2033        self.assertTrue(
2034            '<img align="middle" height="125px" src="passport.jpg" />'
2035            in self.browser.contents)
2036        # Students can open admission letter
2037        self.browser.getLink("Base Data").click()
2038        self.browser.getLink("Download admission letter").click()
2039        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2040        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2041        # Student can view the clearance data
2042        self.browser.open(self.student_path)
2043        self.browser.getLink("Clearance Data").click()
2044        # Student can't open clearance edit form before starting clearance
2045        self.browser.open(self.student_path + '/cedit')
2046        self.assertMatches('...The requested form is locked...',
2047                           self.browser.contents)
2048        self.browser.getLink("Clearance Data").click()
2049        self.browser.getLink("Start clearance").click()
2050        self.student.email = None
2051        # Uups, we forgot to fill the email fields
2052        self.browser.getControl("Start clearance").click()
2053        self.assertMatches('...Not all required fields filled...',
2054                           self.browser.contents)
2055        self.browser.open(self.student_path + '/edit_base')
2056        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
2057        self.browser.getControl("Save").click()
2058        self.browser.open(self.student_path + '/start_clearance')
2059        self.browser.getControl(name="ac_series").value = '3'
2060        self.browser.getControl(name="ac_number").value = '4444444'
2061        self.browser.getControl("Start clearance now").click()
2062        self.assertMatches('...Activation code is invalid...',
2063                           self.browser.contents)
2064        self.browser.getControl(name="ac_series").value = self.existing_clrseries
2065        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
2066        # Owner is Hans Wurst, AC can't be invalidated
2067        self.browser.getControl("Start clearance now").click()
2068        self.assertMatches('...You are not the owner of this access code...',
2069                           self.browser.contents)
2070        # Set the correct owner
2071        self.existing_clrac.owner = self.student_id
2072        # clr_code might be set (and thus returns None) due importing
2073        # an empty clr_code column.
2074        self.student.clr_code = None
2075        self.browser.getControl("Start clearance now").click()
2076        self.assertMatches('...Clearance process has been started...',
2077                           self.browser.contents)
2078        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
2079        self.browser.getControl("Save", index=0).click()
2080        # Student can view the clearance data
2081        self.browser.getLink("Clearance Data").click()
2082        # and go back to the edit form
2083        self.browser.getLink("Edit").click()
2084        # Students can upload documents
2085        ctrl = self.browser.getControl(name='birthcertificateupload')
2086        file_obj = open(SAMPLE_IMAGE, 'rb')
2087        file_ctrl = ctrl.mech_control
2088        file_ctrl.add_file(file_obj, filename='my_birth_certificate.jpg')
2089        self.browser.getControl(
2090            name='upload_birthcertificateupload').click()
2091        self.assertTrue(
2092            '<a target="image" href="birth_certificate">Birth Certificate Scan</a>'
2093            in self.browser.contents)
2094        # Students can open clearance slip
2095        self.browser.getLink("View").click()
2096        self.browser.getLink("Download clearance slip").click()
2097        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2098        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2099        # Students can request clearance
2100        self.browser.open(self.edit_clearance_path)
2101        self.browser.getControl("Save and request clearance").click()
2102        self.browser.getControl(name="ac_series").value = self.existing_clrseries
2103        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
2104        self.browser.getControl("Request clearance now").click()
2105        self.assertMatches('...Clearance has been requested...',
2106                           self.browser.contents)
2107        # Student can't reopen clearance form after requesting clearance
2108        self.browser.open(self.student_path + '/cedit')
2109        self.assertMatches('...The requested form is locked...',
2110                           self.browser.contents)
2111
2112    def test_student_course_registration(self):
2113        # Student cant login if their password is not set
2114        IWorkflowInfo(self.student).fireTransition('admit')
2115        self.browser.open(self.login_path)
2116        self.browser.getControl(name="form.login").value = self.student_id
2117        self.browser.getControl(name="form.password").value = 'spwd'
2118        self.browser.getControl("Login").click()
2119        # Student can't add study level if not in state 'school fee paid'
2120        self.browser.open(self.student_path + '/studycourse/add')
2121        self.assertMatches('...The requested form is locked...',
2122                           self.browser.contents)
2123        # ... and must be transferred first
2124        IWorkflowState(self.student).setState('school fee paid')
2125        # Now students can add the current study level
2126        self.browser.getLink("Study Course").click()
2127        self.browser.getLink("Add course list").click()
2128        self.assertMatches('...Add current level 100 (Year 1)...',
2129                           self.browser.contents)
2130        self.browser.getControl("Create course list now").click()
2131        # A level with one course ticket was created
2132        self.assertEqual(self.student['studycourse']['100'].number_of_tickets, 1)
2133        self.browser.getLink("100").click()
2134        self.browser.getLink("Edit course list").click()
2135        self.browser.getLink("here").click()
2136        self.browser.getControl(name="form.course").value = ['COURSE1']
2137        self.browser.getControl("Add course ticket").click()
2138        self.assertMatches('...The ticket exists...',
2139                           self.browser.contents)
2140        self.student['studycourse'].current_level = 200
2141        self.browser.getLink("Study Course").click()
2142        self.browser.getLink("Add course list").click()
2143        self.assertMatches('...Add current level 200 (Year 2)...',
2144                           self.browser.contents)
2145        self.browser.getControl("Create course list now").click()
2146        self.browser.getLink("200").click()
2147        self.browser.getLink("Edit course list").click()
2148        self.browser.getLink("here").click()
2149        self.browser.getControl(name="form.course").value = ['COURSE1']
2150        self.course.credits = 100
2151        self.browser.getControl("Add course ticket").click()
2152        self.assertMatches(
2153            '...Total credits exceed 50...', self.browser.contents)
2154        self.course.credits = 10
2155        self.browser.getControl("Add course ticket").click()
2156        self.assertMatches('...The ticket exists...',
2157                           self.browser.contents)
2158        # Indeed the ticket exists as carry-over course from level 100
2159        # since its score was 0
2160        self.assertTrue(
2161            self.student['studycourse']['200']['COURSE1'].carry_over is True)
2162        # Students can open the pdf course registration slip
2163        self.browser.open(self.student_path + '/studycourse/200')
2164        self.browser.getLink("Download course registration slip").click()
2165        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2166        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2167        # Students can remove course tickets
2168        self.browser.open(self.student_path + '/studycourse/200/edit')
2169        self.browser.getControl("Remove selected", index=0).click()
2170        self.assertTrue('No ticket selected' in self.browser.contents)
2171        # No ticket can be selected since the carry-over course is a core course
2172        self.assertRaises(
2173            LookupError, self.browser.getControl, name='val_id')
2174        self.student['studycourse']['200']['COURSE1'].mandatory = False
2175        self.browser.open(self.student_path + '/studycourse/200/edit')
2176        # Course list can't be registered if total_credits exceeds max_credits
2177        self.student['studycourse']['200']['COURSE1'].credits = 60
2178        self.browser.getControl("Register course list").click()
2179        self.assertTrue('Maximum credits of 50 exceeded' in self.browser.contents)
2180        # Student can now remove the ticket
2181        ctrl = self.browser.getControl(name='val_id')
2182        ctrl.getControl(value='COURSE1').selected = True
2183        self.browser.getControl("Remove selected", index=0).click()
2184        self.assertTrue('Successfully removed' in self.browser.contents)
2185        # Removing course tickets is properly logged
2186        logfile = os.path.join(
2187            self.app['datacenter'].storage, 'logs', 'students.log')
2188        logcontent = open(logfile).read()
2189        self.assertTrue('K1000000 - students.browser.StudyLevelEditFormPage '
2190        '- K1000000 - removed: COURSE1 at 200' in logcontent)
2191        # They can add the same ticket using the edit page directly.
2192        # We can do the same by adding the course on the manage page directly
2193        self.browser.getControl(name="course").value = 'COURSE1'
2194        self.browser.getControl("Add course ticket").click()
2195        # Adding course tickets is logged
2196        logfile = os.path.join(
2197            self.app['datacenter'].storage, 'logs', 'students.log')
2198        logcontent = open(logfile).read()
2199        self.assertTrue('K1000000 - students.browser.StudyLevelEditFormPage - '
2200            'K1000000 - added: COURSE1|200|2004' in logcontent)
2201        # Course list can be registered
2202        self.browser.getControl("Register course list").click()
2203        self.assertTrue('Course list has been registered' in self.browser.contents)
2204        self.assertEqual(self.student.state, 'courses registered')
2205        # Students can open the pdf transcript
2206        self.browser.open(self.student_path + '/studycourse/transcript.pdf')
2207        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2208        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2209        return
2210
2211    def test_postgraduate_student_access(self):
2212        self.certificate.study_mode = 'pg_ft'
2213        self.certificate.start_level = 999
2214        self.certificate.end_level = 999
2215        self.student['studycourse'].current_level = 999
2216        IWorkflowState(self.student).setState('school fee paid')
2217        self.browser.open(self.login_path)
2218        self.browser.getControl(name="form.login").value = self.student_id
2219        self.browser.getControl(name="form.password").value = 'spwd'
2220        self.browser.getControl("Login").click()
2221        self.assertTrue(
2222            'You logged in.' in self.browser.contents)
2223        # Now students can add the current study level
2224        self.browser.getLink("Study Course").click()
2225        self.browser.getLink("Add course list").click()
2226        self.assertMatches('...Add current level Postgraduate Level...',
2227                           self.browser.contents)
2228        self.browser.getControl("Create course list now").click()
2229        # A level with one course ticket was created
2230        self.assertEqual(self.student['studycourse']['999'].number_of_tickets, 0)
2231        self.browser.getLink("999").click()
2232        self.browser.getLink("Edit course list").click()
2233        self.browser.getLink("here").click()
2234        self.browser.getControl(name="form.course").value = ['COURSE1']
2235        self.browser.getControl("Add course ticket").click()
2236        self.assertMatches('...Successfully added COURSE1...',
2237                           self.browser.contents)
2238        # Postgraduate students can't register course lists
2239        self.browser.getControl("Register course list").click()
2240        self.assertTrue("your course list can't bee registered"
2241            in self.browser.contents)
2242        self.assertEqual(self.student.state, 'school fee paid')
2243        return
2244
2245    def test_student_clearance_wo_clrcode(self):
2246        IWorkflowState(self.student).setState('clearance started')
2247        self.browser.open(self.login_path)
2248        self.browser.getControl(name="form.login").value = self.student_id
2249        self.browser.getControl(name="form.password").value = 'spwd'
2250        self.browser.getControl("Login").click()
2251        self.student.clearance_locked = False
2252        self.browser.open(self.edit_clearance_path)
2253        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
2254        self.browser.getControl("Save and request clearance").click()
2255        self.assertMatches('...Clearance has been requested...',
2256                           self.browser.contents)
2257
2258    def test_student_clearance_payment(self):
2259        # Login
2260        self.browser.open(self.login_path)
2261        self.browser.getControl(name="form.login").value = self.student_id
2262        self.browser.getControl(name="form.password").value = 'spwd'
2263        self.browser.getControl("Login").click()
2264
2265        # Students can add online clearance payment tickets
2266        self.browser.open(self.payments_path + '/addop')
2267        self.browser.getControl(name="form.p_category").value = ['clearance']
2268        self.browser.getControl("Create ticket").click()
2269        self.assertMatches('...ticket created...',
2270                           self.browser.contents)
2271
2272        # Students can't approve the payment
2273        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
2274        ctrl = self.browser.getControl(name='val_id')
2275        value = ctrl.options[0]
2276        self.browser.getLink(value).click()
2277        payment_url = self.browser.url
2278        self.assertRaises(
2279            Unauthorized, self.browser.open, payment_url + '/approve')
2280        # In the base package they can 'use' a fake approval view.
2281        # XXX: I tried to use
2282        # self.student['payments'][value].approveStudentPayment() instead.
2283        # But this function fails in
2284        # w.k.accesscodes.accesscode.create_accesscode.
2285        # grok.getSite returns None in tests.
2286        self.browser.open(payment_url + '/fake_approve')
2287        self.assertMatches('...Payment approved...',
2288                          self.browser.contents)
2289        expected = '''...
2290        <td>
2291          <span>Paid</span>
2292        </td>...'''
2293        expected = '''...
2294        <td>
2295          <span>Paid</span>
2296        </td>...'''
2297        self.assertMatches(expected,self.browser.contents)
2298        payment_id = self.student['payments'].keys()[0]
2299        payment = self.student['payments'][payment_id]
2300        self.assertEqual(payment.p_state, 'paid')
2301        self.assertEqual(payment.r_amount_approved, 3456.0)
2302        self.assertEqual(payment.r_code, 'AP')
2303        self.assertEqual(payment.r_desc, u'Payment approved by Anna Tester')
2304        # The new CLR-0 pin has been created
2305        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
2306        pin = self.app['accesscodes']['CLR-0'].keys()[0]
2307        ac = self.app['accesscodes']['CLR-0'][pin]
2308        self.assertEqual(ac.owner, self.student_id)
2309        self.assertEqual(ac.cost, 3456.0)
2310
2311        # Students can open the pdf payment slip
2312        self.browser.open(payment_url + '/payment_slip.pdf')
2313        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2314        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2315
2316        # The new CLR-0 pin can be used for starting clearance
2317        # but they have to upload a passport picture first
2318        # which is only possible in state admitted
2319        self.browser.open(self.student_path + '/change_portrait')
2320        self.assertMatches('...form is locked...',
2321                          self.browser.contents)
2322        IWorkflowInfo(self.student).fireTransition('admit')
2323        self.browser.open(self.student_path + '/change_portrait')
2324        image = open(SAMPLE_IMAGE, 'rb')
2325        ctrl = self.browser.getControl(name='passportuploadedit')
2326        file_ctrl = ctrl.mech_control
2327        file_ctrl.add_file(image, filename='my_photo.jpg')
2328        self.browser.getControl(
2329            name='upload_passportuploadedit').click()
2330        self.browser.open(self.student_path + '/start_clearance')
2331        parts = pin.split('-')[1:]
2332        clrseries, clrnumber = parts
2333        self.browser.getControl(name="ac_series").value = clrseries
2334        self.browser.getControl(name="ac_number").value = clrnumber
2335        self.browser.getControl("Start clearance now").click()
2336        self.assertMatches('...Clearance process has been started...',
2337                           self.browser.contents)
2338
2339    def test_student_schoolfee_payment(self):
2340        configuration = createObject('waeup.SessionConfiguration')
2341        configuration.academic_session = 2005
2342        self.app['configuration'].addSessionConfiguration(configuration)
2343        # Login
2344        self.browser.open(self.login_path)
2345        self.browser.getControl(name="form.login").value = self.student_id
2346        self.browser.getControl(name="form.password").value = 'spwd'
2347        self.browser.getControl("Login").click()
2348
2349        # Students can add online school fee payment tickets.
2350        IWorkflowState(self.student).setState('returning')
2351        self.browser.open(self.payments_path)
2352        self.assertRaises(
2353            LookupError, self.browser.getControl, name='val_id')
2354        self.browser.getLink("Add current session payment ticket").click()
2355        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2356        self.browser.getControl("Create ticket").click()
2357        self.assertMatches('...ticket created...',
2358                           self.browser.contents)
2359        ctrl = self.browser.getControl(name='val_id')
2360        value = ctrl.options[0]
2361        self.browser.getLink(value).click()
2362        self.assertMatches('...Amount Authorized...',
2363                           self.browser.contents)
2364        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2365        # Payment session and will be calculated as defined
2366        # in w.k.students.utils because we set changed the state
2367        # to returning
2368        self.assertEqual(self.student['payments'][value].p_session, 2005)
2369        self.assertEqual(self.student['payments'][value].p_level, 200)
2370
2371        # Student is the payer of the payment ticket.
2372        payer = IPayer(self.student['payments'][value])
2373        self.assertEqual(payer.display_fullname, 'Anna Tester')
2374        self.assertEqual(payer.id, self.student_id)
2375        self.assertEqual(payer.faculty, 'fac1')
2376        self.assertEqual(payer.department, 'dep1')
2377
2378        # We simulate the approval
2379        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
2380        self.browser.open(self.browser.url + '/fake_approve')
2381        self.assertMatches('...Payment approved...',
2382                          self.browser.contents)
2383
2384        # The new SFE-0 pin can be used for starting new session
2385        self.browser.open(self.studycourse_path)
2386        self.browser.getLink('Start new session').click()
2387        pin = self.app['accesscodes']['SFE-0'].keys()[0]
2388        parts = pin.split('-')[1:]
2389        sfeseries, sfenumber = parts
2390        self.browser.getControl(name="ac_series").value = sfeseries
2391        self.browser.getControl(name="ac_number").value = sfenumber
2392        self.browser.getControl("Start now").click()
2393        self.assertMatches('...Session started...',
2394                           self.browser.contents)
2395        self.assertTrue(self.student.state == 'school fee paid')
2396        return
2397
2398    def test_student_bedallocation_payment(self):
2399        # Login
2400        self.browser.open(self.login_path)
2401        self.browser.getControl(name="form.login").value = self.student_id
2402        self.browser.getControl(name="form.password").value = 'spwd'
2403        self.browser.getControl("Login").click()
2404        self.browser.open(self.payments_path)
2405        self.browser.open(self.payments_path + '/addop')
2406        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
2407        self.browser.getControl("Create ticket").click()
2408        self.assertMatches('...ticket created...',
2409                           self.browser.contents)
2410        # Students can remove only online payment tickets which have
2411        # not received a valid callback
2412        self.browser.open(self.payments_path)
2413        ctrl = self.browser.getControl(name='val_id')
2414        value = ctrl.options[0]
2415        ctrl.getControl(value=value).selected = True
2416        self.browser.getControl("Remove selected", index=0).click()
2417        self.assertTrue('Successfully removed' in self.browser.contents)
2418
2419    def test_student_maintenance_payment(self):
2420        # Login
2421        self.browser.open(self.login_path)
2422        self.browser.getControl(name="form.login").value = self.student_id
2423        self.browser.getControl(name="form.password").value = 'spwd'
2424        self.browser.getControl("Login").click()
2425        self.browser.open(self.payments_path)
2426        self.browser.open(self.payments_path + '/addop')
2427        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
2428        self.browser.getControl("Create ticket").click()
2429        self.assertMatches('...You have not yet booked accommodation...',
2430                           self.browser.contents)
2431        # We continue this test in test_student_accommodation
2432
2433    def test_student_previous_payments(self):
2434        configuration = createObject('waeup.SessionConfiguration')
2435        configuration.academic_session = 2000
2436        configuration.clearance_fee = 3456.0
2437        configuration.booking_fee = 123.4
2438        self.app['configuration'].addSessionConfiguration(configuration)
2439        configuration2 = createObject('waeup.SessionConfiguration')
2440        configuration2.academic_session = 2003
2441        configuration2.clearance_fee = 3456.0
2442        configuration2.booking_fee = 123.4
2443        self.app['configuration'].addSessionConfiguration(configuration2)
2444        configuration3 = createObject('waeup.SessionConfiguration')
2445        configuration3.academic_session = 2005
2446        configuration3.clearance_fee = 3456.0
2447        configuration3.booking_fee = 123.4
2448        self.app['configuration'].addSessionConfiguration(configuration3)
2449        self.student['studycourse'].entry_session = 2002
2450
2451        # Login
2452        self.browser.open(self.login_path)
2453        self.browser.getControl(name="form.login").value = self.student_id
2454        self.browser.getControl(name="form.password").value = 'spwd'
2455        self.browser.getControl("Login").click()
2456
2457        # Students can add previous school fee payment tickets in any state.
2458        IWorkflowState(self.student).setState('courses registered')
2459        self.browser.open(self.payments_path)
2460        self.browser.getLink("Add previous session payment ticket").click()
2461
2462        # Previous session payment form is provided
2463        self.assertEqual(self.student.current_session, 2004)
2464        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2465        self.browser.getControl(name="form.p_session").value = ['2000']
2466        self.browser.getControl(name="form.p_level").value = ['300']
2467        self.browser.getControl("Create ticket").click()
2468        self.assertMatches('...The previous session must not fall below...',
2469                           self.browser.contents)
2470        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2471        self.browser.getControl(name="form.p_session").value = ['2005']
2472        self.browser.getControl(name="form.p_level").value = ['300']
2473        self.browser.getControl("Create ticket").click()
2474        self.assertMatches('...This is not a previous session...',
2475                           self.browser.contents)
2476        # Students can pay current session school fee.
2477        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2478        self.browser.getControl(name="form.p_session").value = ['2004']
2479        self.browser.getControl(name="form.p_level").value = ['300']
2480        self.browser.getControl("Create ticket").click()
2481        self.assertMatches('...ticket created...',
2482                           self.browser.contents)
2483        ctrl = self.browser.getControl(name='val_id')
2484        value = ctrl.options[0]
2485        self.browser.getLink(value).click()
2486        self.assertMatches('...Amount Authorized...',
2487                           self.browser.contents)
2488        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2489
2490        # Payment session is properly set
2491        self.assertEqual(self.student['payments'][value].p_session, 2004)
2492        self.assertEqual(self.student['payments'][value].p_level, 300)
2493
2494        # We simulate the approval
2495        self.browser.open(self.browser.url + '/fake_approve')
2496        self.assertMatches('...Payment approved...',
2497                          self.browser.contents)
2498
2499        # No AC has been created
2500        self.assertEqual(len(self.app['accesscodes']['SFE-0'].keys()), 0)
2501        self.assertTrue(self.student['payments'][value].ac is None)
2502
2503        # Current payment flag is set False
2504        self.assertFalse(self.student['payments'][value].p_current)
2505
2506        # Button and form are not available for students who are in
2507        # states up to cleared
2508        self.student['studycourse'].entry_session = 2004
2509        IWorkflowState(self.student).setState('cleared')
2510        self.browser.open(self.payments_path)
2511        self.assertFalse(
2512            "Add previous session payment ticket" in self.browser.contents)
2513        self.browser.open(self.payments_path + '/addpp')
2514        self.assertTrue(
2515            "No previous payment to be made" in self.browser.contents)
2516        return
2517
2518    def test_postgraduate_student_payments(self):
2519        configuration = createObject('waeup.SessionConfiguration')
2520        configuration.academic_session = 2005
2521        self.app['configuration'].addSessionConfiguration(configuration)
2522        self.certificate.study_mode = 'pg_ft'
2523        self.certificate.start_level = 999
2524        self.certificate.end_level = 999
2525        self.student['studycourse'].current_level = 999
2526        # Login
2527        self.browser.open(self.login_path)
2528        self.browser.getControl(name="form.login").value = self.student_id
2529        self.browser.getControl(name="form.password").value = 'spwd'
2530        self.browser.getControl("Login").click()
2531        # Students can add online school fee payment tickets.
2532        IWorkflowState(self.student).setState('cleared')
2533        self.browser.open(self.payments_path)
2534        self.browser.getLink("Add current session payment ticket").click()
2535        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2536        self.browser.getControl("Create ticket").click()
2537        self.assertMatches('...ticket created...',
2538                           self.browser.contents)
2539        ctrl = self.browser.getControl(name='val_id')
2540        value = ctrl.options[0]
2541        self.browser.getLink(value).click()
2542        self.assertMatches('...Amount Authorized...',
2543                           self.browser.contents)
2544        # Payment session and level are current ones.
2545        # Postgrads have to pay school_fee_1.
2546        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
2547        self.assertEqual(self.student['payments'][value].p_session, 2004)
2548        self.assertEqual(self.student['payments'][value].p_level, 999)
2549
2550        # We simulate the approval
2551        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
2552        self.browser.open(self.browser.url + '/fake_approve')
2553        self.assertMatches('...Payment approved...',
2554                          self.browser.contents)
2555
2556        # The new SFE-0 pin can be used for starting session
2557        self.browser.open(self.studycourse_path)
2558        self.browser.getLink('Start new session').click()
2559        pin = self.app['accesscodes']['SFE-0'].keys()[0]
2560        parts = pin.split('-')[1:]
2561        sfeseries, sfenumber = parts
2562        self.browser.getControl(name="ac_series").value = sfeseries
2563        self.browser.getControl(name="ac_number").value = sfenumber
2564        self.browser.getControl("Start now").click()
2565        self.assertMatches('...Session started...',
2566                           self.browser.contents)
2567        self.assertTrue(self.student.state == 'school fee paid')
2568
2569        # Postgrad students do not need to register courses the
2570        # can just pay for the next session.
2571        self.browser.open(self.payments_path)
2572        # Remove first payment to be sure that we access the right ticket
2573        del self.student['payments'][value]
2574        self.browser.getLink("Add current session payment ticket").click()
2575        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2576        self.browser.getControl("Create ticket").click()
2577        ctrl = self.browser.getControl(name='val_id')
2578        value = ctrl.options[0]
2579        self.browser.getLink(value).click()
2580        # Payment session has increased by one, payment level remains the same.
2581        # Returning Postgraduates have to pay school_fee_2.
2582        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2583        self.assertEqual(self.student['payments'][value].p_session, 2005)
2584        self.assertEqual(self.student['payments'][value].p_level, 999)
2585
2586        # Student is still in old session
2587        self.assertEqual(self.student.current_session, 2004)
2588
2589        # We do not need to pay the ticket if any other
2590        # SFE pin is provided
2591        pin_container = self.app['accesscodes']
2592        pin_container.createBatch(
2593            datetime.utcnow(), 'some_userid', 'SFE', 9.99, 1)
2594        pin = pin_container['SFE-1'].values()[0].representation
2595        sfeseries, sfenumber = pin.split('-')[1:]
2596        # The new SFE-1 pin can be used for starting new session
2597        self.browser.open(self.studycourse_path)
2598        self.browser.getLink('Start new session').click()
2599        self.browser.getControl(name="ac_series").value = sfeseries
2600        self.browser.getControl(name="ac_number").value = sfenumber
2601        self.browser.getControl("Start now").click()
2602        self.assertMatches('...Session started...',
2603                           self.browser.contents)
2604        self.assertTrue(self.student.state == 'school fee paid')
2605        # Student is in new session
2606        self.assertEqual(self.student.current_session, 2005)
2607        self.assertEqual(self.student['studycourse'].current_level, 999)
2608        return
2609
2610    def test_student_accommodation(self):
2611        # Login
2612        self.browser.open(self.login_path)
2613        self.browser.getControl(name="form.login").value = self.student_id
2614        self.browser.getControl(name="form.password").value = 'spwd'
2615        self.browser.getControl("Login").click()
2616
2617        # Students can add online booking fee payment tickets and open the
2618        # callback view (see test_manage_payments)
2619        self.browser.getLink("Payments").click()
2620        self.browser.getLink("Add current session payment ticket").click()
2621        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
2622        self.browser.getControl("Create ticket").click()
2623        ctrl = self.browser.getControl(name='val_id')
2624        value = ctrl.options[0]
2625        self.browser.getLink(value).click()
2626        self.browser.open(self.browser.url + '/fake_approve')
2627        # The new HOS-0 pin has been created
2628        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
2629        pin = self.app['accesscodes']['HOS-0'].keys()[0]
2630        ac = self.app['accesscodes']['HOS-0'][pin]
2631        parts = pin.split('-')[1:]
2632        sfeseries, sfenumber = parts
2633
2634        # Students can use HOS code and book a bed space with it ...
2635        self.browser.open(self.acco_path)
2636        # ... but not if booking period has expired ...
2637        self.app['hostels'].enddate = datetime.now(pytz.utc)
2638        self.browser.getLink("Book accommodation").click()
2639        self.assertMatches('...Outside booking period: ...',
2640                           self.browser.contents)
2641        self.app['hostels'].enddate = datetime.now(pytz.utc) + timedelta(days=10)
2642        # ... or student is not the an allowed state ...
2643        self.browser.getLink("Book accommodation").click()
2644        self.assertMatches('...You are in the wrong...',
2645                           self.browser.contents)
2646        IWorkflowInfo(self.student).fireTransition('admit')
2647        self.browser.getLink("Book accommodation").click()
2648        self.assertMatches('...Activation Code:...',
2649                           self.browser.contents)
2650        # Student can't used faked ACs ...
2651        self.browser.getControl(name="ac_series").value = u'nonsense'
2652        self.browser.getControl(name="ac_number").value = sfenumber
2653        self.browser.getControl("Create bed ticket").click()
2654        self.assertMatches('...Activation code is invalid...',
2655                           self.browser.contents)
2656        # ... or ACs owned by somebody else.
2657        ac.owner = u'Anybody'
2658        self.browser.getControl(name="ac_series").value = sfeseries
2659        self.browser.getControl(name="ac_number").value = sfenumber
2660        self.browser.getControl("Create bed ticket").click()
2661        self.assertMatches('...You are not the owner of this access code...',
2662                           self.browser.contents)
2663        ac.owner = self.student_id
2664        self.browser.getControl(name="ac_series").value = sfeseries
2665        self.browser.getControl(name="ac_number").value = sfenumber
2666        self.browser.getControl("Create bed ticket").click()
2667        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
2668                           self.browser.contents)
2669
2670        # Bed has been allocated
2671        bed = self.app['hostels']['hall-1']['hall-1_A_101_A']
2672        self.assertTrue(bed.owner == self.student_id)
2673
2674        # BedTicketAddPage is now blocked
2675        self.browser.getLink("Book accommodation").click()
2676        self.assertMatches('...You already booked a bed space...',
2677            self.browser.contents)
2678
2679        # The bed ticket displays the data correctly
2680        self.browser.open(self.acco_path + '/2004')
2681        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
2682                           self.browser.contents)
2683        self.assertMatches('...2004/2005...', self.browser.contents)
2684        self.assertMatches('...regular_male_fr...', self.browser.contents)
2685        self.assertMatches('...%s...' % pin, self.browser.contents)
2686
2687        # Students can open the pdf slip
2688        self.browser.open(self.browser.url + '/bed_allocation_slip.pdf')
2689        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2690        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2691
2692        # Students can't relocate themselves
2693        self.assertFalse('Relocate' in self.browser.contents)
2694        relocate_path = self.acco_path + '/2004/relocate'
2695        self.assertRaises(
2696            Unauthorized, self.browser.open, relocate_path)
2697
2698        # Students can't the Remove button and check boxes
2699        self.browser.open(self.acco_path)
2700        self.assertFalse('Remove' in self.browser.contents)
2701        self.assertFalse('val_id' in self.browser.contents)
2702
2703        # Students can pay maintenance fee now
2704        self.browser.open(self.payments_path)
2705        self.browser.open(self.payments_path + '/addop')
2706        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
2707        self.browser.getControl("Create ticket").click()
2708        self.assertMatches('...Payment ticket created...',
2709                           self.browser.contents)
2710        return
2711
2712    def test_change_password_request(self):
2713        self.browser.open('http://localhost/app/changepw')
2714        self.browser.getControl(name="form.identifier").value = '123'
2715        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
2716        self.browser.getControl("Send login credentials").click()
2717        self.assertTrue('An email with' in self.browser.contents)
2718
2719    def test_student_expired_personal_data(self):
2720        # Login
2721        IWorkflowState(self.student).setState('school fee paid')
2722        delta = timedelta(days=180)
2723        self.student.personal_updated = datetime.utcnow() - delta
2724        self.browser.open(self.login_path)
2725        self.browser.getControl(name="form.login").value = self.student_id
2726        self.browser.getControl(name="form.password").value = 'spwd'
2727        self.browser.getControl("Login").click()
2728        self.assertEqual(self.browser.url, self.student_path)
2729        self.assertTrue(
2730            'You logged in' in self.browser.contents)
2731        # Students don't see personal_updated field in edit form
2732        self.browser.open(self.edit_personal_path)
2733        self.assertFalse('Updated' in self.browser.contents)
2734        self.browser.open(self.personal_path)
2735        self.assertTrue('Updated' in self.browser.contents)
2736        self.browser.getLink("Logout").click()
2737        delta = timedelta(days=181)
2738        self.student.personal_updated = datetime.utcnow() - delta
2739        self.browser.open(self.login_path)
2740        self.browser.getControl(name="form.login").value = self.student_id
2741        self.browser.getControl(name="form.password").value = 'spwd'
2742        self.browser.getControl("Login").click()
2743        self.assertEqual(self.browser.url, self.edit_personal_path)
2744        self.assertTrue(
2745            'Your personal data record is outdated.' in self.browser.contents)
2746
2747    def test_setReturningData(self):
2748        utils = getUtility(IStudentsUtils)
2749        self.student['studycourse'].current_level = 600
2750        utils.setReturningData(self.student)
2751        # The new level exceeds the certificates end_level.
2752        # In this case current_level remains unchanged and no error is raised.
2753        self.assertEqual(self.student['studycourse'].current_level, 600)
2754
2755class StudentRequestPWTests(StudentsFullSetup):
2756    # Tests for student registration
2757
2758    layer = FunctionalLayer
2759
2760    def test_request_pw(self):
2761        # Student with wrong number can't be found.
2762        self.browser.open('http://localhost/app/requestpw')
2763        self.browser.getControl(name="form.firstname").value = 'Anna'
2764        self.browser.getControl(name="form.number").value = 'anynumber'
2765        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
2766        self.browser.getControl("Send login credentials").click()
2767        self.assertTrue('No student record found.'
2768            in self.browser.contents)
2769        # Anonymous is not informed that firstname verification failed.
2770        # It seems that the record doesn't exist.
2771        self.browser.open('http://localhost/app/requestpw')
2772        self.browser.getControl(name="form.firstname").value = 'Johnny'
2773        self.browser.getControl(name="form.number").value = '123'
2774        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
2775        self.browser.getControl("Send login credentials").click()
2776        self.assertTrue('No student record found.'
2777            in self.browser.contents)
2778        # Even with the correct firstname we can't register if a
2779        # password has been set and used.
2780        self.browser.getControl(name="form.firstname").value = 'Anna'
2781        self.browser.getControl(name="form.number").value = '123'
2782        self.browser.getControl("Send login credentials").click()
2783        self.assertTrue('Your password has already been set and used.'
2784            in self.browser.contents)
2785        self.browser.open('http://localhost/app/requestpw')
2786        self.app['students'][self.student_id].password = None
2787        # The firstname field, used for verification, is not case-sensitive.
2788        self.browser.getControl(name="form.firstname").value = 'aNNa'
2789        self.browser.getControl(name="form.number").value = '123'
2790        self.browser.getControl(name="form.email").value = 'new@yy.zz'
2791        self.browser.getControl("Send login credentials").click()
2792        # Yeah, we succeded ...
2793        self.assertTrue('Your password request was successful.'
2794            in self.browser.contents)
2795        # We can also use the matric_number instead.
2796        self.browser.open('http://localhost/app/requestpw')
2797        self.browser.getControl(name="form.firstname").value = 'aNNa'
2798        self.browser.getControl(name="form.number").value = '234'
2799        self.browser.getControl(name="form.email").value = 'new@yy.zz'
2800        self.browser.getControl("Send login credentials").click()
2801        self.assertTrue('Your password request was successful.'
2802            in self.browser.contents)
2803        # ... and  student can be found in the catalog via the email address
2804        cat = queryUtility(ICatalog, name='students_catalog')
2805        results = list(
2806            cat.searchResults(
2807            email=('new@yy.zz', 'new@yy.zz')))
2808        self.assertEqual(self.student,results[0])
2809        logfile = os.path.join(
2810            self.app['datacenter'].storage, 'logs', 'main.log')
2811        logcontent = open(logfile).read()
2812        self.assertTrue('zope.anybody - students.browser.StudentRequestPasswordPage - '
2813                        '234 (K1000000) - new@yy.zz' in logcontent)
2814        return
2815
2816    def test_student_locked_level_forms(self):
2817
2818        # Add two study levels, one current and one previous
2819        studylevel = createObject(u'waeup.StudentStudyLevel')
2820        studylevel.level = 100
2821        self.student['studycourse'].addStudentStudyLevel(
2822            self.certificate, studylevel)
2823        studylevel = createObject(u'waeup.StudentStudyLevel')
2824        studylevel.level = 200
2825        self.student['studycourse'].addStudentStudyLevel(
2826            self.certificate, studylevel)
2827        IWorkflowState(self.student).setState('school fee paid')
2828        self.student['studycourse'].current_level = 200
2829
2830        self.browser.open(self.login_path)
2831        self.browser.getControl(name="form.login").value = self.student_id
2832        self.browser.getControl(name="form.password").value = 'spwd'
2833        self.browser.getControl("Login").click()
2834
2835        self.browser.open(self.student_path + '/studycourse/200/edit')
2836        self.assertFalse('The requested form is locked' in self.browser.contents)
2837        self.browser.open(self.student_path + '/studycourse/100/edit')
2838        self.assertTrue('The requested form is locked' in self.browser.contents)
2839
2840        self.browser.open(self.student_path + '/studycourse/200/ctadd')
2841        self.assertFalse('The requested form is locked' in self.browser.contents)
2842        self.browser.open(self.student_path + '/studycourse/100/ctadd')
2843        self.assertTrue('The requested form is locked' in self.browser.contents)
2844
2845        IWorkflowState(self.student).setState('courses registered')
2846        self.browser.open(self.student_path + '/studycourse/200/edit')
2847        self.assertTrue('The requested form is locked' in self.browser.contents)
2848        self.browser.open(self.student_path + '/studycourse/200/ctadd')
2849        self.assertTrue('The requested form is locked' in self.browser.contents)
2850
2851
2852class PublicPagesTests(StudentsFullSetup):
2853    # Tests for simple webservices
2854
2855    layer = FunctionalLayer
2856
2857    def test_paymentrequest(self):
2858        payment = createObject('waeup.StudentOnlinePayment')
2859        payment.p_category = u'schoolfee'
2860        payment.p_session = self.student.current_session
2861        payment.p_item = u'My Certificate'
2862        payment.p_id = u'anyid'
2863        self.student['payments']['anykey'] = payment
2864        # Request information about unpaid payment ticket
2865        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
2866        self.assertEqual(self.browser.contents, '-1')
2867        # Request information about paid payment ticket
2868        payment.p_state = u'paid'
2869        notify(grok.ObjectModifiedEvent(payment))
2870        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
2871        self.assertEqual(self.browser.contents,
2872            'FULL_NAME=Anna Tester&FACULTY=fac1&DEPARTMENT=dep1'
2873            '&PAYMENT_ITEM=My Certificate&PAYMENT_CATEGORY=School Fee'
2874            '&ACADEMIC_SESSION=2004/2005&MATRIC_NUMBER=234&REG_NUMBER=123'
2875            '&FEE_AMOUNT=0.0')
2876        self.browser.open('http://localhost/app/paymentrequest?NONSENSE=nonsense')
2877        self.assertEqual(self.browser.contents, '-1')
2878        self.browser.open('http://localhost/app/paymentrequest?P_ID=nonsense')
2879        self.assertEqual(self.browser.contents, '-1')
2880
2881class StudentDataExportTests(StudentsFullSetup, FunctionalAsyncTestCase):
2882    # Tests for StudentsContainer class views and pages
2883
2884    layer = FunctionalLayer
2885
2886    def wait_for_export_job_completed(self):
2887        # helper function waiting until the current export job is completed
2888        manager = getUtility(IJobManager)
2889        job_id = self.app['datacenter'].running_exports[0][0]
2890        job = manager.get(job_id)
2891        wait_for_result(job)
2892        return job_id
2893
2894    def test_faculties_export(self):
2895        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
2896        facs_path = 'http://localhost/app/faculties'
2897        self.browser.open(facs_path)
2898        self.browser.getLink("Export student data").click()
2899        self.browser.getControl("Configure new export").click()
2900        self.browser.getControl(name="exporter").value = ['bursary']
2901        self.browser.getControl(name="session").value = ['2004']
2902        self.browser.getControl(name="level").value = ['100']
2903        self.browser.getControl(name="mode").value = ['ug_ft']
2904        self.browser.getControl("Create CSV file").click()
2905
2906        # When the job is finished and we reload the page...
2907        job_id = self.wait_for_export_job_completed()
2908        self.browser.open(facs_path + '/exports')
2909        # ... the csv file can be downloaded ...
2910        self.browser.getLink("Download").click()
2911        self.assertEqual(self.browser.headers['content-type'],
2912            'text/csv; charset=UTF-8')
2913        self.assertTrue(
2914            'filename="WAeUP.Kofa_bursary_%s.csv' % job_id in
2915            self.browser.headers['content-disposition'])
2916        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
2917        job_id = self.app['datacenter'].running_exports[0][0]
2918        # ... and discarded
2919        self.browser.open(facs_path + '/exports')
2920        self.browser.getControl("Discard").click()
2921        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
2922        # Creation, downloading and discarding is logged
2923        logfile = os.path.join(
2924            self.app['datacenter'].storage, 'logs', 'datacenter.log')
2925        logcontent = open(logfile).read()
2926        self.assertTrue(
2927            'zope.mgr - students.browser.FacultiesExportJobContainerJobConfig '
2928            '- exported: bursary (2004, 100, ug_ft, None, None), job_id=%s'
2929            % job_id in logcontent
2930            )
2931        self.assertTrue(
2932            'zope.mgr - students.browser.ExportJobContainerDownload '
2933            '- downloaded: WAeUP.Kofa_bursary_%s.csv, job_id=%s'
2934            % (job_id, job_id) in logcontent
2935            )
2936        self.assertTrue(
2937            'zope.mgr - students.browser.ExportJobContainerOverview '
2938            '- discarded: job_id=%s' % job_id in logcontent
2939            )
2940
2941    def test_department_export(self):
2942        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
2943        dep1_path = 'http://localhost/app/faculties/fac1/dep1'
2944        self.browser.open(dep1_path)
2945        self.browser.getLink("Export student data").click()
2946        self.browser.getControl("Configure new export").click()
2947        self.browser.getControl(name="exporter").value = ['students']
2948        self.browser.getControl(name="session").value = ['2004']
2949        self.browser.getControl(name="level").value = ['100']
2950        self.browser.getControl(name="mode").value = ['ug_ft']
2951        self.browser.getControl("Create CSV file").click()
2952
2953        # When the job is finished and we reload the page...
2954        job_id = self.wait_for_export_job_completed()
2955        self.browser.open(dep1_path + '/exports')
2956        # ... the csv file can be downloaded ...
2957        self.browser.getLink("Download").click()
2958        self.assertEqual(self.browser.headers['content-type'],
2959            'text/csv; charset=UTF-8')
2960        self.assertTrue(
2961            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
2962            self.browser.headers['content-disposition'])
2963        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
2964        job_id = self.app['datacenter'].running_exports[0][0]
2965        # ... and discarded
2966        self.browser.open(dep1_path + '/exports')
2967        self.browser.getControl("Discard").click()
2968        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
2969        # Creation, downloading and discarding is logged
2970        logfile = os.path.join(
2971            self.app['datacenter'].storage, 'logs', 'datacenter.log')
2972        logcontent = open(logfile).read()
2973        self.assertTrue(
2974            'zope.mgr - students.browser.DepartmentExportJobContainerJobConfig '
2975            '- exported: students (2004, 100, ug_ft, dep1, None), job_id=%s'
2976            % job_id in logcontent
2977            )
2978        self.assertTrue(
2979            'zope.mgr - students.browser.ExportJobContainerDownload '
2980            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
2981            % (job_id, job_id) in logcontent
2982            )
2983        self.assertTrue(
2984            'zope.mgr - students.browser.ExportJobContainerOverview '
2985            '- discarded: job_id=%s' % job_id in logcontent
2986            )
2987
2988    def test_certificate_export(self):
2989        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
2990        cert1_path = 'http://localhost/app/faculties/fac1/dep1/certificates/CERT1'
2991        self.browser.open(cert1_path)
2992        self.browser.getLink("Export student data").click()
2993        self.browser.getControl("Configure new export").click()
2994        self.browser.getControl(name="exporter").value = ['students']
2995        self.browser.getControl(name="session").value = ['2004']
2996        self.browser.getControl(name="level").value = ['100']
2997        self.browser.getControl("Create CSV file").click()
2998
2999        # When the job is finished and we reload the page...
3000        job_id = self.wait_for_export_job_completed()
3001        self.browser.open(cert1_path + '/exports')
3002        # ... the csv file can be downloaded ...
3003        self.browser.getLink("Download").click()
3004        self.assertEqual(self.browser.headers['content-type'],
3005            'text/csv; charset=UTF-8')
3006        self.assertTrue(
3007            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3008            self.browser.headers['content-disposition'])
3009        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3010        job_id = self.app['datacenter'].running_exports[0][0]
3011        # ... and discarded
3012        self.browser.open(cert1_path + '/exports')
3013        self.browser.getControl("Discard").click()
3014        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3015        # Creation, downloading and discarding is logged
3016        logfile = os.path.join(
3017            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3018        logcontent = open(logfile).read()
3019        self.assertTrue(
3020            'zope.mgr - students.browser.CertificateExportJobContainerJobConfig '
3021            '- exported: students (2004, 100, None, None, CERT1), job_id=%s'
3022            % job_id in logcontent
3023            )
3024        self.assertTrue(
3025            'zope.mgr - students.browser.ExportJobContainerDownload '
3026            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3027            % (job_id, job_id) in logcontent
3028            )
3029        self.assertTrue(
3030            'zope.mgr - students.browser.ExportJobContainerOverview '
3031            '- discarded: job_id=%s' % job_id in logcontent
3032            )
3033
3034    def test_course_export_students(self):
3035        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3036        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
3037        self.browser.open(course1_path)
3038        self.browser.getLink("Export student data").click()
3039        self.browser.getControl("Configure new export").click()
3040        self.browser.getControl(name="exporter").value = ['students']
3041        self.browser.getControl(name="session").value = ['2004']
3042        self.browser.getControl(name="level").value = ['100']
3043        self.browser.getControl("Create CSV file").click()
3044
3045        # When the job is finished and we reload the page...
3046        job_id = self.wait_for_export_job_completed()
3047        self.browser.open(course1_path + '/exports')
3048        # ... the csv file can be downloaded ...
3049        self.browser.getLink("Download").click()
3050        self.assertEqual(self.browser.headers['content-type'],
3051            'text/csv; charset=UTF-8')
3052        self.assertTrue(
3053            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3054            self.browser.headers['content-disposition'])
3055        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3056        job_id = self.app['datacenter'].running_exports[0][0]
3057        # ... and discarded
3058        self.browser.open(course1_path + '/exports')
3059        self.browser.getControl("Discard").click()
3060        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3061        # Creation, downloading and discarding is logged
3062        logfile = os.path.join(
3063            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3064        logcontent = open(logfile).read()
3065        self.assertTrue(
3066            'zope.mgr - students.browser.CourseExportJobContainerJobConfig '
3067            '- exported: students (2004, 100, COURSE1), job_id=%s'
3068            % job_id in logcontent
3069            )
3070        self.assertTrue(
3071            'zope.mgr - students.browser.ExportJobContainerDownload '
3072            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3073            % (job_id, job_id) in logcontent
3074            )
3075        self.assertTrue(
3076            'zope.mgr - students.browser.ExportJobContainerOverview '
3077            '- discarded: job_id=%s' % job_id in logcontent
3078            )
3079
3080    def test_course_export_coursetickets(self):
3081        # We add study level 100 to the student's studycourse
3082        studylevel = StudentStudyLevel()
3083        studylevel.level = 100
3084        studylevel.level_session = 2004
3085        self.student['studycourse'].addStudentStudyLevel(
3086            self.certificate,studylevel)
3087        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3088        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
3089        self.browser.open(course1_path)
3090        self.browser.getLink("Export student data").click()
3091        self.browser.getControl("Configure new export").click()
3092        self.browser.getControl(name="exporter").value = ['coursetickets']
3093        self.browser.getControl(name="session").value = ['2004']
3094        self.browser.getControl(name="level").value = ['100']
3095        self.browser.getControl("Create CSV file").click()
3096        # When the job is finished and we reload the page...
3097        job_id = self.wait_for_export_job_completed()
3098        self.browser.open(course1_path + '/exports')
3099        # ... the csv file can be downloaded ...
3100        self.browser.getLink("Download").click()
3101        self.assertEqual(self.browser.headers['content-type'],
3102            'text/csv; charset=UTF-8')
3103        self.assertTrue(
3104            'filename="WAeUP.Kofa_coursetickets_%s.csv' % job_id in
3105            self.browser.headers['content-disposition'])
3106        # ... and contains the course ticket COURSE1
3107        self.assertEqual(self.browser.contents,
3108            'automatic,carry_over,code,credits,dcode,fcode,level,'
3109            'level_session,mandatory,passmark,score,semester,title,'
3110            'student_id,certcode\r\n1,0,COURSE1,10,dep1,fac1,100,2004,1,40,,1,'
3111            'Unnamed Course,K1000000,CERT1\r\n')
3112
3113        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3114        job_id = self.app['datacenter'].running_exports[0][0]
3115        # Thew job can be discarded
3116        self.browser.open(course1_path + '/exports')
3117        self.browser.getControl("Discard").click()
3118        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3119        # Creation, downloading and discarding is logged
3120        logfile = os.path.join(
3121            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3122        logcontent = open(logfile).read()
3123        self.assertTrue(
3124            'zope.mgr - students.browser.CourseExportJobContainerJobConfig '
3125            '- exported: coursetickets (2004, 100, COURSE1), job_id=%s'
3126            % job_id in logcontent
3127            )
3128        self.assertTrue(
3129            'zope.mgr - students.browser.ExportJobContainerDownload '
3130            '- downloaded: WAeUP.Kofa_coursetickets_%s.csv, job_id=%s'
3131            % (job_id, job_id) in logcontent
3132            )
3133        self.assertTrue(
3134            'zope.mgr - students.browser.ExportJobContainerOverview '
3135            '- discarded: job_id=%s' % job_id in logcontent
3136            )
3137
3138    def test_export_bursary_officers(self):
3139        # Create bursary officer
3140        self.app['users'].addUser('mrbursary', 'mrbursarysecret')
3141        self.app['users']['mrbursary'].email = 'mrbursary@foo.ng'
3142        self.app['users']['mrbursary'].title = 'Carlo Pitter'
3143        prmglobal = IPrincipalRoleManager(self.app)
3144        prmglobal.assignRoleToPrincipal('waeup.BursaryOfficer', 'mrbursary')
3145        # Login as bursary officer
3146        self.browser.open(self.login_path)
3147        self.browser.getControl(name="form.login").value = 'mrbursary'
3148        self.browser.getControl(name="form.password").value = 'mrbursarysecret'
3149        self.browser.getControl("Login").click()
3150        self.assertMatches('...You logged in...', self.browser.contents)
3151        self.browser.getLink("Academics").click()
3152        self.browser.getLink("Export student data").click()
3153        self.browser.getControl("Configure new export").click()
3154        # Only the bursary exporter is available for bursary officers
3155        # not only at facultiescontainer level ...
3156        self.assertFalse('<option value="students">' in self.browser.contents)
3157        self.assertTrue('<option value="bursary">' in self.browser.contents)
3158        self.browser.getControl(name="exporter").value = ['bursary']
3159        self.browser.getControl(name="session").value = ['2004']
3160        self.browser.getControl(name="level").value = ['100']
3161        self.browser.getControl("Create CSV file").click()
3162        self.assertTrue('Export started' in self.browser.contents)
3163        # ... but also at other levels
3164        self.browser.open('http://localhost/app/faculties/fac1/dep1')
3165        self.browser.getLink("Export student data").click()
3166        self.browser.getControl("Configure new export").click()
3167        self.assertFalse('<option value="students">' in self.browser.contents)
3168        self.assertTrue('<option value="bursary">' in self.browser.contents)
3169        # Thew job can be discarded
3170        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3171        #job_id = self.app['datacenter'].running_exports[0][0]
3172        job_id = self.wait_for_export_job_completed()
3173        self.browser.open('http://localhost/app/faculties/exports')
3174        self.browser.getControl("Discard").click()
3175        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
Note: See TracBrowser for help on using the repository browser.