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

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

Adjust and extend tests for previous revision.

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