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

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

Set maintenance fee for each hostel.

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