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

Last change on this file since 15344 was 15334, checked in by Henrik Bettermann, 6 years ago

Add tests and adjust the documentation.

  • Property svn:keywords set to Id
File size: 230.8 KB
Line 
1# -*- coding: utf-8 -*-
2## $Id: test_browser.py 15334 2019-02-17 19:43:26Z henrik $
3##
4## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
5## This program is free software; you can redistribute it and/or modify
6## it under the terms of the GNU General Public License as published by
7## the Free Software Foundation; either version 2 of the License, or
8## (at your option) any later version.
9##
10## This program is distributed in the hope that it will be useful,
11## but WITHOUT ANY WARRANTY; without even the implied warranty of
12## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13## GNU General Public License for more details.
14##
15## You should have received a copy of the GNU General Public License
16## along with this program; if not, write to the Free Software
17## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18##
19"""
20Test the student-related UI components.
21"""
22import shutil
23import tempfile
24import pytz
25import base64
26from datetime import datetime, timedelta, date
27from StringIO import StringIO
28import os
29import grok
30from zc.async.testing import wait_for_result
31from zope.event import notify
32from zope.component import createObject, queryUtility, getUtility
33from zope.component.hooks import setSite, clearSite
34from zope.catalog.interfaces import ICatalog
35from zope.security.interfaces import Unauthorized
36from zope.securitypolicy.interfaces import IPrincipalRoleManager
37from zope.testbrowser.testing import Browser
38from zope.interface import implementedBy
39from zope.schema.fieldproperty import FieldProperty
40from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
41from waeup.kofa.testing import FunctionalLayer, FunctionalTestCase
42from waeup.kofa.app import University
43from waeup.kofa.interfaces import IFileStoreNameChooser, IExtFileStore
44from waeup.kofa.payments.interfaces import IPayer
45from waeup.kofa.students.payments import StudentOnlinePayment
46from waeup.kofa.students.student import Student
47from waeup.kofa.students.studylevel import StudentStudyLevel
48from waeup.kofa.university.faculty import Faculty
49from waeup.kofa.university.department import Department
50from waeup.kofa.interfaces import IUserAccount, IJobManager, VALIDATED, CREATED
51from waeup.kofa.authentication import LocalRoleSetEvent
52from waeup.kofa.hostels.hostel import Hostel, Bed, NOT_OCCUPIED
53from waeup.kofa.tests.test_async import FunctionalAsyncTestCase
54from waeup.kofa.browser.tests.test_pdf import samples_dir
55from waeup.kofa.tests.test_authentication import SECRET
56
57PH_LEN = 15911  # Length of placeholder file
58
59SAMPLE_IMAGE = os.path.join(os.path.dirname(__file__), 'test_image.jpg')
60SAMPLE_IMAGE_BMP = os.path.join(os.path.dirname(__file__), 'test_image.bmp')
61URL_LECTURER_LANDING = 'http://localhost/app/my_courses'
62
63curr_year = datetime.now().year
64
65def lookup_submit_value(name, value, browser):
66    """Find a button with a certain value."""
67    for num in range(0, 100):
68        try:
69            button = browser.getControl(name=name, index=num)
70            if button.value.endswith(value):
71                return button
72        except IndexError:
73            break
74    return None
75
76
77class StudentsFullSetup(FunctionalTestCase):
78    # A test case that only contains a setup and teardown
79    #
80    # Complete setup for students handlings is rather complex and
81    # requires lots of things created before we can start. This is a
82    # setup that does all this, creates a university, creates PINs,
83    # etc.  so that we do not have to bother with that in different
84    # test cases.
85
86    layer = FunctionalLayer
87
88    def setUp(self):
89        super(StudentsFullSetup, self).setUp()
90
91        # Setup a sample site for each test
92        app = University()
93        self.dc_root = tempfile.mkdtemp()
94        app['datacenter'].setStoragePath(self.dc_root)
95
96        # Prepopulate the ZODB...
97        self.getRootFolder()['app'] = app
98        # we add the site immediately after creation to the
99        # ZODB. Catalogs and other local utilities are not setup
100        # before that step.
101        self.app = self.getRootFolder()['app']
102        # Set site here. Some of the following setup code might need
103        # to access grok.getSite() and should get our new app then
104        setSite(app)
105
106        # Add student with subobjects
107        student = createObject('waeup.Student')
108        student.firstname = u'Anna'
109        student.lastname = u'Tester'
110        student.reg_number = u'123'
111        student.matric_number = u'234'
112        student.sex = u'm'
113        student.email = 'aa@aa.ng'
114        student.phone = u'1234'
115        student.date_of_birth = date(1981, 2, 4)
116        self.app['students'].addStudent(student)
117        self.student_id = student.student_id
118        self.student = self.app['students'][self.student_id]
119
120        # Set password
121        IUserAccount(
122            self.app['students'][self.student_id]).setPassword('spwd')
123
124        self.login_path = 'http://localhost/app/login'
125        self.container_path = 'http://localhost/app/students'
126        self.manage_container_path = self.container_path + '/@@manage'
127        self.add_student_path = self.container_path + '/addstudent'
128        self.student_path = self.container_path + '/' + self.student_id
129        self.manage_student_path = self.student_path + '/manage_base'
130        self.trigtrans_path = self.student_path + '/trigtrans'
131        self.clearance_path = self.student_path + '/view_clearance'
132        self.personal_path = self.student_path + '/view_personal'
133        self.edit_clearance_path = self.student_path + '/cedit'
134        self.manage_clearance_path = self.student_path + '/manage_clearance'
135        self.edit_personal_path = self.student_path + '/edit_personal'
136        self.manage_personal_path = self.student_path + '/manage_personal'
137        self.studycourse_path = self.student_path + '/studycourse'
138        self.payments_path = self.student_path + '/payments'
139        self.acco_path = self.student_path + '/accommodation'
140        self.history_path = self.student_path + '/history'
141
142        # Create 5 access codes with prefix'PWD'
143        pin_container = self.app['accesscodes']
144        pin_container.createBatch(
145            datetime.utcnow(), 'some_userid', 'PWD', 9.99, 5)
146        pins = pin_container['PWD-1'].values()
147        self.pwdpins = [x.representation for x in pins]
148        self.existing_pwdpin = self.pwdpins[0]
149        parts = self.existing_pwdpin.split('-')[1:]
150        self.existing_pwdseries, self.existing_pwdnumber = parts
151        # Create 5 access codes with prefix 'CLR'
152        pin_container.createBatch(
153            datetime.now(), 'some_userid', 'CLR', 9.99, 5)
154        pins = pin_container['CLR-1'].values()
155        pins[0].owner = u'Hans Wurst'
156        self.existing_clrac = pins[0]
157        self.existing_clrpin = pins[0].representation
158        parts = self.existing_clrpin.split('-')[1:]
159        self.existing_clrseries, self.existing_clrnumber = parts
160        # Create 2 access codes with prefix 'HOS'
161        pin_container.createBatch(
162            datetime.now(), 'some_userid', 'HOS', 9.99, 2)
163        pins = pin_container['HOS-1'].values()
164        self.existing_hosac = pins[0]
165        self.existing_hospin = pins[0].representation
166        parts = self.existing_hospin.split('-')[1:]
167        self.existing_hosseries, self.existing_hosnumber = parts
168
169        # Populate university
170        self.certificate = createObject('waeup.Certificate')
171        self.certificate.code = u'CERT1'
172        self.certificate.application_category = 'basic'
173        self.certificate.study_mode = 'ug_ft'
174        self.certificate.start_level = 100
175        self.certificate.end_level = 500
176        self.certificate.school_fee_1 = 40000.0
177        self.certificate.school_fee_2 = 20000.0
178        self.app['faculties']['fac1'] = Faculty(code=u'fac1')
179        self.app['faculties']['fac1']['dep1'] = Department(code=u'dep1')
180        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
181            self.certificate)
182        self.course = createObject('waeup.Course')
183        self.course.code = 'COURSE1'
184        self.course.semester = 1
185        self.course.credits = 10
186        self.course.passmark = 40
187        self.app['faculties']['fac1']['dep1'].courses.addCourse(
188            self.course)
189        self.app['faculties']['fac1']['dep1'].certificates[
190            'CERT1'].addCertCourse(self.course, level=100)
191
192        # Configure university and hostels
193        self.app['hostels'].accommodation_states = ['admitted']
194        self.app['hostels'].accommodation_session = 2004
195        delta = timedelta(days=10)
196        self.app['hostels'].startdate = datetime.now(pytz.utc) - delta
197        self.app['hostels'].enddate = datetime.now(pytz.utc) + delta
198        self.app['configuration'].carry_over = True
199        configuration = createObject('waeup.SessionConfiguration')
200        configuration.academic_session = 2004
201        configuration.clearance_fee = 3456.0
202        configuration.transcript_fee = 4567.0
203        configuration.booking_fee = 123.4
204        configuration.maint_fee = 987.0
205        configuration.transfer_fee = 456.0
206        configuration.late_registration_fee = 345.0
207        self.app['configuration'].addSessionConfiguration(configuration)
208
209        # Create a hostel with two beds
210        hostel = Hostel()
211        hostel.hostel_id = u'hall-1'
212        hostel.hostel_name = u'Hall 1'
213        hostel.maint_fee = 876.0
214        self.app['hostels'].addHostel(hostel)
215        bed = Bed()
216        bed.bed_id = u'hall-1_A_101_A'
217        bed.bed_number = 1
218        bed.owner = NOT_OCCUPIED
219        bed.bed_type = u'regular_male_fr'
220        self.app['hostels'][hostel.hostel_id].addBed(bed)
221        bed = Bed()
222        bed.bed_id = u'hall-1_A_101_B'
223        bed.bed_number = 2
224        bed.owner = NOT_OCCUPIED
225        bed.bed_type = u'regular_female_fr'
226        self.app['hostels'][hostel.hostel_id].addBed(bed)
227
228        # Set study course attributes of test student
229        self.student['studycourse'].certificate = self.certificate
230        self.student['studycourse'].current_session = 2004
231        self.student['studycourse'].entry_session = 2004
232        self.student['studycourse'].current_verdict = 'A'
233        self.student['studycourse'].current_level = 100
234        # Update the catalog
235        notify(grok.ObjectModifiedEvent(self.student))
236
237        # Put the prepopulated site into test ZODB and prepare test
238        # browser
239        self.browser = Browser()
240        self.browser.handleErrors = False
241
242    def tearDown(self):
243        super(StudentsFullSetup, self).tearDown()
244        clearSite()
245        shutil.rmtree(self.dc_root)
246
247
248class StudentsContainerUITests(StudentsFullSetup):
249    # Tests for StudentsContainer class views and pages
250
251    layer = FunctionalLayer
252
253    def test_anonymous_access(self):
254        # Anonymous users can't access students containers
255        self.assertRaises(
256            Unauthorized, self.browser.open, self.container_path)
257        self.assertRaises(
258            Unauthorized, self.browser.open, self.manage_container_path)
259        return
260
261    def test_manage_access(self):
262        # Managers can access the view page of students
263        # containers and can perform actions
264        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
265        self.browser.open(self.container_path)
266        self.assertEqual(self.browser.headers['Status'], '200 Ok')
267        self.assertEqual(self.browser.url, self.container_path)
268        self.browser.getLink("Manage students section").click()
269        self.assertEqual(self.browser.headers['Status'], '200 Ok')
270        self.assertEqual(self.browser.url, self.manage_container_path)
271        return
272
273    def test_add_search_delete_students(self):
274        # Managers can add search and remove students
275        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
276        self.browser.open(self.manage_container_path)
277        self.browser.getLink("Add student").click()
278        self.assertEqual(self.browser.headers['Status'], '200 Ok')
279        self.assertEqual(self.browser.url, self.add_student_path)
280        self.browser.getControl(name="form.firstname").value = 'Bob'
281        self.browser.getControl(name="form.lastname").value = 'Tester'
282        self.browser.getControl(name="form.reg_number").value = '123'
283        self.browser.getControl("Create student").click()
284        self.assertTrue(
285            'Registration number exists already' in self.browser.contents)
286        self.browser.getControl(name="form.reg_number").value = '1234'
287        self.browser.getControl("Create student").click()
288        self.assertTrue('Student record created' in self.browser.contents)
289
290        # Registration and matric numbers must be unique
291        self.browser.getLink("Manage").click()
292        self.browser.getControl(name="form.reg_number").value = '123'
293        self.browser.getControl("Save").click()
294        self.assertMatches('...Registration number exists...',
295                           self.browser.contents)
296        self.browser.getControl(name="form.reg_number").value = '789'
297        self.browser.getControl(name="form.matric_number").value = '234'
298        self.browser.getControl("Save").click()
299        self.assertMatches('...Matriculation number exists...',
300                           self.browser.contents)
301
302        # We can find a student with a certain student_id
303        self.browser.open(self.container_path)
304        self.browser.getControl("Find student(s)").click()
305        self.assertTrue('Empty search string' in self.browser.contents)
306        self.browser.getControl(name="searchtype").value = ['student_id']
307        self.browser.getControl(name="searchterm").value = self.student_id
308        self.browser.getControl("Find student(s)").click()
309        self.assertTrue('Anna Tester' in self.browser.contents)
310
311        # We can find a student in a certain session
312        self.browser.open(self.container_path)
313        self.browser.getControl(name="searchtype").value = ['current_session']
314        self.browser.getControl(name="searchterm").value = '2004'
315        self.browser.getControl("Find student(s)").click()
316        self.assertTrue('Anna Tester' in self.browser.contents)
317        # Session fileds require integer values
318        self.browser.open(self.container_path)
319        self.browser.getControl(name="searchtype").value = ['current_session']
320        self.browser.getControl(name="searchterm").value = '2004/2005'
321        self.browser.getControl("Find student(s)").click()
322        self.assertTrue('Only year dates allowed' in self.browser.contents)
323        self.browser.open(self.manage_container_path)
324        self.browser.getControl(name="searchtype").value = ['current_session']
325        self.browser.getControl(name="searchterm").value = '2004/2005'
326        self.browser.getControl("Find student(s)").click()
327        self.assertTrue('Only year dates allowed' in self.browser.contents)
328
329        # We can find a student in a certain study_mode
330        self.browser.open(self.container_path)
331        self.browser.getControl(name="searchtype").value = ['current_mode']
332        self.browser.getControl(name="searchterm").value = 'ug_ft'
333        self.browser.getControl("Find student(s)").click()
334        self.assertTrue('Anna Tester' in self.browser.contents)
335
336        # We can find a student in a certain department
337        self.browser.open(self.container_path)
338        self.browser.getControl(name="searchtype").value = ['depcode']
339        self.browser.getControl(name="searchterm").value = 'dep1'
340        self.browser.getControl("Find student(s)").click()
341        self.assertTrue('Anna Tester' in self.browser.contents)
342
343        # We can find a student by searching for all kind of name parts
344        self.browser.open(self.manage_container_path)
345        self.browser.getControl("Find student(s)").click()
346        self.assertTrue('Empty search string' in self.browser.contents)
347        self.browser.getControl(name="searchtype").value = ['fullname']
348        self.browser.getControl(name="searchterm").value = 'Anna 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 = 'Anna'
354        self.browser.getControl("Find student(s)").click()
355        self.assertTrue('Anna Tester' in self.browser.contents)
356        self.browser.open(self.manage_container_path)
357        self.browser.getControl(name="searchtype").value = ['fullname']
358        self.browser.getControl(name="searchterm").value = 'Tester'
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 = 'An'
364        self.browser.getControl("Find student(s)").click()
365        self.assertFalse('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 = 'An*'
369        self.browser.getControl("Find student(s)").click()
370        self.assertTrue('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'
374        self.browser.getControl("Find student(s)").click()
375        self.assertTrue('Anna Tester' in self.browser.contents)
376        self.browser.open(self.manage_container_path)
377        self.browser.getControl(name="searchtype").value = ['fullname']
378        self.browser.getControl(name="searchterm").value = 'Tester Ana'
379        self.browser.getControl("Find student(s)").click()
380        self.assertFalse('Anna Tester' in self.browser.contents)
381        self.browser.open(self.manage_container_path)
382        self.browser.getControl(name="searchtype").value = ['fullname']
383        self.browser.getControl(name="searchterm").value = 'Tester Anna'
384        self.browser.getControl("Find student(s)").click()
385        self.assertTrue('Anna Tester' in self.browser.contents)
386        # The old searchterm will be used again
387        self.browser.getControl("Find student(s)").click()
388        self.assertTrue('Anna Tester' in self.browser.contents)
389
390        # We can find suspended students
391        self.student.suspended = True
392        notify(grok.ObjectModifiedEvent(self.student))
393        self.browser.open(self.manage_container_path)
394        self.browser.getControl(name="searchtype").value = ['suspended']
395        self.browser.getControl("Find student(s)").click()
396        self.assertTrue('Anna Tester' in self.browser.contents)
397        self.browser.open(self.container_path)
398        self.browser.getControl(name="searchtype").value = ['suspended']
399        self.browser.getControl("Find student(s)").click()
400        self.assertTrue('Anna Tester' in self.browser.contents)
401
402        # The catalog is informed when studycourse objects have been
403        # edited
404        self.browser.open(self.studycourse_path + '/manage')
405        self.browser.getControl(name="form.current_session").value = ['2010']
406        self.browser.getControl(name="form.entry_session").value = ['2010']
407        self.browser.getControl(name="form.entry_mode").value = ['ug_ft']
408        self.browser.getControl("Save").click()
409
410        # We can find the student in the new session
411        self.browser.open(self.manage_container_path)
412        self.browser.getControl(name="searchtype").value = ['current_session']
413        self.browser.getControl(name="searchterm").value = '2010'
414        self.browser.getControl("Find student(s)").click()
415        self.assertTrue('Anna Tester' in self.browser.contents)
416
417        ctrl = self.browser.getControl(name='entries')
418        ctrl.getControl(value=self.student_id).selected = True
419        self.browser.getControl("Remove selected", index=0).click()
420        self.assertTrue('Successfully removed' in self.browser.contents)
421        self.browser.getControl(name="searchtype").value = ['student_id']
422        self.browser.getControl(name="searchterm").value = self.student_id
423        self.browser.getControl("Find student(s)").click()
424        self.assertTrue('No student found' in self.browser.contents)
425
426        self.browser.open(self.container_path)
427        self.browser.getControl(name="searchtype").value = ['student_id']
428        self.browser.getControl(name="searchterm").value = self.student_id
429        self.browser.getControl("Find student(s)").click()
430        self.assertTrue('No student found' in self.browser.contents)
431        return
432
433    def test_add_graduated_students(self):
434        # Managers can add search and remove students
435        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
436        self.browser.open(self.manage_container_path)
437        self.browser.getLink("Add student").click()
438        self.assertEqual(self.browser.headers['Status'], '200 Ok')
439        self.assertEqual(self.browser.url, self.add_student_path)
440        self.browser.getControl(name="form.firstname").value = 'Bob'
441        self.browser.getControl(name="form.lastname").value = 'Tester'
442        self.browser.getControl(name="form.reg_number").value = '1234'
443        self.browser.getControl("Create graduated student").click()
444        self.assertTrue('Student record created' in self.browser.contents)
445        self.assertEqual(self.app['students']['K1000001'].state, 'graduated')
446        return
447
448
449class OfficerUITests(StudentsFullSetup):
450    # Tests for Student class views and pages
451
452    def test_student_properties(self):
453        self.student['studycourse'].current_level = 100
454        self.assertEqual(self.student.current_level, 100)
455        self.student['studycourse'].current_session = 2011
456        self.assertEqual(self.student.current_session, 2011)
457        self.student['studycourse'].current_verdict = 'A'
458        self.assertEqual(self.student.current_verdict, 'A')
459        return
460
461    def test_studylevelmanagepage(self):
462        studylevel = StudentStudyLevel()
463        studylevel.level = 100
464        cert = self.app['faculties']['fac1']['dep1'].certificates['CERT1']
465        self.student['studycourse'].addStudentStudyLevel(cert, studylevel)
466        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
467        self.browser.open(self.studycourse_path + '/100/manage')
468        self.assertEqual(
469            self.browser.url, self.studycourse_path + '/100/manage')
470        self.assertEqual(self.browser.headers['Status'], '200 Ok')
471
472    def test_basic_auth(self):
473        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
474        self.browser.open('http://localhost/app')
475        self.browser.getLink("Logout").click()
476        self.assertTrue('You have been logged out' in self.browser.contents)
477        # But we are still logged in since we've used basic
478        # authentication here.  Wikipedia says: Existing browsers
479        # retain authentication information until the tab or browser
480        # is closed or the user clears the history.  HTTP does not
481        # provide a method for a server to direct clients to discard
482        # these cached credentials. This means that there is no
483        # effective way for a server to "log out" the user without
484        # closing the browser. This is a significant defect that
485        # requires browser manufacturers to support a "logout" user
486        # interface element ...
487        self.assertTrue('Manager' in self.browser.contents)
488
489    def test_basic_auth_base64(self):
490        auth_token = base64.b64encode('mgr:mgrpw')
491        self.browser.addHeader('Authorization', 'Basic %s' % auth_token)
492        self.browser.open(self.manage_container_path)
493        self.assertEqual(self.browser.headers['Status'], '200 Ok')
494
495    def test_manage_access(self):
496        # Managers can access the pages of students
497        # and can perform actions
498        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
499        self.browser.open(self.student_path)
500        self.assertEqual(self.browser.headers['Status'], '200 Ok')
501        self.assertEqual(self.browser.url, self.student_path)
502        self.browser.getLink("Trigger").click()
503        self.assertEqual(self.browser.headers['Status'], '200 Ok')
504        # Managers can trigger transitions
505        self.browser.getControl(name="transition").value = ['admit']
506        self.browser.getControl("Save").click()
507        # Managers can edit base
508        self.browser.open(self.student_path)
509        self.browser.getLink("Manage").click()
510        self.assertEqual(self.browser.url, self.manage_student_path)
511        self.assertEqual(self.browser.headers['Status'], '200 Ok')
512        self.browser.getControl(name="form.firstname").value = 'John'
513        self.browser.getControl(name="form.lastname").value = 'Tester'
514        self.browser.getControl(name="form.reg_number").value = '345'
515        self.browser.getControl(name="password").value = 'secret'
516        self.browser.getControl(name="control_password").value = 'secret'
517        self.browser.getControl("Save").click()
518        self.assertMatches('...Form has been saved...',
519                           self.browser.contents)
520        self.browser.open(self.student_path)
521        self.browser.getLink("Clearance Data").click()
522        self.assertEqual(self.browser.headers['Status'], '200 Ok')
523        self.assertEqual(self.browser.url, self.clearance_path)
524        self.browser.getLink("Manage").click()
525        self.assertEqual(self.browser.headers['Status'], '200 Ok')
526        self.assertEqual(self.browser.url, self.manage_clearance_path)
527        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
528        self.browser.getControl("Save").click()
529        self.assertMatches('...Form has been saved...',
530                           self.browser.contents)
531
532        self.browser.open(self.student_path)
533        self.browser.getLink("Personal Data").click()
534        self.assertEqual(self.browser.headers['Status'], '200 Ok')
535        self.assertEqual(self.browser.url, self.personal_path)
536        self.browser.getLink("Manage").click()
537        self.assertEqual(self.browser.headers['Status'], '200 Ok')
538        self.assertEqual(self.browser.url, self.manage_personal_path)
539        self.browser.open(self.personal_path)
540        self.assertTrue('Updated' in self.browser.contents)
541        self.browser.getLink("Edit").click()
542        self.assertEqual(self.browser.headers['Status'], '200 Ok')
543        self.assertEqual(self.browser.url, self.edit_personal_path)
544        self.browser.getControl("Save").click()
545        # perm_address is required in IStudentPersonalEdit
546        self.assertMatches('...Required input is missing...',
547                           self.browser.contents)
548        self.browser.getControl(name="form.perm_address").value = 'My address!'
549        self.browser.getControl("Save").click()
550        self.assertMatches('...Form has been saved...',
551                           self.browser.contents)
552
553        # Managers can browse all subobjects
554        self.browser.open(self.student_path)
555        self.browser.getLink("Payments").click()
556        self.assertEqual(self.browser.headers['Status'], '200 Ok')
557        self.assertEqual(self.browser.url, self.payments_path)
558        self.browser.open(self.student_path)
559        self.browser.getLink("Accommodation").click()
560        self.assertEqual(self.browser.headers['Status'], '200 Ok')
561        self.assertEqual(self.browser.url, self.acco_path)
562        self.browser.open(self.student_path)
563        self.browser.getLink("History").click()
564        self.assertEqual(self.browser.headers['Status'], '200 Ok')
565        self.assertEqual(self.browser.url, self.history_path)
566        self.assertMatches('...Admitted by Manager...',
567                           self.browser.contents)
568        # Only the Application Slip does not exist
569        self.assertFalse('Application Slip' in self.browser.contents)
570        return
571
572    def test_flash_notice(self):
573        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
574        self.browser.open(self.student_path)
575        self.assertFalse('alert alert-warning' in self.browser.contents)
576        self.student.flash_notice = u'Happy Birthday!'
577        self.browser.open(self.student_path)
578        self.assertTrue(
579            '<div><div class="alert alert-warning">Happy Birthday!</div>'
580            in self.browser.contents)
581        return
582
583    def test_manage_contact_student(self):
584        # Managers can contact student
585        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
586        # Remove required FieldProperty attribute first ...
587        delattr(Student, 'email')
588        # ... and replace by None
589        self.student.email = None
590        # Now we have to add  the FieldProperty attribute again. Otherwise
591        # many other tests below will fail.
592        iface = list(implementedBy(Student))[0]
593        field_property = FieldProperty(iface['email'])
594        setattr(Student, 'email', field_property)
595        self.browser.open(self.student_path)
596        self.browser.getLink("Send email").click()
597        self.browser.getControl(
598            name="form.subject").value = 'Important subject'
599        self.browser.getControl(name="form.body").value = 'Hello!'
600        self.browser.getControl("Send message now").click()
601        self.assertTrue(
602            'An smtp server error occurred' in self.browser.contents)
603        self.student.email = 'xx@yy.zz'
604        self.browser.getControl("Send message now").click()
605        self.assertTrue('Your message has been sent' in self.browser.contents)
606        return
607
608    def test_manage_remove_department(self):
609        # Lazy student is studying CERT1
610        lazystudent = Student()
611        lazystudent.firstname = u'Lazy'
612        lazystudent.lastname = u'Student'
613        self.app['students'].addStudent(lazystudent)
614        student_id = lazystudent.student_id
615        student_path = self.container_path + '/' + student_id
616        lazystudent['studycourse'].certificate = self.certificate
617        notify(grok.ObjectModifiedEvent(lazystudent))
618        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
619        self.browser.open(student_path + '/studycourse')
620        self.assertTrue('CERT1' in self.browser.contents)
621        # After some years the department is removed
622        del self.app['faculties']['fac1']['dep1']
623        # So CERT1 does no longer exist and lazy student's
624        # certificate reference is removed too
625        self.browser.open(student_path + '/studycourse')
626        self.assertEqual(self.browser.headers['Status'], '200 Ok')
627        self.assertEqual(self.browser.url, student_path + '/studycourse')
628        self.assertFalse('CERT1' in self.browser.contents)
629        self.assertMatches('...<div>--</div>...',
630                           self.browser.contents)
631
632    def test_manage_upload_file(self):
633        # Managers can upload a file via the StudentClearanceManageFormPage
634        # The image is stored even if form has errors
635        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
636        self.browser.open(self.manage_clearance_path)
637        # No birth certificate has been uploaded yet
638        # Browsing the link shows a placerholder image
639        self.browser.open('birth_certificate')
640        self.assertEqual(
641            self.browser.headers['content-type'], 'image/jpeg')
642        self.assertEqual(len(self.browser.contents), PH_LEN)
643        # Create a pseudo image file and select it to be uploaded in form
644        # as birth certificate
645        self.browser.open(self.manage_clearance_path)
646        image = open(SAMPLE_IMAGE, 'rb')
647        ctrl = self.browser.getControl(name='birthcertificateupload')
648        file_ctrl = ctrl.mech_control
649        file_ctrl.add_file(image, filename='my_birth_certificate.jpg')
650        # The Save action does not upload files
651        self.browser.getControl("Save").click()  # submit form
652        self.assertFalse(
653            '<a target="image" href="birth_certificate">'
654            in self.browser.contents)
655        # ... but the correct upload submit button does
656        image = open(SAMPLE_IMAGE)
657        ctrl = self.browser.getControl(name='birthcertificateupload')
658        file_ctrl = ctrl.mech_control
659        file_ctrl.add_file(image, filename='my_birth_certificate.jpg')
660        self.browser.getControl(
661            name='upload_birthcertificateupload').click()
662        # There is a correct <img> link included
663        self.assertTrue(
664            'href="http://localhost/app/students/K1000000/birth_certificate"'
665            in self.browser.contents)
666        # Browsing the link shows a real image
667        self.browser.open('birth_certificate')
668        self.assertEqual(
669            self.browser.headers['content-type'], 'image/jpeg')
670        self.assertEqual(len(self.browser.contents), 2787)
671        # We can't reupload a file. The existing file must be deleted first.
672        self.browser.open(self.manage_clearance_path)
673        self.assertFalse(
674            'upload_birthcertificateupload' in self.browser.contents)
675        # File must be deleted first
676        self.browser.getControl(name='delete_birthcertificateupload').click()
677        self.assertTrue(
678            'birth_certificate deleted' in self.browser.contents)
679        # Uploading a file which is bigger than 250k will raise an error
680        big_image = StringIO(open(SAMPLE_IMAGE, 'rb').read() * 100)
681        ctrl = self.browser.getControl(name='birthcertificateupload')
682        file_ctrl = ctrl.mech_control
683        file_ctrl.add_file(big_image, filename='my_birth_certificate.jpg')
684        self.browser.getControl(
685            name='upload_birthcertificateupload').click()
686        self.assertTrue(
687            'Uploaded file is too big' in self.browser.contents)
688        # we do not rely on filename extensions given by uploaders
689        image = open(SAMPLE_IMAGE, 'rb')  # a jpg-file
690        ctrl = self.browser.getControl(name='birthcertificateupload')
691        file_ctrl = ctrl.mech_control
692        # tell uploaded file is bmp
693        file_ctrl.add_file(image, filename='my_birth_certificate.bmp')
694        self.browser.getControl(
695            name='upload_birthcertificateupload').click()
696        self.assertTrue(
697            # jpg file was recognized
698            'File birth_certificate.jpg uploaded.' in self.browser.contents)
699        # Delete file again
700        self.browser.getControl(name='delete_birthcertificateupload').click()
701        self.assertTrue(
702            'birth_certificate deleted' in self.browser.contents)
703        # File names must meet several conditions
704        bmp_image = open(SAMPLE_IMAGE_BMP, 'rb')
705        ctrl = self.browser.getControl(name='birthcertificateupload')
706        file_ctrl = ctrl.mech_control
707        file_ctrl.add_file(bmp_image, filename='my_birth_certificate.bmp')
708        self.browser.getControl(
709            name='upload_birthcertificateupload').click()
710        self.assertTrue(
711            'Only the following extensions are allowed'
712            in self.browser.contents)
713
714        # Managers can upload a file via the StudentBaseManageFormPage
715        self.browser.open(self.manage_student_path)
716        image = open(SAMPLE_IMAGE_BMP, 'rb')
717        ctrl = self.browser.getControl(name='passportuploadmanage')
718        file_ctrl = ctrl.mech_control
719        file_ctrl.add_file(image, filename='my_photo.bmp')
720        self.browser.getControl(
721            name='upload_passportuploadmanage').click()
722        self.assertTrue(
723            'jpg file format expected' in self.browser.contents)
724        ctrl = self.browser.getControl(name='passportuploadmanage')
725        file_ctrl = ctrl.mech_control
726        image = open(SAMPLE_IMAGE, 'rb')
727        file_ctrl.add_file(image, filename='my_photo.jpg')
728        self.browser.getControl(
729            name='upload_passportuploadmanage').click()
730        self.assertTrue(
731            'src="http://localhost/app/students/K1000000/passport.jpg"'
732            in self.browser.contents)
733        # We remove the passport file again
734        self.browser.open(self.manage_student_path)
735        self.browser.getControl('Delete').click()
736        self.browser.open(self.student_path + '/clearance_slip.pdf')
737        self.assertEqual(self.browser.headers['Status'], '200 Ok')
738        self.assertEqual(self.browser.headers['Content-Type'],
739                         'application/pdf')
740        # We want to see the signature fields.
741        IWorkflowState(self.student).setState('cleared')
742        self.browser.open(self.student_path + '/clearance_slip.pdf')
743        self.assertEqual(self.browser.headers['Status'], '200 Ok')
744        self.assertEqual(self.browser.headers['Content-Type'],
745                         'application/pdf')
746        path = os.path.join(samples_dir(), 'clearance_slip.pdf')
747        open(path, 'wb').write(self.browser.contents)
748        print "Sample PDF clearance_slip.pdf written to %s" % path
749
750    def test_manage_course_lists(self):
751        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
752        self.browser.open(self.student_path)
753        self.browser.getLink("Study Course").click()
754        self.assertEqual(self.browser.headers['Status'], '200 Ok')
755        self.assertEqual(self.browser.url, self.studycourse_path)
756        self.assertTrue('Undergraduate Full-Time' in self.browser.contents)
757        self.browser.getLink("Manage").click()
758        self.assertTrue('Manage study course' in self.browser.contents)
759        # Before we can select a level, the certificate must
760        # be selected and saved
761        self.browser.getControl(name="form.certificate").value = ['CERT1']
762        self.browser.getControl(name="form.current_session").value = ['2004']
763        self.browser.getControl(name="form.current_verdict").value = ['A']
764        self.browser.getControl(name="form.entry_mode").value = ['ug_ft']
765        self.browser.getControl("Save").click()
766        # Now we can save also the current level which depends on start and end
767        # level of the certificate
768        self.browser.getControl(name="form.current_level").value = ['100']
769        self.browser.getControl("Save").click()
770        # Managers can add and remove any study level (course list)
771        self.browser.getControl(name="addlevel").value = ['100']
772        self.browser.getControl("Add study level").click()
773        self.assertMatches(
774            '...You must select a session...', self.browser.contents)
775        self.browser.getControl(name="addlevel").value = ['100']
776        self.browser.getControl(name="level_session").value = ['2004']
777        self.browser.getControl("Add study level").click()
778        self.assertMatches('...<span>100</span>...', self.browser.contents)
779        self.assertEqual(self.student['studycourse']['100'].level, 100)
780        self.assertEqual(
781            self.student['studycourse']['100'].level_session, 2004)
782        self.browser.getControl(name="addlevel").value = ['100']
783        self.browser.getControl(name="level_session").value = ['2004']
784        self.browser.getControl("Add study level").click()
785        self.assertMatches('...This level exists...', self.browser.contents)
786        self.browser.getControl("Remove selected").click()
787        self.assertMatches(
788            '...No study level selected...', self.browser.contents)
789        self.browser.getControl(name="val_id").value = ['100']
790        self.browser.getControl(name="level_session").value = ['2004']
791        self.browser.getControl("Remove selected").click()
792        self.assertMatches('...Successfully removed...', self.browser.contents)
793        # Removing levels is properly logged
794        logfile = os.path.join(
795            self.app['datacenter'].storage, 'logs', 'students.log')
796        logcontent = open(logfile).read()
797        self.assertTrue(
798            'zope.mgr - students.browser.StudyCourseManageFormPage '
799            '- K1000000 - removed: 100' in logcontent)
800        # Add level again
801        self.browser.getControl(name="addlevel").value = ['100']
802        self.browser.getControl(name="level_session").value = ['2004']
803        self.browser.getControl("Add study level").click()
804
805        # Managers can view and manage course lists
806        self.browser.getLink("100").click()
807        self.assertMatches(
808            '...: 100 (Year 1)...', self.browser.contents)
809        self.browser.getLink("Manage").click()
810        self.browser.getControl(name="form.level_session").value = ['2002']
811        self.browser.getControl("Save").click()
812        self.browser.getControl("Remove selected").click()
813        self.assertMatches('...No ticket selected...', self.browser.contents)
814        ctrl = self.browser.getControl(name='val_id')
815        ctrl.getControl(value='COURSE1').selected = True
816        self.browser.getControl("Remove selected", index=0).click()
817        self.assertTrue('Successfully removed' in self.browser.contents)
818        # Removing course tickets is properly logged
819        logfile = os.path.join(
820            self.app['datacenter'].storage, 'logs', 'students.log')
821        logcontent = open(logfile).read()
822        self.assertTrue(
823            'zope.mgr - students.browser.StudyLevelManageFormPage '
824            '- K1000000 - removed: COURSE1 at 100' in logcontent)
825        self.browser.getLink("here").click()
826        self.browser.getControl(name="form.course").value = ['COURSE1']
827        self.course.credits = 100
828        self.browser.getControl("Add course ticket").click()
829        self.assertMatches(
830            '...Maximum credits exceeded...', self.browser.contents)
831        self.course.credits = 10
832        self.browser.getControl("Add course ticket").click()
833        self.assertTrue('Successfully added' in self.browser.contents)
834        # We can do the same by adding the course on the manage page directly
835        del self.student['studycourse']['100']['COURSE1']
836        self.browser.getControl(name="course").value = 'COURSE1'
837        self.browser.getControl("Add course ticket").click()
838        self.assertTrue('Successfully added' in self.browser.contents)
839        self.browser.getLink("here").click()
840        self.browser.getControl(name="form.course").value = ['COURSE1']
841        self.browser.getControl("Add course ticket").click()
842        self.assertTrue('The ticket exists' in self.browser.contents)
843        self.browser.getControl("Cancel").click()
844        self.browser.getLink("COURSE1").click()
845        self.browser.getLink("Manage").click()
846        self.browser.getControl("Save").click()
847        self.assertTrue('Form has been saved' in self.browser.contents)
848        # Grade and weight have been determined
849        self.browser.open(self.studycourse_path + '/100/COURSE1')
850        self.assertFalse('Grade' in self.browser.contents)
851        self.assertFalse('Weight' in self.browser.contents)
852        self.student['studycourse']['100']['COURSE1'].score = 55
853        self.browser.open(self.studycourse_path + '/100/COURSE1')
854        self.assertTrue('Grade' in self.browser.contents)
855        self.assertTrue('Weight' in self.browser.contents)
856        self.assertEqual(
857            self.student['studycourse']['100']['COURSE1'].grade, 'C')
858        self.assertEqual(
859            self.student['studycourse']['100']['COURSE1'].weight, 3)
860        # We add another ticket to check if GPA will be correctly
861        # calculated (and rounded)
862        courseticket = createObject('waeup.CourseTicket')
863        courseticket.code = 'ANYCODE'
864        courseticket.title = u'Any TITLE'
865        courseticket.credits = 13
866        courseticket.score = 66
867        courseticket.semester = 1
868        courseticket.dcode = u'ANYDCODE'
869        courseticket.fcode = u'ANYFCODE'
870        self.student['studycourse']['100']['COURSE2'] = courseticket
871        self.browser.open(self.student_path + '/studycourse/100')
872        # total credits
873        self.assertEqual(
874            self.student['studycourse']['100'].gpa_params_rectified[1], 23)
875        # weigheted credits = 3 * 10 + 4 * 13
876        self.assertEqual(
877            self.student['studycourse']['100'].gpa_params_rectified[2], 82.0)
878        # sgpa = 82 / 23
879        self.assertEqual(
880            self.student['studycourse']['100'].gpa_params_rectified[0],
881            3.5652173913043477)
882        # Carry-over courses will be collected when next level is created
883        self.browser.open(self.student_path + '/studycourse/manage')
884        # Add next level
885        self.student['studycourse']['100']['COURSE1'].score = 10
886        self.browser.getControl(name="addlevel").value = ['200']
887        self.browser.getControl(name="level_session").value = ['2005']
888        self.browser.getControl("Add study level").click()
889        self.browser.getLink("200").click()
890        self.assertMatches(
891            '...: 200 (Year 2)...', self.browser.contents)
892        # Since COURSE1 has score 10 it becomes a carry-over course
893        # in level 200
894        self.assertEqual(
895            sorted(self.student['studycourse']['200'].keys()), [u'COURSE1'])
896        self.assertTrue(
897            self.student['studycourse']['200']['COURSE1'].carry_over)
898        # Passed and failed courses have been counted
899        self.assertEqual(
900            self.student['studycourse']['100'].passed_params,
901            (1, 1, 13, 10, 'COURSE1 ', ''))
902        self.assertEqual(
903            self.student['studycourse']['200'].passed_params,
904            (0, 0, 0, 0, '', 'COURSE1 '))
905        # And also cumulative params can be calculated. Meanwhile we have the
906        # following courses: COURSE1 and COURSE2 in level 100 and
907        # COURSE1 as carry-over course in level 200.
908        self.assertEqual(
909            self.student['studycourse']['100'].cumulative_params,
910            (2.260869565217391, 23, 52.0, 23, 13))
911        # COURSE1 in level 200 is not taken into consideration
912        # when calculating the gpa.
913        self.assertEqual(
914            self.student['studycourse']['200'].cumulative_params,
915            (2.260869565217391, 23, 52.0, 33, 13))
916        return
917
918    def test_gpa_calculation_with_carryover(self):
919        studylevel = createObject(u'waeup.StudentStudyLevel')
920        studylevel.level = 100
921        studylevel.level_session = 2005
922        self.student['studycourse'].entry_mode = 'ug_ft'
923        self.student['studycourse'].addStudentStudyLevel(
924            self.certificate, studylevel)
925        # First course has been added automatically.
926        # Set score above passmark.
927        studylevel['COURSE1'].score = studylevel['COURSE1'].passmark + 1
928        # GPA is 1.
929        self.assertEqual(
930            self.student['studycourse']['100'].gpa_params_rectified[0], 1.0)
931        # Set score below passmark.
932        studylevel['COURSE1'].score = studylevel['COURSE1'].passmark - 1
933        # GPA is still 0.
934        self.assertEqual(
935            self.student['studycourse']['100'].gpa_params_rectified[0], 0.0)
936        studylevel2 = createObject(u'waeup.StudentStudyLevel')
937        studylevel2.level = 200
938        studylevel2.level_session = 2006
939        self.student['studycourse'].addStudentStudyLevel(
940            self.certificate, studylevel2)
941        # Carry-over course has been autonatically added.
942        studylevel2['COURSE1'].score = 66
943        # The score of the carry-over course is now used for calculation of the
944        # GPA at level 100 ...
945        self.assertEqual(
946            self.student['studycourse']['100'].gpa_params_rectified[0], 4.0)
947        # ... but not at level 200
948        self.assertEqual(
949            self.student['studycourse']['200'].gpa_params_rectified[0], 0.0)
950        return
951
952    def test_manage_payments(self):
953        # Managers can add online school fee payment tickets
954        # if certain requirements are met
955        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
956        self.browser.open(self.payments_path)
957        IWorkflowState(self.student).setState('cleared')
958        self.browser.getLink("Add current session payment ticket").click()
959        self.browser.getControl(name="form.p_category").value = ['schoolfee']
960        self.browser.getControl("Create ticket").click()
961        self.assertMatches('...ticket created...',
962                           self.browser.contents)
963        ctrl = self.browser.getControl(name='val_id')
964        value = ctrl.options[0]
965        self.browser.getLink(value).click()
966        self.assertMatches('...Amount Authorized...',
967                           self.browser.contents)
968        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
969        payment_url = self.browser.url
970        logfile = os.path.join(
971            self.app['datacenter'].storage, 'logs', 'students.log')
972        logcontent = open(logfile).read()
973        self.assertTrue(
974            ' zope.mgr - students.browser.OnlinePaymentAddFormPage - '
975            'K1000000 - added: %s' % value
976            in logcontent)
977        # The pdf payment slip can't yet be opened
978        #self.browser.open(payment_url + '/payment_slip.pdf')
979        #self.assertMatches('...Ticket not yet paid...',
980        #                   self.browser.contents)
981
982        # The same payment (with same p_item, p_session and
983        # p_category) can be initialized a second time if the former
984        # ticket is not yet paid.
985        self.browser.open(self.payments_path)
986        self.browser.getLink("Add current session payment ticket").click()
987        self.browser.getControl(name="form.p_category").value = ['schoolfee']
988        self.browser.getControl("Create ticket").click()
989        self.assertMatches('...Payment ticket created...',
990                           self.browser.contents)
991
992        # The ticket can be found in the payments_catalog
993        cat = queryUtility(ICatalog, name='payments_catalog')
994        results = list(cat.searchResults(p_state=('unpaid', 'unpaid')))
995        self.assertEqual(len(results), 2)
996        self.assertTrue(results[0] is self.student['payments'][value])
997        # Managers can approve the payment
998        # If, by some reason, the state has already changed,
999        # an access code is created after approval.
1000        IWorkflowState(self.student).setState('school fee paid')
1001        self.assertEqual(len(self.app['accesscodes']['SFE-0']), 0)
1002        self.browser.open(payment_url)
1003        self.browser.getLink("Approve payment").click()
1004        self.assertMatches(
1005            '...Payment approved...', self.browser.contents)
1006        # Approval is logged in students.log ...
1007        logcontent = open(logfile).read()
1008        self.assertTrue(
1009            'zope.mgr - students.browser.OnlinePaymentApproveView '
1010            '- K1000000 - schoolfee payment approved'
1011            in logcontent)
1012        # ... and in payments.log
1013        logfile = os.path.join(
1014            self.app['datacenter'].storage, 'logs', 'payments.log')
1015        logcontent = open(logfile).read()
1016        self.assertTrue(
1017            '"zope.mgr",K1000000,%s,schoolfee,40000.0,AP,,,,,,\n' % value
1018            in logcontent)
1019        # The authorized amount has been stored in the new access code
1020        self.assertEqual(
1021            self.app['accesscodes']['SFE-0'].values()[0].cost, 40000.0)
1022
1023        # The catalog has been updated
1024        results = list(cat.searchResults(p_state=('unpaid', 'unpaid')))
1025        self.assertTrue(len(results), 0)
1026        results = list(cat.searchResults(p_state=('paid', 'paid')))
1027        self.assertTrue(len(results), 1)
1028        self.assertTrue(results[0] is self.student['payments'][value])
1029
1030        # Payments can't be approved twice
1031        self.browser.open(payment_url + '/approve')
1032        self.assertMatches('...This ticket has already been paid...',
1033                          self.browser.contents)
1034
1035        # Now the first ticket is paid and no more ticket of same type
1036        # (with same p_item, p_session and p_category) can be added.
1037        # First we have to reset the workflow state.
1038        IWorkflowState(self.student).setState('cleared')
1039        self.browser.open(self.payments_path)
1040        self.browser.getLink("Add current session payment ticket").click()
1041        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1042        self.browser.getControl("Create ticket").click()
1043        self.assertMatches(
1044            '...This type of payment has already been made...',
1045            self.browser.contents)
1046
1047        # Managers can open the pdf payment slip
1048        self.browser.open(payment_url)
1049        self.browser.getLink("Download payment slip").click()
1050        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1051        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1052
1053        # Managers can remove online school fee payment tickets
1054        self.browser.open(self.payments_path)
1055        self.browser.getControl("Remove selected").click()
1056        self.assertMatches('...No payment selected...', self.browser.contents)
1057        ctrl = self.browser.getControl(name='val_id')
1058        value = ctrl.options[0]
1059        ctrl.getControl(value=value).selected = True
1060        self.browser.getControl("Remove selected", index=0).click()
1061        self.assertTrue('Successfully removed' in self.browser.contents)
1062
1063        # Managers can add online clearance payment tickets
1064        self.browser.open(self.payments_path + '/addop')
1065        self.browser.getControl(name="form.p_category").value = ['clearance']
1066        self.browser.getControl("Create ticket").click()
1067        self.assertMatches('...ticket created...',
1068                           self.browser.contents)
1069
1070        # Managers can approve the payment
1071        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
1072        ctrl = self.browser.getControl(name='val_id')
1073        value = ctrl.options[1] # The clearance payment is the second in the table
1074        self.browser.getLink(value).click()
1075        self.browser.open(self.browser.url + '/approve')
1076        self.assertMatches('...Payment approved...',
1077                          self.browser.contents)
1078        expected = '''...
1079        <td>
1080          <span>Paid</span>
1081        </td>...'''
1082        self.assertMatches(expected,self.browser.contents)
1083        # The new CLR-0 pin has been created
1084        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
1085        pin = self.app['accesscodes']['CLR-0'].keys()[0]
1086        ac = self.app['accesscodes']['CLR-0'][pin]
1087        self.assertEqual(ac.owner, self.student_id)
1088        self.assertEqual(ac.cost, 3456.0)
1089
1090        # Managers can add online transcript payment tickets
1091        self.browser.open(self.payments_path + '/addop')
1092        self.browser.getControl(name="form.p_category").value = ['transcript']
1093        self.browser.getControl("Create ticket").click()
1094        self.assertMatches('...ticket created...',
1095                           self.browser.contents)
1096
1097        # Managers can approve the payment
1098        self.assertEqual(len(self.app['accesscodes']['TSC-0']),0)
1099        ctrl = self.browser.getControl(name='val_id')
1100        value = ctrl.options[2] # The clearance payment is the third in the table
1101        self.browser.getLink(value).click()
1102        self.browser.open(self.browser.url + '/approve')
1103        self.assertMatches('...Payment approved...',
1104                          self.browser.contents)
1105        expected = '''...
1106        <td>
1107          <span>Paid</span>
1108        </td>...'''
1109        self.assertMatches(expected,self.browser.contents)
1110        # The new CLR-0 pin has been created
1111        self.assertEqual(len(self.app['accesscodes']['TSC-0']),1)
1112        pin = self.app['accesscodes']['TSC-0'].keys()[0]
1113        ac = self.app['accesscodes']['TSC-0'][pin]
1114        self.assertEqual(ac.owner, self.student_id)
1115        self.assertEqual(ac.cost, 4567.0)
1116        return
1117
1118    def test_add_transfer_payment(self):
1119        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1120        self.browser.open(self.payments_path)
1121        self.browser.getLink("Add current session payment ticket").click()
1122        self.browser.getControl(name="form.p_category").value = ['transfer']
1123        self.browser.getControl(name="new_programme").value = 'my new study course'
1124        self.browser.getControl("Create ticket").click()
1125        self.assertMatches('...ticket created...',
1126                           self.browser.contents)
1127        ctrl = self.browser.getControl(name='val_id')
1128        value = ctrl.options[0]
1129        self.browser.getLink(value).click()
1130        self.assertMatches('...my new study course...',
1131                           self.browser.contents)
1132        self.assertEqual(self.student['payments'][value].p_item, u'my new study course')
1133
1134    def test_manage_payments_bypass_ac_creation(self):
1135        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1136        self.browser.open(self.payments_path)
1137        IWorkflowState(self.student).setState('cleared')
1138        self.browser.getLink("Add current session payment ticket").click()
1139        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1140        self.browser.getControl("Create ticket").click()
1141        ctrl = self.browser.getControl(name='val_id')
1142        value = ctrl.options[0]
1143        self.browser.getLink(value).click()
1144        payment_url = self.browser.url
1145        logfile = os.path.join(
1146            self.app['datacenter'].storage, 'logs', 'students.log')
1147        # The ticket can be found in the payments_catalog
1148        cat = queryUtility(ICatalog, name='payments_catalog')
1149        results = list(cat.searchResults(p_state=('unpaid', 'unpaid')))
1150        self.assertTrue(len(results), 1)
1151        self.assertTrue(results[0] is self.student['payments'][value])
1152        # Managers can approve the payment
1153        self.browser.open(payment_url)
1154        self.browser.getLink("Approve payment").click()
1155        self.assertMatches('...Payment approved...',
1156                          self.browser.contents)
1157        # Approval is logged in students.log ...
1158        logcontent = open(logfile).read()
1159        self.assertTrue(
1160            'zope.mgr - students.browser.OnlinePaymentApproveView '
1161            '- K1000000 - schoolfee payment approved'
1162            in logcontent)
1163        # ... and in payments.log
1164        logfile = os.path.join(
1165            self.app['datacenter'].storage, 'logs', 'payments.log')
1166        logcontent = open(logfile).read()
1167        self.assertTrue(
1168            '"zope.mgr",K1000000,%s,schoolfee,40000.0,AP,,,,,,\n' % value
1169            in logcontent)
1170        # Student is in state school fee paid, no activation
1171        # code was necessary.
1172        self.assertEqual(self.student.state, 'school fee paid')
1173        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
1174        return
1175
1176    def test_payment_disabled(self):
1177        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1178        self.browser.open(self.payments_path)
1179        IWorkflowState(self.student).setState('cleared')
1180        self.browser.getLink("Add current session payment ticket").click()
1181        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1182        self.browser.getControl("Create ticket").click()
1183        self.assertMatches('...ticket created...',
1184                           self.browser.contents)
1185        self.app['configuration']['2004'].payment_disabled = ['sf_all']
1186        self.browser.getLink("Add current session payment ticket").click()
1187        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1188        self.browser.getControl("Create ticket").click()
1189        self.assertMatches('...This category of payments has been disabled...',
1190                           self.browser.contents)
1191        return
1192
1193    def test_manage_balance_payments(self):
1194
1195        # Login
1196        #self.browser.open(self.login_path)
1197        #self.browser.getControl(name="form.login").value = self.student_id
1198        #self.browser.getControl(name="form.password").value = 'spwd'
1199        #self.browser.getControl("Login").click()
1200
1201        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1202        self.browser.open(self.payments_path)
1203
1204        # Managers can add balance school fee payment tickets in any state.
1205        IWorkflowState(self.student).setState('courses registered')
1206        self.browser.open(self.payments_path)
1207        self.browser.getLink("Add balance payment ticket").click()
1208
1209        # Balance payment form is provided
1210        self.assertEqual(self.student.current_session, 2004)
1211        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1212        self.browser.getControl(name="form.balance_session").value = ['2004']
1213        self.browser.getControl(name="form.balance_level").value = ['300']
1214        self.browser.getControl(name="form.balance_amount").value = '-567.8'
1215        self.browser.getControl("Create ticket").click()
1216        self.assertMatches('...Amount must be greater than 0...',
1217                           self.browser.contents)
1218        self.browser.getControl(name="form.balance_amount").value = '0'
1219        self.browser.getControl("Create ticket").click()
1220        self.assertMatches('...Amount must be greater than 0...',
1221                           self.browser.contents)
1222        self.browser.getControl(name="form.balance_amount").value = '567.8'
1223        self.browser.getControl("Create ticket").click()
1224        self.assertMatches('...ticket created...',
1225                           self.browser.contents)
1226        ctrl = self.browser.getControl(name='val_id')
1227        value = ctrl.options[0]
1228        self.browser.getLink(value).click()
1229        self.assertMatches('...Amount Authorized...',
1230                           self.browser.contents)
1231        self.assertEqual(self.student['payments'][value].amount_auth, 567.8)
1232        # Payment attributes are properly set
1233        self.assertEqual(self.student['payments'][value].p_session, 2004)
1234        self.assertEqual(self.student['payments'][value].p_level, 300)
1235        self.assertEqual(self.student['payments'][value].p_item, u'Balance')
1236        self.assertEqual(self.student['payments'][value].p_category, 'schoolfee')
1237        # Adding payment tickets is logged.
1238        logfile = os.path.join(
1239            self.app['datacenter'].storage, 'logs', 'students.log')
1240        logcontent = open(logfile).read()
1241        self.assertTrue('zope.mgr - students.browser.BalancePaymentAddFormPage '
1242                        '- K1000000 - added: %s' % value in logcontent)
1243
1244    def test_manage_accommodation(self):
1245        logfile = os.path.join(
1246            self.app['datacenter'].storage, 'logs', 'students.log')
1247        # Managers can add online booking fee payment tickets and open the
1248        # callback view (see test_manage_payments)
1249        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1250        self.browser.open(self.payments_path)
1251        self.browser.getLink("Add current session payment ticket").click()
1252        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1253        # If student is not in accommodation session, payment cannot be processed
1254        self.app['hostels'].accommodation_session = 2011
1255        self.browser.getControl("Create ticket").click()
1256        self.assertMatches('...Your current session does not match...',
1257                           self.browser.contents)
1258        self.app['hostels'].accommodation_session = 2004
1259        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1260        self.browser.getControl("Create ticket").click()
1261        ctrl = self.browser.getControl(name='val_id')
1262        value = ctrl.options[0]
1263        self.browser.getLink(value).click()
1264        self.browser.open(self.browser.url + '/approve')
1265        # The new HOS-0 pin has been created
1266        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
1267        pin = self.app['accesscodes']['HOS-0'].keys()[0]
1268        ac = self.app['accesscodes']['HOS-0'][pin]
1269        self.assertEqual(ac.owner, self.student_id)
1270        parts = pin.split('-')[1:]
1271        sfeseries, sfenumber = parts
1272        # Managers can use HOS code and book a bed space with it
1273        self.browser.open(self.acco_path)
1274        self.browser.getControl("Book accommodation").click()
1275        self.assertMatches('...You are in the wrong...',
1276                           self.browser.contents)
1277        IWorkflowInfo(self.student).fireTransition('admit')
1278        # An existing HOS code can only be used if students
1279        # are in accommodation session
1280        self.student['studycourse'].current_session = 2003
1281        self.browser.getControl("Book accommodation").click()
1282        self.assertMatches('...Your current session does not match...',
1283                           self.browser.contents)
1284        self.student['studycourse'].current_session = 2004
1285        # All requirements are met and ticket can be created
1286        self.browser.getControl("Book accommodation").click()
1287        self.assertMatches('...Activation Code:...',
1288                           self.browser.contents)
1289        self.browser.getControl(name="ac_series").value = sfeseries
1290        self.browser.getControl(name="ac_number").value = sfenumber
1291        self.browser.getControl("Create bed ticket").click()
1292        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1293                           self.browser.contents)
1294        # Bed has been allocated
1295        bed1 = self.app['hostels']['hall-1']['hall-1_A_101_A']
1296        self.assertTrue(bed1.owner == self.student_id)
1297        # BedTicketAddPage is now blocked
1298        self.browser.getControl("Book accommodation").click()
1299        self.assertMatches('...You already booked a bed space...',
1300            self.browser.contents)
1301        # The bed ticket displays the data correctly
1302        self.browser.open(self.acco_path + '/2004')
1303        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1304                           self.browser.contents)
1305        self.assertMatches('...2004/2005...', self.browser.contents)
1306        self.assertMatches('...regular_male_fr...', self.browser.contents)
1307        self.assertMatches('...%s...' % pin, self.browser.contents)
1308        # Booking is properly logged
1309        logcontent = open(logfile).read()
1310        self.assertTrue('zope.mgr - students.browser.BedTicketAddPage '
1311            '- K1000000 - booked: hall-1_A_101_A' in logcontent)
1312        # Managers can relocate students if the student's bed_type has changed
1313        self.browser.getLink("Relocate student").click()
1314        self.assertMatches(
1315            "...Student can't be relocated...", self.browser.contents)
1316        self.student.sex = u'f'
1317        self.browser.getLink("Relocate student").click()
1318        self.assertMatches(
1319            "...Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
1320        self.assertTrue(bed1.owner == NOT_OCCUPIED)
1321        bed2 = self.app['hostels']['hall-1']['hall-1_A_101_B']
1322        self.assertTrue(bed2.owner == self.student_id)
1323        self.assertTrue(self.student['accommodation'][
1324            '2004'].bed_type == u'regular_female_fr')
1325        # Relocation is properly logged
1326        logcontent = open(logfile).read()
1327        self.assertTrue('zope.mgr - students.accommodation.BedTicket '
1328            '- K1000000 - relocated: hall-1_A_101_B' in logcontent)
1329        # The payment object still shows the original payment item
1330        payment_id = self.student['payments'].keys()[0]
1331        payment = self.student['payments'][payment_id]
1332        self.assertTrue(payment.p_item == u'regular_male_fr')
1333        # Managers can relocate students if the bed's bed_type has changed
1334        bed1.bed_type = u'regular_female_fr'
1335        bed2.bed_type = u'regular_male_fr'
1336        notify(grok.ObjectModifiedEvent(bed1))
1337        notify(grok.ObjectModifiedEvent(bed2))
1338        self.browser.getLink("Relocate student").click()
1339        self.assertMatches(
1340            "...Student relocated...", self.browser.contents)
1341        self.assertMatches(
1342            "... Hall 1, Block A, Room 101, Bed A...", self.browser.contents)
1343        self.assertMatches(bed1.owner, self.student_id)
1344        self.assertMatches(bed2.owner, NOT_OCCUPIED)
1345        # Managers can't relocate students if bed is reserved
1346        self.student.sex = u'm'
1347        bed1.bed_type = u'regular_female_reserved'
1348        notify(grok.ObjectModifiedEvent(bed1))
1349        self.browser.getLink("Relocate student").click()
1350        self.assertMatches(
1351            "...Students in reserved beds can't be relocated...",
1352            self.browser.contents)
1353        # Managers can relocate students if booking has been cancelled but
1354        # other bed space has been manually allocated after cancellation
1355        old_owner = bed1.releaseBed()
1356        self.assertMatches(old_owner, self.student_id)
1357        bed2.owner = self.student_id
1358        self.browser.open(self.acco_path + '/2004')
1359        self.assertMatches(
1360            "...booking cancelled...", self.browser.contents)
1361        self.browser.getLink("Relocate student").click()
1362        # We didn't informed the catalog therefore the new owner is not found
1363        self.assertMatches(
1364            "...There is no free bed in your category regular_male_fr...",
1365            self.browser.contents)
1366        # Now we fire the event properly
1367        notify(grok.ObjectModifiedEvent(bed2))
1368        self.browser.getLink("Relocate student").click()
1369        self.assertMatches(
1370            "...Student relocated...", self.browser.contents)
1371        self.assertMatches(
1372            "... Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
1373          # Managers can delete bed tickets
1374        self.browser.open(self.acco_path)
1375        ctrl = self.browser.getControl(name='val_id')
1376        value = ctrl.options[0]
1377        ctrl.getControl(value=value).selected = True
1378        self.browser.getControl("Remove selected", index=0).click()
1379        self.assertMatches('...Successfully removed...', self.browser.contents)
1380        # The bed has been properly released by the event handler
1381        self.assertMatches(bed1.owner, NOT_OCCUPIED)
1382        self.assertMatches(bed2.owner, NOT_OCCUPIED)
1383        return
1384
1385    def test_manage_workflow(self):
1386        # Managers can pass through the whole workflow
1387        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1388        student = self.app['students'][self.student_id]
1389        self.browser.open(self.trigtrans_path)
1390        self.assertTrue(student.clearance_locked)
1391        self.browser.getControl(name="transition").value = ['admit']
1392        self.browser.getControl("Save").click()
1393        self.assertTrue(student.clearance_locked)
1394        self.browser.getControl(name="transition").value = ['start_clearance']
1395        self.browser.getControl("Save").click()
1396        self.assertFalse(student.clearance_locked)
1397        self.browser.getControl(name="transition").value = ['request_clearance']
1398        self.browser.getControl("Save").click()
1399        self.assertTrue(student.clearance_locked)
1400        self.browser.getControl(name="transition").value = ['clear']
1401        self.browser.getControl("Save").click()
1402        # Managers approve payment, they do not pay
1403        self.assertFalse('pay_first_school_fee' in self.browser.contents)
1404        self.browser.getControl(
1405            name="transition").value = ['approve_first_school_fee']
1406        self.browser.getControl("Save").click()
1407        self.browser.getControl(name="transition").value = ['reset6']
1408        self.browser.getControl("Save").click()
1409        # In state returning the pay_school_fee transition triggers some
1410        # changes of attributes
1411        self.browser.getControl(name="transition").value = ['approve_school_fee']
1412        self.browser.getControl("Save").click()
1413        self.assertEqual(student['studycourse'].current_session, 2005) # +1
1414        self.assertEqual(student['studycourse'].current_level, 200) # +100
1415        self.assertEqual(student['studycourse'].current_verdict, '0') # 0 = Zero = not set
1416        self.assertEqual(student['studycourse'].previous_verdict, 'A')
1417        self.browser.getControl(name="transition").value = ['register_courses']
1418        self.browser.getControl("Save").click()
1419        self.browser.getControl(name="transition").value = ['validate_courses']
1420        self.browser.getControl("Save").click()
1421        self.browser.getControl(name="transition").value = ['return']
1422        self.browser.getControl("Save").click()
1423        return
1424
1425    def test_manage_pg_workflow(self):
1426        # Managers can pass through the whole workflow
1427        IWorkflowState(self.student).setState('school fee paid')
1428        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1429        self.browser.open(self.trigtrans_path)
1430        self.assertTrue('<option value="reset6">' in self.browser.contents)
1431        self.assertTrue('<option value="register_courses">' in self.browser.contents)
1432        self.assertTrue('<option value="reset5">' in self.browser.contents)
1433        self.certificate.study_mode = 'pg_ft'
1434        self.browser.open(self.trigtrans_path)
1435        self.assertFalse('<option value="reset6">' in self.browser.contents)
1436        self.assertFalse('<option value="register_courses">' in self.browser.contents)
1437        self.assertTrue('<option value="reset5">' in self.browser.contents)
1438        return
1439
1440    def test_manage_import(self):
1441        # Managers can import student data files
1442        datacenter_path = 'http://localhost/app/datacenter'
1443        # Prepare a csv file for students
1444        open('students.csv', 'wb').write(
1445"""firstname,lastname,reg_number,date_of_birth,matric_number,email,phone,sex,password
1446Aaren,Pieri,1,1990-01-02,100000,aa@aa.ng,1234,m,mypwd1
1447Claus,Finau,2,1990-01-03,100001,aa@aa.ng,1234,m,mypwd1
1448Brit,Berson,3,1990-01-04,100001,aa@aa.ng,1234,m,mypwd1
1449""")
1450        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1451        self.browser.open(datacenter_path)
1452        self.browser.getLink('Upload data').click()
1453        filecontents = StringIO(open('students.csv', 'rb').read())
1454        filewidget = self.browser.getControl(name='uploadfile:file')
1455        filewidget.add_file(filecontents, 'text/plain', 'students.csv')
1456        self.browser.getControl(name='SUBMIT').click()
1457        self.browser.getLink('Process data').click()
1458        self.browser.getLink("Switch maintenance mode").click()
1459        button = lookup_submit_value(
1460            'select', 'students_zope.mgr.csv', self.browser)
1461        button.click()
1462        importerselect = self.browser.getControl(name='importer')
1463        modeselect = self.browser.getControl(name='mode')
1464        importerselect.getControl('Student Processor').selected = True
1465        modeselect.getControl(value='create').selected = True
1466        self.browser.getControl('Proceed to step 3').click()
1467        self.assertTrue('Header fields OK' in self.browser.contents)
1468        self.browser.getControl('Perform import').click()
1469        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
1470        self.assertTrue('Successfully processed 2 rows' in self.browser.contents)
1471        self.assertTrue('Batch processing finished' in self.browser.contents)
1472        open('studycourses.csv', 'wb').write(
1473"""reg_number,matric_number,certificate,current_session,current_level
14741,,CERT1,2008,100
1475,100001,CERT1,2008,100
1476,100002,CERT1,2008,100
1477""")
1478        self.browser.open(datacenter_path)
1479        self.browser.getLink('Upload data').click()
1480        filecontents = StringIO(open('studycourses.csv', 'rb').read())
1481        filewidget = self.browser.getControl(name='uploadfile:file')
1482        filewidget.add_file(filecontents, 'text/plain', 'studycourses.csv')
1483        self.browser.getControl(name='SUBMIT').click()
1484        self.browser.getLink('Process data').click()
1485        # Meanwhile maintenance mode is disabled again.
1486        self.browser.getLink("Switch maintenance mode").click()
1487        button = lookup_submit_value(
1488            'select', 'studycourses_zope.mgr.csv', self.browser)
1489        button.click()
1490        importerselect = self.browser.getControl(name='importer')
1491        modeselect = self.browser.getControl(name='mode')
1492        importerselect.getControl(
1493            'StudentStudyCourse Processor (update only)').selected = True
1494        modeselect.getControl(value='create').selected = True
1495        self.browser.getControl('Proceed to step 3').click()
1496        self.assertTrue('Update mode only' in self.browser.contents)
1497        self.browser.getControl('Proceed to step 3').click()
1498        self.assertTrue('Header fields OK' in self.browser.contents)
1499        self.browser.getControl('Perform import').click()
1500        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
1501        self.assertTrue('Successfully processed 2 rows'
1502                        in self.browser.contents)
1503        # The students are properly indexed and we can
1504        # thus find a student in  the department
1505        self.browser.open(self.manage_container_path)
1506        self.browser.getControl(name="searchtype").value = ['depcode']
1507        self.browser.getControl(name="searchterm").value = 'dep1'
1508        self.browser.getControl("Find student(s)").click()
1509        self.assertTrue('Aaren Pieri' in self.browser.contents)
1510        # We can search for a new student by name ...
1511        self.browser.getControl(name="searchtype").value = ['fullname']
1512        self.browser.getControl(name="searchterm").value = 'Claus'
1513        self.browser.getControl("Find student(s)").click()
1514        self.assertTrue('Claus Finau' in self.browser.contents)
1515        # ... and check if the imported password has been properly set
1516        ctrl = self.browser.getControl(name='entries')
1517        value = ctrl.options[0]
1518        claus = self.app['students'][value]
1519        self.assertTrue(IUserAccount(claus).checkPassword('mypwd1'))
1520        return
1521
1522    def init_clearance_officer(self):
1523        # Create clearance officer
1524        self.app['users'].addUser('mrclear', SECRET)
1525        self.app['users']['mrclear'].email = 'mrclear@foo.ng'
1526        self.app['users']['mrclear'].title = 'Carlo Pitter'
1527        # Clearance officers need not necessarily to get
1528        # the StudentsOfficer site role
1529        #prmglobal = IPrincipalRoleManager(self.app)
1530        #prmglobal.assignRoleToPrincipal('waeup.StudentsOfficer', 'mrclear')
1531        # Assign local ClearanceOfficer role
1532        self.department = self.app['faculties']['fac1']['dep1']
1533        prmlocal = IPrincipalRoleManager(self.department)
1534        prmlocal.assignRoleToPrincipal('waeup.local.ClearanceOfficer', 'mrclear')
1535        IWorkflowState(self.student).setState('clearance started')
1536        # Add another student for testing
1537        other_student = Student()
1538        other_student.firstname = u'Dep2'
1539        other_student.lastname = u'Student'
1540        self.app['students'].addStudent(other_student)
1541        self.other_student_path = (
1542            'http://localhost/app/students/%s' % other_student.student_id)
1543        # Login as clearance officer
1544        self.browser.open(self.login_path)
1545        self.browser.getControl(name="form.login").value = 'mrclear'
1546        self.browser.getControl(name="form.password").value = SECRET
1547        self.browser.getControl("Login").click()
1548
1549    def test_handle_clearance_by_co(self):
1550        self.init_clearance_officer()
1551        self.assertMatches('...You logged in...', self.browser.contents)
1552        # CO is landing on index page.
1553        self.assertEqual(self.browser.url, 'http://localhost/app/index')
1554        # CO can see his roles
1555        self.browser.getLink("My Roles").click()
1556        self.assertMatches(
1557            '...<div>Academics Officer (view only)</div>...',
1558            self.browser.contents)
1559        # But not his local role ...
1560        self.assertFalse('Clearance Officer' in self.browser.contents)
1561        # ... because we forgot to notify the department that the local role
1562        # has changed.
1563        notify(LocalRoleSetEvent(
1564            self.department, 'waeup.local.ClearanceOfficer', 'mrclear',
1565            granted=True))
1566        self.browser.open('http://localhost/app/users/mrclear/my_roles')
1567        self.assertTrue('Clearance Officer' in self.browser.contents)
1568        self.assertMatches(
1569            '...<a href="http://localhost/app/faculties/fac1/dep1">...',
1570            self.browser.contents)
1571        # CO can view the student ...
1572        self.browser.open(self.clearance_path)
1573        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1574        self.assertEqual(self.browser.url, self.clearance_path)
1575        # ... but not other students.
1576        self.assertRaises(
1577            Unauthorized, self.browser.open, self.other_student_path)
1578        # Clearance is disabled for this session.
1579        self.browser.open(self.clearance_path)
1580        self.assertFalse('Clear student' in self.browser.contents)
1581        self.browser.open(self.student_path + '/clear')
1582        self.assertTrue('Clearance is disabled for this session'
1583            in self.browser.contents)
1584        self.app['configuration']['2004'].clearance_enabled = True
1585        # Only in state clearance requested the CO does see the 'Clear' button.
1586        self.browser.open(self.clearance_path)
1587        self.assertFalse('Clear student' in self.browser.contents)
1588        self.browser.open(self.student_path + '/clear')
1589        self.assertTrue('Student is in wrong state.'
1590            in self.browser.contents)
1591        IWorkflowInfo(self.student).fireTransition('request_clearance')
1592        self.browser.open(self.clearance_path)
1593        self.assertTrue('Clear student' in self.browser.contents)
1594        self.browser.getLink("Clear student").click()
1595        self.assertTrue('Student has been cleared' in self.browser.contents)
1596        self.assertTrue('cleared' in self.browser.contents)
1597        self.browser.open(self.history_path)
1598        self.assertTrue('Cleared by Carlo Pitter' in self.browser.contents)
1599        # Hide real name.
1600        self.app['users']['mrclear'].public_name = 'My Public Name'
1601        self.browser.open(self.clearance_path)
1602        self.browser.getLink("Reject clearance").click()
1603        self.assertEqual(
1604            self.browser.url, self.student_path + '/reject_clearance')
1605        # Type comment why.
1606        self.browser.getControl(name="form.officer_comment").value = (
1607            'Dear Student,\n'
1608            'You did not fill properly.')
1609        self.browser.getControl("Save comment").click()
1610        self.assertTrue('Clearance has been annulled' in self.browser.contents)
1611        url = ('http://localhost/app/students/K1000000/'
1612              'contactstudent?body=Dear+Student%2C%0AYou+did+not+fill+properly.'
1613              '&subject=Clearance+has+been+annulled.')
1614        # CO does now see the prefilled contact form and can send a message.
1615        self.assertEqual(self.browser.url, url)
1616        self.assertTrue('clearance started' in self.browser.contents)
1617        self.assertTrue('name="form.subject" size="20" type="text" '
1618            'value="Clearance has been annulled."'
1619            in self.browser.contents)
1620        self.assertTrue('name="form.body" rows="10" >Dear Student,'
1621            in self.browser.contents)
1622        self.browser.getControl("Send message now").click()
1623        self.assertTrue('Your message has been sent' in self.browser.contents)
1624        # The comment has been stored ...
1625        self.assertEqual(self.student.officer_comment,
1626            u'Dear Student,\nYou did not fill properly.')
1627        # ... and logged.
1628        logfile = os.path.join(
1629            self.app['datacenter'].storage, 'logs', 'students.log')
1630        logcontent = open(logfile).read()
1631        self.assertTrue(
1632            'INFO - mrclear - students.browser.StudentRejectClearancePage - '
1633            'K1000000 - comment: Dear Student,<br>You did not fill '
1634            'properly.\n' in logcontent)
1635        self.browser.open(self.history_path)
1636        self.assertTrue("Reset to 'clearance started' by My Public Name" in
1637            self.browser.contents)
1638        IWorkflowInfo(self.student).fireTransition('request_clearance')
1639        self.browser.open(self.clearance_path)
1640        self.browser.getLink("Reject clearance").click()
1641        self.browser.getControl("Save comment").click()
1642        self.assertTrue('Clearance request has been rejected'
1643            in self.browser.contents)
1644        self.assertTrue('clearance started' in self.browser.contents)
1645        # The CO can't clear students if not in state
1646        # clearance requested.
1647        self.browser.open(self.student_path + '/clear')
1648        self.assertTrue('Student is in wrong state'
1649            in self.browser.contents)
1650        # The CO can go to his department throug the my_roles page ...
1651        self.browser.open('http://localhost/app/users/mrclear/my_roles')
1652        self.browser.getLink("http://localhost/app/faculties/fac1/dep1").click()
1653        # ... and view the list of students.
1654        self.browser.getLink("Show students").click()
1655        self.browser.getControl(name="session").value = ['2004']
1656        self.browser.getControl(name="level").value = ['200']
1657        self.browser.getControl("Show").click()
1658        self.assertFalse(self.student_id in self.browser.contents)
1659        self.browser.getControl(name="session").value = ['2004']
1660        self.browser.getControl(name="level").value = ['100']
1661        self.browser.getControl("Show").click()
1662        self.assertTrue(self.student_id in self.browser.contents)
1663        # The comment is indicated by 'yes'.
1664        self.assertTrue('<td><span>yes</span></td>' in self.browser.contents)
1665        # Check if the enquiries form is not pre-filled with officer_comment
1666        # (regression test).
1667        self.browser.getLink("Logout").click()
1668        self.browser.open('http://localhost/app/enquiries')
1669        self.assertFalse(
1670            'You did not fill properly'
1671            in self.browser.contents)
1672        # When a student is cleared the comment is automatically deleted
1673        IWorkflowInfo(self.student).fireTransition('request_clearance')
1674        IWorkflowInfo(self.student).fireTransition('clear')
1675        self.assertEqual(self.student.officer_comment, None)
1676        return
1677
1678    def test_handle_mass_clearance_by_co(self):
1679        self.init_clearance_officer()
1680        # Additional setups according to test above
1681        notify(LocalRoleSetEvent(
1682            self.department, 'waeup.local.ClearanceOfficer', 'mrclear',
1683            granted=True))
1684        self.app['configuration']['2004'].clearance_enabled = True
1685        IWorkflowState(self.student).setState('clearance requested')
1686        # Update the catalog
1687        notify(grok.ObjectModifiedEvent(self.student))
1688        # The CO can go to the department and clear all students in department
1689        self.browser.open('http://localhost/app/faculties/fac1/dep1')
1690        self.browser.getLink("Clear all students").click()
1691        self.assertTrue('1 students have been cleared' in self.browser.contents)
1692        self.browser.open(self.history_path)
1693        self.assertTrue('Cleared by Carlo Pitter' in self.browser.contents)
1694        logfile = os.path.join(
1695            self.app['datacenter'].storage, 'logs', 'students.log')
1696        logcontent = open(logfile).read()
1697        self.assertTrue(
1698            'INFO - mrclear - K1000000 - Cleared' in logcontent)
1699        self.browser.open('http://localhost/app/faculties/fac1/dep1')
1700        self.browser.getLink("Clear all students").click()
1701        self.assertTrue('0 students have been cleared' in self.browser.contents)
1702        return
1703
1704    def test_handle_courses_by_ca(self):
1705        self.app['users'].addUser('mrsadvise', SECRET)
1706        self.app['users']['mrsadvise'].email = 'mradvise@foo.ng'
1707        self.app['users']['mrsadvise'].title = u'Helen Procter'
1708        # Assign local CourseAdviser100 role for a certificate
1709        cert = self.app['faculties']['fac1']['dep1'].certificates['CERT1']
1710        prmlocal = IPrincipalRoleManager(cert)
1711        prmlocal.assignRoleToPrincipal('waeup.local.CourseAdviser100', 'mrsadvise')
1712        IWorkflowState(self.student).setState('school fee paid')
1713        # Login as course adviser.
1714        self.browser.open(self.login_path)
1715        self.browser.getControl(name="form.login").value = 'mrsadvise'
1716        self.browser.getControl(name="form.password").value = SECRET
1717        self.browser.getControl("Login").click()
1718        self.assertMatches('...You logged in...', self.browser.contents)
1719        # CO can see his roles.
1720        self.browser.getLink("My Roles").click()
1721        self.assertMatches(
1722            '...<div>Academics Officer (view only)</div>...',
1723            self.browser.contents)
1724        # But not his local role ...
1725        self.assertFalse('Course Adviser' in self.browser.contents)
1726        # ... because we forgot to notify the certificate that the local role
1727        # has changed.
1728        notify(LocalRoleSetEvent(
1729            cert, 'waeup.local.CourseAdviser100', 'mrsadvise', granted=True))
1730        self.browser.open('http://localhost/app/users/mrsadvise/my_roles')
1731        self.assertTrue('Course Adviser 100L' in self.browser.contents)
1732        self.assertMatches(
1733            '...<a href="http://localhost/app/faculties/fac1/dep1/certificates/CERT1">...',
1734            self.browser.contents)
1735        # CA can view the student ...
1736        self.browser.open(self.student_path)
1737        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1738        self.assertEqual(self.browser.url, self.student_path)
1739        # ... but not other students.
1740        other_student = Student()
1741        other_student.firstname = u'Dep2'
1742        other_student.lastname = u'Student'
1743        self.app['students'].addStudent(other_student)
1744        other_student_path = (
1745            'http://localhost/app/students/%s' % other_student.student_id)
1746        self.assertRaises(
1747            Unauthorized, self.browser.open, other_student_path)
1748        # We add study level 110 to the student's studycourse.
1749        studylevel = StudentStudyLevel()
1750        studylevel.level = 110
1751        self.student['studycourse'].addStudentStudyLevel(
1752            cert,studylevel)
1753        L110_student_path = self.studycourse_path + '/110'
1754        # The CA can neither see the Validate nor the Edit button.
1755        self.browser.open(L110_student_path)
1756        self.assertFalse('Validate courses' in self.browser.contents)
1757        self.assertFalse('Edit' in self.browser.contents)
1758        IWorkflowInfo(self.student).fireTransition('register_courses')
1759        self.browser.open(L110_student_path)
1760        self.assertFalse('Validate courses' in self.browser.contents)
1761        self.assertFalse('Edit' in self.browser.contents)
1762        # Only in state courses registered and only if the current level
1763        # corresponds with the name of the study level object
1764        # the 100L CA does see the 'Validate' button but not
1765        # the edit button.
1766        self.student['studycourse'].current_level = 110
1767        self.browser.open(L110_student_path)
1768        self.assertFalse('Edit' in self.browser.contents)
1769        self.assertTrue('Validate courses' in self.browser.contents)
1770        # But a 100L CA does not see the button at other levels.
1771        studylevel2 = StudentStudyLevel()
1772        studylevel2.level = 200
1773        self.student['studycourse'].addStudentStudyLevel(
1774            cert,studylevel2)
1775        L200_student_path = self.studycourse_path + '/200'
1776        self.browser.open(L200_student_path)
1777        self.assertFalse('Edit' in self.browser.contents)
1778        self.assertFalse('Validate courses' in self.browser.contents)
1779        self.browser.open(L110_student_path)
1780        self.browser.getLink("Validate courses").click()
1781        self.assertTrue('Course list has been validated' in self.browser.contents)
1782        self.assertTrue('courses validated' in self.browser.contents)
1783        self.assertEqual(self.student['studycourse']['110'].validated_by,
1784            'Helen Procter')
1785        self.assertMatches(
1786            '<YYYY-MM-DD hh:mm:ss>',
1787            self.student['studycourse']['110'].validation_date.strftime(
1788                "%Y-%m-%d %H:%M:%S"))
1789        self.browser.getLink("Reject courses").click()
1790        self.assertTrue('Course list request has been annulled.'
1791            in self.browser.contents)
1792        urlmessage = 'Course+list+request+has+been+annulled.'
1793        self.assertEqual(self.browser.url, self.student_path +
1794            '/contactstudent?subject=%s' % urlmessage)
1795        self.assertTrue('school fee paid' in self.browser.contents)
1796        self.assertTrue(self.student['studycourse']['110'].validated_by is None)
1797        self.assertTrue(self.student['studycourse']['110'].validation_date is None)
1798        IWorkflowInfo(self.student).fireTransition('register_courses')
1799        self.browser.open(L110_student_path)
1800        self.browser.getLink("Reject courses").click()
1801        self.assertTrue('Course list has been unregistered'
1802            in self.browser.contents)
1803        self.assertTrue('school fee paid' in self.browser.contents)
1804        # CA does now see the contact form and can send a message.
1805        self.browser.getControl(name="form.subject").value = 'Important subject'
1806        self.browser.getControl(name="form.body").value = 'Course list rejected'
1807        self.browser.getControl("Send message now").click()
1808        self.assertTrue('Your message has been sent' in self.browser.contents)
1809        # The CA does now see the Edit button and can edit
1810        # current study level.
1811        self.browser.open(L110_student_path)
1812        self.browser.getLink("Edit").click()
1813        self.assertTrue('Edit course list of 100 (Year 1) on 1st probation'
1814            in self.browser.contents)
1815        # The CA can't validate courses if not in state
1816        # courses registered.
1817        self.browser.open(L110_student_path + '/validate_courses')
1818        self.assertTrue('Student is in the wrong state'
1819            in self.browser.contents)
1820        # The CA can go to his certificate through the my_roles page ...
1821        self.browser.open('http://localhost/app/users/mrsadvise/my_roles')
1822        self.browser.getLink(
1823            "http://localhost/app/faculties/fac1/dep1/certificates/CERT1").click()
1824        # ... and view the list of students.
1825        self.browser.getLink("Show students").click()
1826        self.browser.getControl(name="session").value = ['2004']
1827        self.browser.getControl(name="level").value = ['100']
1828        self.browser.getControl("Show").click()
1829        self.assertTrue(self.student_id in self.browser.contents)
1830
1831    def test_change_current_mode(self):
1832        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1833        self.browser.open(self.clearance_path)
1834        self.assertFalse('Employer' in self.browser.contents)
1835        self.browser.open(self.manage_clearance_path)
1836        self.assertFalse('Employer' in self.browser.contents)
1837        self.browser.open(self.edit_clearance_path)
1838        self.assertFalse('Employer' in self.browser.contents)
1839        # Now we change the study mode of the certificate and a different
1840        # interface is used by clearance views.
1841        self.certificate.study_mode = 'pg_ft'
1842        # Invariants are not being checked here?!
1843        self.certificate.end_level = 100
1844        self.browser.open(self.clearance_path)
1845        self.assertTrue('Employer' in self.browser.contents)
1846        self.browser.open(self.manage_clearance_path)
1847        self.assertTrue('Employer' in self.browser.contents)
1848        IWorkflowState(self.student).setState('clearance started')
1849        self.browser.open(self.edit_clearance_path)
1850        self.assertTrue('Employer' in self.browser.contents)
1851
1852    def test_find_students_in_faculties(self):
1853        # Create local students manager in faculty
1854        self.app['users'].addUser('mrmanager', SECRET)
1855        self.app['users']['mrmanager'].email = 'mrmanager@foo.ng'
1856        self.app['users']['mrmanager'].title = u'Volk Wagen'
1857        # Assign LocalStudentsManager role for faculty
1858        fac = self.app['faculties']['fac1']
1859        prmlocal = IPrincipalRoleManager(fac)
1860        prmlocal.assignRoleToPrincipal(
1861            'waeup.local.LocalStudentsManager', 'mrmanager')
1862        notify(LocalRoleSetEvent(
1863            fac, 'waeup.local.LocalStudentsManager', 'mrmanager',
1864            granted=True))
1865        # Login as manager
1866        self.browser.open(self.login_path)
1867        self.browser.getControl(name="form.login").value = 'mrmanager'
1868        self.browser.getControl(name="form.password").value = SECRET
1869        self.browser.getControl("Login").click()
1870        self.assertMatches('...You logged in...', self.browser.contents)
1871        # Manager can see his roles
1872        self.browser.getLink("My Roles").click()
1873        self.assertMatches(
1874            '...<span>Students Manager</span>...',
1875            self.browser.contents)
1876        # The manager can go to his faculty
1877        self.browser.getLink(
1878            "http://localhost/app/faculties/fac1").click()
1879        # and find students
1880        self.browser.getLink("Find students").click()
1881        self.browser.getControl("Find student").click()
1882        self.assertTrue('Empty search string' in self.browser.contents)
1883        self.browser.getControl(name="searchtype").value = ['student_id']
1884        self.browser.getControl(name="searchterm").value = self.student_id
1885        self.browser.getControl("Find student").click()
1886        self.assertTrue('Anna Tester' in self.browser.contents)
1887
1888    def test_activate_deactivate_buttons(self):
1889        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1890        self.browser.open(self.student_path)
1891        self.browser.getLink("Deactivate").click()
1892        self.assertTrue(
1893            'Student account has been deactivated.' in self.browser.contents)
1894        self.assertTrue(
1895            'Base Data (account deactivated)' in self.browser.contents)
1896        self.assertTrue(self.student.suspended)
1897        self.browser.getLink("Activate").click()
1898        self.assertTrue(
1899            'Student account has been activated.' in self.browser.contents)
1900        self.assertFalse(
1901            'Base Data (account deactivated)' in self.browser.contents)
1902        self.assertFalse(self.student.suspended)
1903        # History messages have been added ...
1904        self.browser.getLink("History").click()
1905        self.assertTrue(
1906            'Student account deactivated by Manager<br />' in self.browser.contents)
1907        self.assertTrue(
1908            'Student account activated by Manager<br />' in self.browser.contents)
1909        # ... and actions have been logged.
1910        logfile = os.path.join(
1911            self.app['datacenter'].storage, 'logs', 'students.log')
1912        logcontent = open(logfile).read()
1913        self.assertTrue('zope.mgr - students.browser.StudentDeactivateView - '
1914                        'K1000000 - account deactivated' in logcontent)
1915        self.assertTrue('zope.mgr - students.browser.StudentActivateView - '
1916                        'K1000000 - account activated' in logcontent)
1917
1918    def test_manage_student_transfer(self):
1919        # Add second certificate
1920        self.certificate2 = createObject('waeup.Certificate')
1921        self.certificate2.code = u'CERT2'
1922        self.certificate2.study_mode = 'ug_ft'
1923        self.certificate2.start_level = 999
1924        self.certificate2.end_level = 999
1925        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
1926            self.certificate2)
1927
1928        # Add study level to old study course
1929        studylevel = createObject(u'waeup.StudentStudyLevel')
1930        studylevel.level = 200
1931        self.student['studycourse'].addStudentStudyLevel(
1932            self.certificate, studylevel)
1933        studylevel = createObject(u'waeup.StudentStudyLevel')
1934        studylevel.level = 999
1935        self.student['studycourse'].addStudentStudyLevel(
1936            self.certificate, studylevel)
1937
1938        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1939        self.browser.open(self.student_path)
1940        self.browser.getLink("Transfer").click()
1941        self.browser.getControl(name="form.certificate").value = ['CERT2']
1942        self.browser.getControl(name="form.current_session").value = ['2011']
1943        self.browser.getControl(name="form.current_level").value = ['200']
1944        self.browser.getControl("Transfer").click()
1945        self.assertTrue(
1946            'Current level does not match certificate levels'
1947            in self.browser.contents)
1948        self.browser.getControl(name="form.current_level").value = ['999']
1949        self.browser.getControl("Transfer").click()
1950        self.assertTrue('Successfully transferred' in self.browser.contents)
1951        # The catalog has been updated
1952        cat = queryUtility(ICatalog, name='students_catalog')
1953        results = list(
1954            cat.searchResults(
1955            certcode=('CERT2', 'CERT2')))
1956        self.assertTrue(results[0] is self.student)
1957        results = list(
1958            cat.searchResults(
1959            current_session=(2011, 2011)))
1960        self.assertTrue(results[0] is self.student)
1961        # Add study level to new study course
1962        studylevel = createObject(u'waeup.StudentStudyLevel')
1963        studylevel.level = 999
1964        self.student['studycourse'].addStudentStudyLevel(
1965            self.certificate, studylevel)
1966
1967        # Edit and add pages are locked for old study courses
1968        self.browser.open(self.student_path + '/studycourse/manage')
1969        self.assertFalse('The requested form is locked' in self.browser.contents)
1970        self.browser.open(self.student_path + '/studycourse_1/manage')
1971        self.assertTrue('The requested form is locked' in self.browser.contents)
1972
1973        self.browser.open(self.student_path + '/studycourse/start_session')
1974        self.assertFalse('The requested form is locked' in self.browser.contents)
1975        self.browser.open(self.student_path + '/studycourse_1/start_session')
1976        self.assertTrue('The requested form is locked' in self.browser.contents)
1977
1978        IWorkflowState(self.student).setState('school fee paid')
1979        self.browser.open(self.student_path + '/studycourse/add')
1980        self.assertFalse('The requested form is locked' in self.browser.contents)
1981        self.browser.open(self.student_path + '/studycourse_1/add')
1982        self.assertTrue('The requested form is locked' in self.browser.contents)
1983
1984        self.browser.open(self.student_path + '/studycourse/999/manage')
1985        self.assertFalse('The requested form is locked' in self.browser.contents)
1986        self.browser.open(self.student_path + '/studycourse_1/999/manage')
1987        self.assertTrue('The requested form is locked' in self.browser.contents)
1988
1989        self.browser.open(self.student_path + '/studycourse/999/validate_courses')
1990        self.assertFalse('The requested form is locked' in self.browser.contents)
1991        self.browser.open(self.student_path + '/studycourse_1/999/validate_courses')
1992        self.assertTrue('The requested form is locked' in self.browser.contents)
1993
1994        self.browser.open(self.student_path + '/studycourse/999/reject_courses')
1995        self.assertFalse('The requested form is locked' in self.browser.contents)
1996        self.browser.open(self.student_path + '/studycourse_1/999/reject_courses')
1997        self.assertTrue('The requested form is locked' in self.browser.contents)
1998
1999        self.browser.open(self.student_path + '/studycourse/999/add')
2000        self.assertFalse('The requested form is locked' in self.browser.contents)
2001        self.browser.open(self.student_path + '/studycourse_1/999/add')
2002        self.assertTrue('The requested form is locked' in self.browser.contents)
2003
2004        self.browser.open(self.student_path + '/studycourse/999/edit')
2005        self.assertFalse('The requested form is locked' in self.browser.contents)
2006        self.browser.open(self.student_path + '/studycourse_1/999/edit')
2007        self.assertTrue('The requested form is locked' in self.browser.contents)
2008
2009        # Revert transfer
2010        self.browser.open(self.student_path + '/studycourse_1')
2011        self.browser.getLink("Reactivate").click()
2012        self.browser.getControl("Revert now").click()
2013        self.assertTrue('Previous transfer reverted' in self.browser.contents)
2014        results = list(
2015            cat.searchResults(
2016            certcode=('CERT1', 'CERT1')))
2017        self.assertTrue(results[0] is self.student)
2018        self.assertEqual([i for i in self.student.keys()],
2019            [u'accommodation', u'payments', u'studycourse'])
2020
2021    def test_login_as_student(self):
2022        # StudentImpersonators can login as student
2023        # Create clearance officer
2024        self.app['users'].addUser('mrofficer', SECRET)
2025        self.app['users']['mrofficer'].email = 'mrofficer@foo.ng'
2026        self.app['users']['mrofficer'].title = 'Harry Actor'
2027        prmglobal = IPrincipalRoleManager(self.app)
2028        prmglobal.assignRoleToPrincipal('waeup.StudentImpersonator', 'mrofficer')
2029        prmglobal.assignRoleToPrincipal('waeup.StudentsManager', 'mrofficer')
2030        # Login as student impersonator
2031        self.browser.open(self.login_path)
2032        self.browser.getControl(name="form.login").value = 'mrofficer'
2033        self.browser.getControl(name="form.password").value = SECRET
2034        self.browser.getControl("Login").click()
2035        self.assertMatches('...You logged in...', self.browser.contents)
2036        self.browser.open(self.student_path)
2037        self.browser.getLink("Login as").click()
2038        self.browser.getControl("Set password now").click()
2039        temp_password = self.browser.getControl(name='form.password').value
2040        self.browser.getControl("Login now").click()
2041        self.assertMatches(
2042            '...You successfully logged in as...', self.browser.contents)
2043        # We are logged in as student and can see the 'My Data' tab
2044        self.assertMatches(
2045            '...<a href="#" class="dropdown-toggle" data-toggle="dropdown">...',
2046            self.browser.contents)
2047        self.assertMatches(
2048            '...My Data...',
2049            self.browser.contents)
2050        self.browser.getLink("Logout").click()
2051        # The student can't login with the original password ...
2052        self.browser.open(self.login_path)
2053        self.browser.getControl(name="form.login").value = self.student_id
2054        self.browser.getControl(name="form.password").value = 'spwd'
2055        self.browser.getControl("Login").click()
2056        self.assertMatches(
2057            '...Your account has been temporarily deactivated...',
2058            self.browser.contents)
2059        # ... but with the temporary password
2060        self.browser.open(self.login_path)
2061        self.browser.getControl(name="form.login").value = self.student_id
2062        self.browser.getControl(name="form.password").value = temp_password
2063        self.browser.getControl("Login").click()
2064        self.assertMatches('...You logged in...', self.browser.contents)
2065        # Creation of temp_password is properly logged
2066        logfile = os.path.join(
2067            self.app['datacenter'].storage, 'logs', 'students.log')
2068        logcontent = open(logfile).read()
2069        self.assertTrue(
2070            'mrofficer - students.browser.LoginAsStudentStep1 - K1000000 - '
2071            'temp_password generated: %s' % temp_password in logcontent)
2072
2073    def test_transcripts(self):
2074        studylevel = createObject(u'waeup.StudentStudyLevel')
2075        studylevel.level = 100
2076        studylevel.level_session = 2005
2077        self.student['studycourse'].entry_mode = 'ug_ft'
2078        self.student['studycourse'].addStudentStudyLevel(
2079            self.certificate, studylevel)
2080        studylevel2 = createObject(u'waeup.StudentStudyLevel')
2081        studylevel2.level = 110
2082        studylevel2.level_session = 2006
2083        self.student['studycourse'].addStudentStudyLevel(
2084            self.certificate, studylevel2)
2085        # Add second course (COURSE has been added automatically)
2086        courseticket = createObject('waeup.CourseTicket')
2087        courseticket.code = 'ANYCODE'
2088        courseticket.title = u'Any TITLE'
2089        courseticket.credits = 13
2090        courseticket.score = 66
2091        courseticket.semester = 1
2092        courseticket.dcode = u'ANYDCODE'
2093        courseticket.fcode = u'ANYFCODE'
2094        self.student['studycourse']['110']['COURSE2'] = courseticket
2095        self.student['studycourse']['100']['COURSE1'].score = 55
2096        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[0], 3.0)
2097        self.assertEqual(self.student['studycourse']['110'].gpa_params_rectified[0], 4.0)
2098        # Get transcript data
2099        td = self.student['studycourse'].getTranscriptData()
2100        self.assertEqual(td[0][0]['level_key'], '100')
2101        self.assertEqual(td[0][0]['sgpa'], 3.0)
2102        self.assertEqual(td[0][0]['level'].level, 100)
2103        self.assertEqual(td[0][0]['level'].level_session, 2005)
2104        self.assertEqual(td[0][0]['tickets_1'][0].code, 'COURSE1')
2105        self.assertEqual(td[0][1]['level_key'], '110')
2106        self.assertEqual(td[0][1]['sgpa'], 4.0)
2107        self.assertEqual(td[0][1]['level'].level, 110)
2108        self.assertEqual(td[0][1]['level'].level_session, 2006)
2109        self.assertEqual(td[0][1]['tickets_1'][0].code, 'ANYCODE')
2110        self.assertEqual(td[1], 3.5652173913043477)
2111        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
2112        self.browser.open(self.student_path + '/studycourse/transcript')
2113        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2114        self.assertTrue('Transcript' in self.browser.contents)
2115        # Officers can open the pdf transcript
2116        self.browser.open(self.student_path + '/studycourse/transcript.pdf')
2117        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2118        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2119        path = os.path.join(samples_dir(), 'transcript.pdf')
2120        open(path, 'wb').write(self.browser.contents)
2121        print "Sample PDF transcript.pdf written to %s" % path
2122
2123    def test_process_transcript(self):
2124        IWorkflowState(self.student).setState('transcript requested')
2125        notify(grok.ObjectModifiedEvent(self.student))
2126        self.student['studycourse'].transcript_comment = (
2127            u'On 07/08/2013 08:59:54 UTC K1000000 wrote:\n\nComment line 1 \n'
2128            'Comment line2\n\nDispatch Address:\nAddress line 1 \n'
2129            'Address line2\n\n')
2130        # Create officer with both roles
2131        self.app['users'].addUser('mrtranscript', SECRET)
2132        self.app['users']['mrtranscript'].email = 'mrtranscript@foo.ng'
2133        self.app['users']['mrtranscript'].title = 'Ruth Gordon'
2134        prmglobal = IPrincipalRoleManager(self.app)
2135        prmglobal.assignRoleToPrincipal('waeup.TranscriptOfficer', 'mrtranscript')
2136        prmglobal.assignRoleToPrincipal('waeup.StudentsManager', 'mrtranscript')
2137        prmglobal.assignRoleToPrincipal('waeup.TranscriptSignee', 'mrtranscript')
2138        # Login
2139        self.browser.open(self.login_path)
2140        self.browser.getControl(name="form.login").value = 'mrtranscript'
2141        self.browser.getControl(name="form.password").value = SECRET
2142        self.browser.getControl("Login").click()
2143        self.assertMatches('...You logged in...', self.browser.contents)
2144        # Officer can see his roles
2145        self.browser.getLink("My Roles").click()
2146        self.assertMatches(
2147            '...<div>Transcript Officer</div>...',
2148            self.browser.contents)
2149        # Officer can search for students in state 'transcript requested'
2150        self.browser.open(self.container_path)
2151        self.browser.getControl(name="searchtype").value = [
2152            'transcript requested']
2153        self.browser.getControl("Find student(s)").click()
2154        self.assertTrue('Anna Tester' in self.browser.contents)
2155        self.browser.getLink("K1000000").click()
2156        self.assertFalse('Release transcript request' in self.browser.contents)
2157        # Officers can still edit studycourse, studylevel and course tickets.
2158        self.browser.open(self.studycourse_path + '/manage')
2159        self.assertTrue('Undergraduate Full-Time</option>'
2160            in self.browser.contents)
2161        self.browser.getControl(name="form.certificate").value = ['CERT1']
2162        self.browser.getControl(name="form.current_session").value = ['2004']
2163        self.browser.getControl(name="form.current_verdict").value = ['A']
2164        self.browser.getControl(name="form.entry_mode").value = ['ug_ft']
2165        self.browser.getControl("Save").click()
2166        self.browser.getControl(name="form.current_level").value = ['100']
2167        self.browser.getControl("Save").click()
2168        self.browser.getControl(name="addlevel").value = ['100']
2169        self.browser.getControl(name="level_session").value = ['2004']
2170        self.browser.getControl("Add study level").click()
2171        self.browser.getLink("100").click()
2172        self.browser.getLink("Manage").click()
2173        self.browser.getControl(name="form.level_session").value = ['2002']
2174        self.browser.getControl("Save").click()
2175        self.browser.getLink("COURSE1").click()
2176        self.browser.getLink("Manage").click()
2177        self.browser.getControl("Save").click()
2178        self.assertTrue('Form has been saved' in self.browser.contents)
2179        # Officer can edit transcript remarks and validate the transcript
2180        self.browser.open(self.studycourse_path + '/transcript')
2181        self.browser.getLink("Validate transcript").click()
2182        self.browser.getLink("Edit").click()
2183        self.assertEqual(
2184            self.browser.url, self.studycourse_path + '/100/remark')
2185        self.browser.getControl(
2186            name="form.transcript_remark").value = 'Oh, the student failed'
2187        self.browser.getControl(
2188            "Save remark and go and back to transcript validation page").click()
2189        self.assertEqual(
2190            self.browser.url,
2191            self.studycourse_path + '/validate_transcript#tab4')
2192        self.assertEqual(self.student['studycourse']['100'].transcript_remark,
2193            'Oh, the student failed')
2194        self.browser.getControl("Save comment and validate transcript").click()
2195        # After validation all manage forms are locked.
2196        self.browser.open(self.studycourse_path + '/manage')
2197        self.assertTrue('The requested form is locked' in self.browser.contents)
2198        self.assertFalse('Undergraduate Full-Time</option>'
2199            in self.browser.contents)
2200        self.browser.open(self.studycourse_path + '/100/manage')
2201        self.assertTrue('The requested form is locked' in self.browser.contents)
2202        self.browser.open(self.studycourse_path + '/100/COURSE1/manage')
2203        self.assertTrue('The requested form is locked' in self.browser.contents)
2204        self.browser.open(self.studycourse_path + '/100/remark')
2205        self.assertTrue('The requested form is locked' in self.browser.contents)
2206
2207        # Transcript can be signed if officer has the permission to sign
2208        #self.browser.open(self.studycourse_path + '/transcript')
2209        #self.assertFalse('Sign transcript' in self.browser.contents)
2210        #prmglobal = IPrincipalRoleManager(self.app)
2211        #prmglobal.assignRoleToPrincipal('waeup.TranscriptSignee', 'mrtranscript')
2212
2213        self.browser.open(self.studycourse_path + '/transcript')
2214        self.browser.getLink("Sign transcript electronically").click()
2215        # Transcript signing has been logged ...
2216        logfile = os.path.join(
2217            self.app['datacenter'].storage, 'logs', 'students.log')
2218        logcontent = open(logfile).read()
2219        self.assertTrue(
2220            'mrtranscript - students.browser.StudentTranscriptSignView - '
2221            'K1000000 - Transcript signed' in logcontent)
2222        # ... appears in the student's history ...
2223        self.browser.open(self.history_path)
2224        self.assertTrue('Transcript signed by Ruth Gordon'
2225            in self.browser.contents)
2226        # ... and is also stored in the transcript_signee attribute.
2227        self.assertTrue(
2228            u'Electronically signed by Ruth Gordon (mrtranscript) on '
2229            in self.student['studycourse'].transcript_signees)
2230        # Officer can release the transcript
2231        self.browser.open(self.studycourse_path + '/transcript')
2232        self.browser.getLink("Release transcript").click()
2233        self.assertTrue(' UTC K1000000 wrote:<br><br>Comment line 1 <br>'
2234        'Comment line2<br><br>Dispatch Address:<br>Address line 1 <br>'
2235        'Address line2<br><br></p>' in self.browser.contents)
2236        self.browser.getControl(name="comment").value = (
2237            'Hello,\nYour transcript has been sent to the address provided.')
2238        self.browser.getControl("Save comment and release transcript").click()
2239        self.assertTrue(
2240            'UTC mrtranscript wrote:\n\nHello,\nYour transcript has '
2241            'been sent to the address provided.\n\n'
2242            in self.student['studycourse'].transcript_comment)
2243        # The comment has been logged
2244        logfile = os.path.join(
2245            self.app['datacenter'].storage, 'logs', 'students.log')
2246        logcontent = open(logfile).read()
2247        self.assertTrue(
2248            'mrtranscript - students.browser.StudentTranscriptReleaseFormPage - '
2249            'K1000000 - comment: Hello,<br>'
2250            'Your transcript has been sent to the address provided'
2251            in logcontent)
2252        # File has been stored in the file system
2253        # Check if transcript exists in the file system and is a PDF file
2254        storage = getUtility(IExtFileStore)
2255        file_id = IFileStoreNameChooser(
2256            self.student).chooseName(attr='final_transcript.pdf')
2257        pdf = storage.getFile(file_id).read()
2258        self.assertTrue(len(pdf) > 0)
2259        self.assertEqual(pdf[:8], '%PDF-1.4')
2260        # Copy the file to samples_dir
2261        path = os.path.join(samples_dir(), 'final_transcript.pdf')
2262        open(path, 'wb').write(pdf)
2263        print "Sample PDF final_transcript.pdf written to %s" % path
2264        # Check if there is an transcript pdf link in UI
2265        self.browser.open(self.student_path)
2266        self.assertTrue('Final Transcript' in self.browser.contents)
2267        self.browser.getLink("Final Transcript").click()
2268        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2269        self.assertEqual(self.browser.headers['Content-Type'],
2270                         'application/pdf')
2271        # Transcript views are no longer accessible
2272        self.browser.open(self.studycourse_path)
2273        self.assertFalse('studycourse/transcript' in self.browser.contents)
2274        self.browser.open(self.studycourse_path + '/transcript')
2275        self.assertTrue('Forbidden!' in self.browser.contents)
2276        self.browser.open(self.studycourse_path + '/transcript.pdf')
2277        self.assertTrue('Forbidden!' in self.browser.contents)
2278        # If we reset the transcript process
2279        # (can't be done by transcript officer), the file will be deleted
2280        IWorkflowInfo(self.student).fireTransition('reset11')
2281        self.browser.open(self.student_path)
2282        self.assertFalse('Final Transcript' in self.browser.contents)
2283        # ... and transcript process information has been removed
2284        self.assertEqual(self.student['studycourse'].transcript_comment, None)
2285        self.assertEqual(self.student['studycourse'].transcript_signees, None)
2286
2287    def test_landingpage_transcript_officer(self):
2288        IWorkflowState(self.student).setState('transcript requested')
2289        notify(grok.ObjectModifiedEvent(self.student))
2290        # Create transcript officer
2291        self.app['users'].addUser('mrtranscript', SECRET)
2292        self.app['users']['mrtranscript'].email = 'mrtranscript@foo.ng'
2293        self.app['users']['mrtranscript'].title = 'Ruth Gordon'
2294        # We assign transcript officer role at faculty level
2295        fac = self.app['faculties']['fac1']
2296        prmlocal = IPrincipalRoleManager(fac)
2297        prmlocal.assignRoleToPrincipal(
2298            'waeup.local.TranscriptOfficer', 'mrtranscript')
2299        notify(LocalRoleSetEvent(
2300            fac, 'waeup.local.TranscriptOfficer', 'mrtranscript', granted=True))
2301        # Login as transcript officer
2302        self.browser.open(self.login_path)
2303        self.browser.getControl(name="form.login").value = 'mrtranscript'
2304        self.browser.getControl(name="form.password").value = SECRET
2305        self.browser.getControl("Login").click()
2306        self.assertMatches('...You logged in...', self.browser.contents)
2307        # Officer is on landing page and does see the transcript link
2308        self.assertTrue(
2309            'http://localhost/app/students/K1000000/studycourse/transcript'
2310            in self.browser.contents)
2311        self.browser.getLink("K1000000").click()
2312        self.assertTrue(
2313            'Anna Tester: Transcript Data' in self.browser.contents)
2314        # Officer is on transcript page and can validate the transcript
2315        self.browser.getLink("Validate transcript").click()
2316        self.browser.getControl("Save comment and validate transcript").click()
2317        self.assertTrue(
2318            '<div class="alert alert-success">Transcript validated.</div>'
2319            in self.browser.contents)
2320        # Officer is still on transcript page and can release the transcript
2321        self.browser.getLink("Release transcript").click()
2322        self.browser.getControl("Save comment and release transcript").click()
2323        self.assertTrue(
2324            '<div class="alert alert-success">'
2325            'Transcript released and final transcript file saved.</div>'
2326            in self.browser.contents)
2327
2328    def test_landingpage_transcript_signee(self):
2329        IWorkflowState(self.student).setState('transcript validated')
2330        notify(grok.ObjectModifiedEvent(self.student))
2331        # Create transcript signee
2332        self.app['users'].addUser('mrtranscript', SECRET)
2333        self.app['users']['mrtranscript'].email = 'mrtranscript@foo.ng'
2334        self.app['users']['mrtranscript'].title = 'Ruth Gordon'
2335        # We assign transcript officer role at faculty level
2336        fac = self.app['faculties']['fac1']
2337        prmlocal = IPrincipalRoleManager(fac)
2338        prmlocal.assignRoleToPrincipal(
2339            'waeup.local.TranscriptSignee', 'mrtranscript')
2340        notify(LocalRoleSetEvent(
2341            fac, 'waeup.local.TranscriptSignee', 'mrtranscript', granted=True))
2342        # Login as transcript officer
2343        self.browser.open(self.login_path)
2344        self.browser.getControl(name="form.login").value = 'mrtranscript'
2345        self.browser.getControl(name="form.password").value = SECRET
2346        self.browser.getControl("Login").click()
2347        self.assertMatches('...You logged in...', self.browser.contents)
2348        # Officer is on landing page and does see the transcript link
2349        self.assertTrue(
2350            'http://localhost/app/students/K1000000/studycourse/transcript'
2351            in self.browser.contents)
2352        self.browser.getLink("K1000000").click()
2353        self.assertTrue(
2354            'Anna Tester: Transcript Data' in self.browser.contents)
2355        # Officer is on transcript page and can sign the transcript
2356        self.browser.getLink("Sign transcript").click()
2357        self.assertTrue(
2358            '<div class="alert alert-success">Transcript signed.</div>'
2359            in self.browser.contents)
2360        # Officer is still on transcript page
2361        self.assertTrue(
2362            'Anna Tester: Transcript Data' in self.browser.contents)
2363        # Officer can sign the transcript only once
2364        self.browser.getLink("Sign transcript").click()
2365        self.assertTrue(
2366            '<div class="alert alert-warning">'
2367            'You have already signed this transcript.</div>'
2368            in self.browser.contents)
2369        # Signature can be seen on transcript page
2370        self.assertTrue(
2371            'Electronically signed by Ruth Gordon (mrtranscript) on'
2372            in self.browser.contents)
2373
2374
2375class StudentUITests(StudentsFullSetup):
2376    # Tests for Student class views and pages
2377
2378    def test_student_change_password(self):
2379        # Students can change the password
2380        self.student.personal_updated = datetime.utcnow()
2381        self.browser.open(self.login_path)
2382        self.browser.getControl(name="form.login").value = self.student_id
2383        self.browser.getControl(name="form.password").value = 'spwd'
2384        self.browser.getControl("Login").click()
2385        self.assertEqual(self.browser.url, self.student_path)
2386        self.assertTrue('You logged in' in self.browser.contents)
2387        # Change password
2388        self.browser.getLink("Change password").click()
2389        self.browser.getControl(name="change_password").value = 'pw'
2390        self.browser.getControl(
2391            name="change_password_repeat").value = 'pw'
2392        self.browser.getControl("Save").click()
2393        self.assertTrue('Password must have at least' in self.browser.contents)
2394        self.browser.getControl(name="change_password").value = 'new_password'
2395        self.browser.getControl(
2396            name="change_password_repeat").value = 'new_passssword'
2397        self.browser.getControl("Save").click()
2398        self.assertTrue('Passwords do not match' in self.browser.contents)
2399        self.browser.getControl(name="change_password").value = 'new_password'
2400        self.browser.getControl(
2401            name="change_password_repeat").value = 'new_password'
2402        self.browser.getControl("Save").click()
2403        self.assertTrue('Password changed' in self.browser.contents)
2404        # We are still logged in. Changing the password hasn't thrown us out.
2405        self.browser.getLink("Base Data").click()
2406        self.assertEqual(self.browser.url, self.student_path)
2407        # We can logout
2408        self.browser.getLink("Logout").click()
2409        self.assertTrue('You have been logged out' in self.browser.contents)
2410        self.assertEqual(self.browser.url, 'http://localhost/app/index')
2411        # We can login again with the new password
2412        self.browser.getLink("Login").click()
2413        self.browser.open(self.login_path)
2414        self.browser.getControl(name="form.login").value = self.student_id
2415        self.browser.getControl(name="form.password").value = 'new_password'
2416        self.browser.getControl("Login").click()
2417        self.assertEqual(self.browser.url, self.student_path)
2418        self.assertTrue('You logged in' in self.browser.contents)
2419        return
2420
2421    def test_forbidden_name(self):
2422        self.student.lastname = u'<TAG>Tester</TAG>'
2423        self.browser.open(self.login_path)
2424        self.browser.getControl(name="form.login").value = self.student_id
2425        self.browser.getControl(name="form.password").value = 'spwd'
2426        self.browser.getControl("Login").click()
2427        self.assertTrue('XXX: Base Data' in self.browser.contents)
2428        self.assertTrue('&lt;TAG&gt;Tester&lt;/TAG&gt;' in self.browser.contents)
2429        self.assertFalse('<TAG>Tester</TAG>' in self.browser.contents)
2430        return
2431
2432    def test_setpassword(self):
2433        # Set password for first-time access
2434        student = Student()
2435        student.reg_number = u'123456'
2436        student.firstname = u'Klaus'
2437        student.lastname = u'Tester'
2438        self.app['students'].addStudent(student)
2439        setpassword_path = 'http://localhost/app/setpassword'
2440        student_path = 'http://localhost/app/students/%s' % student.student_id
2441        self.browser.open(setpassword_path)
2442        self.browser.getControl(name="ac_series").value = self.existing_pwdseries
2443        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
2444        self.browser.getControl(name="reg_number").value = '223456'
2445        self.browser.getControl("Set").click()
2446        self.assertMatches('...No student found...',
2447                           self.browser.contents)
2448        self.browser.getControl(name="reg_number").value = '123456'
2449        self.browser.getControl(name="ac_number").value = '999999'
2450        self.browser.getControl("Set").click()
2451        self.assertMatches('...Access code is invalid...',
2452                           self.browser.contents)
2453        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
2454        self.browser.getControl("Set").click()
2455        self.assertMatches('...Password has been set. Your Student Id is...',
2456                           self.browser.contents)
2457        self.browser.getControl("Set").click()
2458        self.assertMatches(
2459            '...Password has already been set. Your Student Id is...',
2460            self.browser.contents)
2461        existing_pwdpin = self.pwdpins[1]
2462        parts = existing_pwdpin.split('-')[1:]
2463        existing_pwdseries, existing_pwdnumber = parts
2464        self.browser.getControl(name="ac_series").value = existing_pwdseries
2465        self.browser.getControl(name="ac_number").value = existing_pwdnumber
2466        self.browser.getControl(name="reg_number").value = '123456'
2467        self.browser.getControl("Set").click()
2468        self.assertMatches(
2469            '...You are using the wrong Access Code...',
2470            self.browser.contents)
2471        # The student can login with the new credentials
2472        self.browser.open(self.login_path)
2473        self.browser.getControl(name="form.login").value = student.student_id
2474        self.browser.getControl(
2475            name="form.password").value = self.existing_pwdnumber
2476        self.browser.getControl("Login").click()
2477        self.assertEqual(self.browser.url, student_path)
2478        self.assertTrue('You logged in' in self.browser.contents)
2479        return
2480
2481    def test_student_login(self):
2482        # Student cant login if their password is not set
2483        self.student.password = None
2484        self.browser.open(self.login_path)
2485        self.browser.getControl(name="form.login").value = self.student_id
2486        self.browser.getControl(name="form.password").value = 'spwd'
2487        self.browser.getControl("Login").click()
2488        self.assertTrue(
2489            'You entered invalid credentials.' in self.browser.contents)
2490        # We set the password again
2491        IUserAccount(
2492            self.app['students'][self.student_id]).setPassword('spwd')
2493        # Students can't login if their account is suspended/deactivated
2494        self.student.suspended = True
2495        self.browser.open(self.login_path)
2496        self.browser.getControl(name="form.login").value = self.student_id
2497        self.browser.getControl(name="form.password").value = 'spwd'
2498        self.browser.getControl("Login").click()
2499        self.assertMatches(
2500            '...<div class="alert alert-warning">'
2501            'Your account has been deactivated.</div>...', self.browser.contents)
2502        # If suspended_comment is set this message will be flashed instead
2503        self.student.suspended_comment = u'Aetsch baetsch!'
2504        self.browser.getControl(name="form.login").value = self.student_id
2505        self.browser.getControl(name="form.password").value = 'spwd'
2506        self.browser.getControl("Login").click()
2507        self.assertMatches(
2508            '...<div class="alert alert-warning">Aetsch baetsch!</div>...',
2509            self.browser.contents)
2510        self.student.suspended = False
2511        # Students can't login if a temporary password has been set and
2512        # is not expired
2513        self.app['students'][self.student_id].setTempPassword(
2514            'anybody', 'temp_spwd')
2515        self.browser.open(self.login_path)
2516        self.browser.getControl(name="form.login").value = self.student_id
2517        self.browser.getControl(name="form.password").value = 'spwd'
2518        self.browser.getControl("Login").click()
2519        self.assertMatches(
2520            '...Your account has been temporarily deactivated...',
2521            self.browser.contents)
2522        # The student can login with the temporary password
2523        self.browser.open(self.login_path)
2524        self.browser.getControl(name="form.login").value = self.student_id
2525        self.browser.getControl(name="form.password").value = 'temp_spwd'
2526        self.browser.getControl("Login").click()
2527        self.assertMatches(
2528            '...You logged in...', self.browser.contents)
2529        # Student can view the base data
2530        self.browser.open(self.student_path)
2531        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2532        self.assertEqual(self.browser.url, self.student_path)
2533        # When the password expires ...
2534        delta = timedelta(minutes=11)
2535        self.app['students'][self.student_id].temp_password[
2536            'timestamp'] = datetime.utcnow() - delta
2537        self.app['students'][self.student_id]._p_changed = True
2538        # ... the student will be automatically logged out
2539        self.assertRaises(
2540            Unauthorized, self.browser.open, self.student_path)
2541        # Then the student can login with the original password
2542        self.browser.open(self.login_path)
2543        self.browser.getControl(name="form.login").value = self.student_id
2544        self.browser.getControl(name="form.password").value = 'spwd'
2545        self.browser.getControl("Login").click()
2546        self.assertMatches(
2547            '...You logged in...', self.browser.contents)
2548
2549    def test_maintenance_mode(self):
2550        config = grok.getSite()['configuration']
2551        self.browser.open(self.login_path)
2552        self.browser.getControl(name="form.login").value = self.student_id
2553        self.browser.getControl(name="form.password").value = 'spwd'
2554        self.browser.getControl("Login").click()
2555        # Student logged in.
2556        self.assertTrue('You logged in' in self.browser.contents)
2557        self.assertTrue("Anna Tester" in self.browser.contents)
2558        # If maintenance mode is enabled, student is immediately logged out.
2559        config.maintmode_enabled_by = u'any_user'
2560        self.assertRaises(
2561            Unauthorized, self.browser.open, 'http://localhost/app/faculties')
2562        self.browser.open('http://localhost/app/login')
2563        self.assertTrue('The portal is in maintenance mode' in self.browser.contents)
2564        # Student really can't login if maintenance mode is enabled.
2565        self.browser.open(self.login_path)
2566        self.browser.getControl(name="form.login").value = self.student_id
2567        self.browser.getControl(name="form.password").value = 'spwd'
2568        self.browser.getControl("Login").click()
2569        # A second warning is raised.
2570        self.assertTrue(
2571            'The portal is in maintenance mode. You can\'t login!'
2572            in self.browser.contents)
2573        return
2574
2575    def test_student_clearance(self):
2576        # Student cant login if their password is not set
2577        IWorkflowInfo(self.student).fireTransition('admit')
2578        self.browser.open(self.login_path)
2579        self.browser.getControl(name="form.login").value = self.student_id
2580        self.browser.getControl(name="form.password").value = 'spwd'
2581        self.browser.getControl("Login").click()
2582        self.assertMatches(
2583            '...You logged in...', self.browser.contents)
2584        # Admitted student can upload a passport picture
2585        self.browser.open(self.student_path + '/change_portrait')
2586        ctrl = self.browser.getControl(name='passportuploadedit')
2587        file_obj = open(SAMPLE_IMAGE, 'rb')
2588        file_ctrl = ctrl.mech_control
2589        file_ctrl.add_file(file_obj, filename='my_photo.jpg')
2590        self.browser.getControl(
2591            name='upload_passportuploadedit').click()
2592        self.assertTrue(
2593            'src="http://localhost/app/students/K1000000/passport.jpg"'
2594            in self.browser.contents)
2595        # Students can open admission letter
2596        self.browser.getLink("Base Data").click()
2597        self.browser.getLink("Download admission letter").click()
2598        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2599        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2600        # Student can view the clearance data
2601        self.browser.open(self.student_path)
2602        self.browser.getLink("Clearance Data").click()
2603        # Student can't open clearance edit form before starting clearance
2604        self.browser.open(self.student_path + '/cedit')
2605        self.assertMatches('...The requested form is locked...',
2606                           self.browser.contents)
2607        self.browser.getLink("Clearance Data").click()
2608        self.browser.getLink("Start clearance").click()
2609        self.student.phone = None
2610        # Uups, we forgot to fill the phone fields
2611        self.browser.getControl("Start clearance").click()
2612        self.assertMatches('...Phone number is missing...',
2613                           self.browser.contents)
2614        self.browser.open(self.student_path + '/edit_base')
2615        self.browser.getControl(name="form.phone.ext").value = '12345'
2616        self.browser.getControl("Save").click()
2617        self.browser.open(self.student_path + '/start_clearance')
2618        self.browser.getControl(name="ac_series").value = '3'
2619        self.browser.getControl(name="ac_number").value = '4444444'
2620        self.browser.getControl("Start clearance now").click()
2621        self.assertMatches('...Activation code is invalid...',
2622                           self.browser.contents)
2623        self.browser.getControl(name="ac_series").value = self.existing_clrseries
2624        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
2625        # Owner is Hans Wurst, AC can't be invalidated
2626        self.browser.getControl("Start clearance now").click()
2627        self.assertMatches('...You are not the owner of this access code...',
2628                           self.browser.contents)
2629        # Set the correct owner
2630        self.existing_clrac.owner = self.student_id
2631        # clr_code might be set (and thus returns None) due importing
2632        # an empty clr_code column.
2633        self.student.clr_code = None
2634        self.browser.getControl("Start clearance now").click()
2635        self.assertMatches('...Clearance process has been started...',
2636                           self.browser.contents)
2637        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
2638        self.browser.getControl("Save", index=0).click()
2639        # Student can view the clearance data
2640        self.browser.getLink("Clearance Data").click()
2641        # and go back to the edit form
2642        self.browser.getLink("Edit").click()
2643        # Students can upload documents
2644        ctrl = self.browser.getControl(name='birthcertificateupload')
2645        file_obj = open(SAMPLE_IMAGE, 'rb')
2646        file_ctrl = ctrl.mech_control
2647        file_ctrl.add_file(file_obj, filename='my_birth_certificate.jpg')
2648        self.browser.getControl(
2649            name='upload_birthcertificateupload').click()
2650        self.assertTrue(
2651            'href="http://localhost/app/students/K1000000/birth_certificate"'
2652            in self.browser.contents)
2653        # Students can open clearance slip
2654        self.browser.getLink("View").click()
2655        self.browser.getLink("Download clearance slip").click()
2656        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2657        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2658        # Students can request clearance
2659        self.browser.open(self.edit_clearance_path)
2660        self.browser.getControl("Save and request clearance").click()
2661        self.browser.getControl(name="ac_series").value = self.existing_clrseries
2662        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
2663        self.browser.getControl("Request clearance now").click()
2664        self.assertMatches('...Clearance has been requested...',
2665                           self.browser.contents)
2666        # Student can't reopen clearance form after requesting clearance
2667        self.browser.open(self.student_path + '/cedit')
2668        self.assertMatches('...The requested form is locked...',
2669                           self.browser.contents)
2670
2671    def test_student_course_registration(self):
2672        # Student cant login if their password is not set
2673        IWorkflowInfo(self.student).fireTransition('admit')
2674        self.browser.open(self.login_path)
2675        self.browser.getControl(name="form.login").value = self.student_id
2676        self.browser.getControl(name="form.password").value = 'spwd'
2677        self.browser.getControl("Login").click()
2678        # Student can't add study level if not in state 'school fee paid'
2679        self.browser.open(self.student_path + '/studycourse/add')
2680        self.assertMatches('...The requested form is locked...',
2681                           self.browser.contents)
2682        # ... and must be transferred first
2683        IWorkflowState(self.student).setState('school fee paid')
2684        # Now students can add the current study level
2685        self.browser.getLink("Study Course").click()
2686        self.student['studycourse'].current_level = None
2687        self.browser.getLink("Add course list").click()
2688        self.assertMatches('...Your data are incomplete...',
2689                           self.browser.contents)
2690        self.student['studycourse'].current_level = 100
2691        self.browser.getLink("Add course list").click()
2692        self.assertMatches('...Add current level 100 (Year 1)...',
2693                           self.browser.contents)
2694        self.browser.getControl("Create course list now").click()
2695        # A level with one course ticket was created
2696        self.assertEqual(self.student['studycourse']['100'].number_of_tickets, 1)
2697        self.browser.getLink("100").click()
2698        self.browser.getLink("Edit course list").click()
2699        self.browser.getLink("here").click()
2700        self.browser.getControl(name="form.course").value = ['COURSE1']
2701        self.browser.getControl("Add course ticket").click()
2702        self.assertMatches('...The ticket exists...',
2703                           self.browser.contents)
2704        self.student['studycourse'].current_level = 200
2705        self.browser.getLink("Study Course").click()
2706        self.browser.getLink("Add course list").click()
2707        self.assertMatches('...Add current level 200 (Year 2)...',
2708                           self.browser.contents)
2709        self.browser.getControl("Create course list now").click()
2710        self.browser.getLink("200").click()
2711        self.browser.getLink("Edit course list").click()
2712        self.browser.getLink("here").click()
2713        self.browser.getControl(name="form.course").value = ['COURSE1']
2714        self.course.credits = 100
2715        self.browser.getControl("Add course ticket").click()
2716        self.assertMatches(
2717            '...Maximum credits exceeded...', self.browser.contents)
2718        self.course.credits = 10
2719        self.browser.getControl("Add course ticket").click()
2720        self.assertMatches('...The ticket exists...',
2721                           self.browser.contents)
2722        # Indeed the ticket exists as carry-over course from level 100
2723        # since its score was 0
2724        self.assertTrue(
2725            self.student['studycourse']['200']['COURSE1'].carry_over is True)
2726        self.assertTrue(
2727            self.student['studycourse']['200']['COURSE1'].course_category is None)
2728        # Students can open the pdf course registration slip
2729        self.browser.open(self.student_path + '/studycourse/200')
2730        self.browser.getLink("Download course registration slip").click()
2731        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2732        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2733        # Students can remove course tickets
2734        self.browser.open(self.student_path + '/studycourse/200/edit')
2735        self.browser.getControl("Remove selected", index=0).click()
2736        self.assertTrue('No ticket selected' in self.browser.contents)
2737        # No ticket can be selected since the carry-over course is a core course
2738        self.assertRaises(
2739            LookupError, self.browser.getControl, name='val_id')
2740        self.student['studycourse']['200']['COURSE1'].mandatory = False
2741        self.browser.open(self.student_path + '/studycourse/200/edit')
2742        # Course list can't be registered if total_credits exceeds max_credits
2743        self.student['studycourse']['200']['COURSE1'].credits = 60
2744        self.browser.getControl("Register course list").click()
2745        self.assertTrue('Maximum credits exceeded' in self.browser.contents)
2746        # Student can now remove the ticket
2747        ctrl = self.browser.getControl(name='val_id')
2748        ctrl.getControl(value='COURSE1').selected = True
2749        self.browser.getControl("Remove selected", index=0).click()
2750        self.assertTrue('Successfully removed' in self.browser.contents)
2751        # Removing course tickets is properly logged
2752        logfile = os.path.join(
2753            self.app['datacenter'].storage, 'logs', 'students.log')
2754        logcontent = open(logfile).read()
2755        self.assertTrue('K1000000 - students.browser.StudyLevelEditFormPage '
2756        '- K1000000 - removed: COURSE1 at 200' in logcontent)
2757        # They can add the same ticket using the edit page directly.
2758        # We can do the same by adding the course on the manage page directly
2759        self.browser.getControl(name="course").value = 'COURSE1'
2760        self.browser.getControl("Add course ticket").click()
2761        # Adding course tickets is logged
2762        logfile = os.path.join(
2763            self.app['datacenter'].storage, 'logs', 'students.log')
2764        logcontent = open(logfile).read()
2765        self.assertTrue('K1000000 - students.browser.StudyLevelEditFormPage - '
2766            'K1000000 - added: COURSE1|200|2004' in logcontent)
2767        # Course list can be registered
2768        self.browser.getControl("Register course list").click()
2769        self.assertTrue('Course list has been registered' in self.browser.contents)
2770        self.assertEqual(self.student.state, 'courses registered')
2771        # Course list can be unregistered
2772        self.browser.getLink("Unregister courses").click()
2773        self.assertEqual(self.student.state, 'school fee paid')
2774        self.assertTrue('Course list has been unregistered' in self.browser.contents)
2775        self.browser.open(self.student_path + '/studycourse/200/unregister_courses')
2776        self.assertTrue('You are in the wrong state' in self.browser.contents)
2777        # Students can view the transcript
2778        #self.browser.open(self.studycourse_path)
2779        #self.browser.getLink("Transcript").click()
2780        #self.browser.getLink("Academic Transcript").click()
2781        #self.assertEqual(self.browser.headers['Status'], '200 Ok')
2782        #self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2783        return
2784
2785    def test_student_ticket_update(self):
2786        IWorkflowState(self.student).setState('school fee paid')
2787        self.student['studycourse'].current_level = 100
2788        self.browser.open(self.login_path)
2789        self.browser.getControl(name="form.login").value = self.student_id
2790        self.browser.getControl(name="form.password").value = 'spwd'
2791        self.browser.getControl("Login").click()
2792        # Now students can add the current study level
2793        self.browser.getLink("Study Course").click()
2794        self.browser.getLink("Add course list").click()
2795        self.assertMatches('...Add current level 100 (Year 1)...',
2796                           self.browser.contents)
2797        self.browser.getControl("Create course list now").click()
2798        # A level with one course ticket was created
2799        self.assertEqual(
2800            self.student['studycourse']['100'].number_of_tickets, 1)
2801        self.browser.getLink("100").click()
2802        self.assertTrue('<td>Unnamed Course</td>' in self.browser.contents)
2803        self.browser.getLink("Edit course list").click()
2804        self.browser.getControl("Update all tickets").click()
2805        self.assertTrue('All course tickets updated.' in self.browser.contents)
2806        # ... nothing has changed
2807        self.assertTrue('<td>Unnamed Course</td>' in self.browser.contents)
2808        # We change the title of the course
2809        self.course.title = u'New Title'
2810        self.browser.getControl("Update all tickets").click()
2811        self.assertTrue('<td>New Title</td>' in self.browser.contents)
2812        # We remove the course
2813        del self.app['faculties']['fac1']['dep1'].courses['COURSE1']
2814        self.browser.getControl("Update all tickets").click()
2815        self.assertTrue(' <td>New Title (course cancelled)</td>'
2816            in self.browser.contents)
2817        # Course ticket invalidation has been logged
2818        logfile = os.path.join(
2819            self.app['datacenter'].storage, 'logs', 'students.log')
2820        logcontent = open(logfile).read()
2821        self.assertTrue(
2822            'K1000000 - students.browser.StudyLevelEditFormPage - '
2823            'K1000000 - course tickets invalidated: COURSE1'
2824            in logcontent)
2825        return
2826
2827    def test_student_course_registration_outstanding(self):
2828        self.course = createObject('waeup.Course')
2829        self.course.code = 'COURSE2'
2830        self.course.semester = 1
2831        self.course.credits = 45
2832        self.course.passmark = 40
2833        self.app['faculties']['fac1']['dep1'].courses.addCourse(
2834            self.course)
2835        IWorkflowState(self.student).setState('school fee paid')
2836        self.browser.open(self.login_path)
2837        self.browser.getControl(name="form.login").value = self.student_id
2838        self.browser.getControl(name="form.password").value = 'spwd'
2839        self.browser.getControl("Login").click()
2840        self.browser.open(self.student_path + '/studycourse/add')
2841        self.browser.getControl("Create course list now").click()
2842        self.assertEqual(self.student['studycourse']['100'].number_of_tickets, 1)
2843        self.student['studycourse'].current_level = 200
2844        self.browser.getLink("Study Course").click()
2845        self.browser.getLink("Add course list").click()
2846        self.assertMatches('...Add current level 200 (Year 2)...',
2847                           self.browser.contents)
2848        self.browser.getControl("Create course list now").click()
2849        self.browser.getLink("200").click()
2850        self.browser.getLink("Edit course list").click()
2851        self.browser.getLink("here").click()
2852        self.browser.getControl(name="form.course").value = ['COURSE2']
2853        self.browser.getControl("Add course ticket").click()
2854        # Carryover COURSE1 in level 200 already has 10 credits
2855        self.assertMatches(
2856            '...Maximum credits exceeded...', self.browser.contents)
2857        # If COURSE1 is outstanding, its credits won't be considered
2858        self.student['studycourse']['200']['COURSE1'].outstanding = True
2859        self.browser.getControl("Add course ticket").click()
2860        self.assertMatches(
2861            '...Successfully added COURSE2...', self.browser.contents)
2862        return
2863
2864    def test_postgraduate_student_access(self):
2865        self.certificate.study_mode = 'pg_ft'
2866        self.certificate.start_level = 999
2867        self.certificate.end_level = 999
2868        self.student['studycourse'].current_level = 999
2869        IWorkflowState(self.student).setState('school fee paid')
2870        self.browser.open(self.login_path)
2871        self.browser.getControl(name="form.login").value = self.student_id
2872        self.browser.getControl(name="form.password").value = 'spwd'
2873        self.browser.getControl("Login").click()
2874        self.assertTrue(
2875            'You logged in.' in self.browser.contents)
2876        # Now students can add the current study level
2877        self.browser.getLink("Study Course").click()
2878        self.browser.getLink("Add course list").click()
2879        self.assertMatches('...Add current level Postgraduate Level...',
2880                           self.browser.contents)
2881        self.browser.getControl("Create course list now").click()
2882        self.assertTrue("You successfully created a new course list"
2883            in self.browser.contents)
2884        # A level with one course ticket was created
2885        self.assertEqual(self.student['studycourse']['999'].number_of_tickets, 0)
2886        self.browser.getLink("Edit course list").click()
2887        self.browser.getLink("here").click()
2888        self.browser.getControl(name="form.course").value = ['COURSE1']
2889        self.browser.getControl("Add course ticket").click()
2890        self.assertMatches('...Successfully added COURSE1...',
2891                           self.browser.contents)
2892        # Postgraduate students can't register course lists
2893        self.browser.getControl("Register course list").click()
2894        self.assertTrue("your course list can't bee registered"
2895            in self.browser.contents)
2896        self.assertEqual(self.student.state, 'school fee paid')
2897        return
2898
2899    def test_student_clearance_wo_clrcode(self):
2900        IWorkflowState(self.student).setState('clearance started')
2901        self.browser.open(self.login_path)
2902        self.browser.getControl(name="form.login").value = self.student_id
2903        self.browser.getControl(name="form.password").value = 'spwd'
2904        self.browser.getControl("Login").click()
2905        self.browser.open(self.edit_clearance_path)
2906        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
2907        self.browser.getControl("Save and request clearance").click()
2908        self.assertMatches('...Clearance has been requested...',
2909                           self.browser.contents)
2910
2911    def test_student_clearance_payment(self):
2912        # Login
2913        self.browser.open(self.login_path)
2914        self.browser.getControl(name="form.login").value = self.student_id
2915        self.browser.getControl(name="form.password").value = 'spwd'
2916        self.browser.getControl("Login").click()
2917
2918        # Students can add online clearance payment tickets
2919        self.browser.open(self.payments_path + '/addop')
2920        self.browser.getControl(name="form.p_category").value = ['clearance']
2921        self.browser.getControl("Create ticket").click()
2922        self.assertMatches('...ticket created...',
2923                           self.browser.contents)
2924
2925        # Students can't approve the payment
2926        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
2927        ctrl = self.browser.getControl(name='val_id')
2928        value = ctrl.options[0]
2929        self.browser.getLink(value).click()
2930        payment_url = self.browser.url
2931        self.assertRaises(
2932            Unauthorized, self.browser.open, payment_url + '/approve')
2933        # In the base package they can 'use' a fake approval view.
2934        # XXX: I tried to use
2935        # self.student['payments'][value].approveStudentPayment() instead.
2936        # But this function fails in
2937        # w.k.accesscodes.accesscode.create_accesscode.
2938        # grok.getSite returns None in tests.
2939        self.browser.open(payment_url + '/fake_approve')
2940        self.assertMatches('...Payment approved...',
2941                          self.browser.contents)
2942        expected = '''...
2943        <td>
2944          <span>Paid</span>
2945        </td>...'''
2946        expected = '''...
2947        <td>
2948          <span>Paid</span>
2949        </td>...'''
2950        self.assertMatches(expected,self.browser.contents)
2951        payment_id = self.student['payments'].keys()[0]
2952        payment = self.student['payments'][payment_id]
2953        self.assertEqual(payment.p_state, 'paid')
2954        self.assertEqual(payment.r_amount_approved, 3456.0)
2955        self.assertEqual(payment.r_code, 'AP')
2956        self.assertEqual(payment.r_desc, u'Payment approved by Anna Tester')
2957        # The new CLR-0 pin has been created
2958        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
2959        pin = self.app['accesscodes']['CLR-0'].keys()[0]
2960        ac = self.app['accesscodes']['CLR-0'][pin]
2961        self.assertEqual(ac.owner, self.student_id)
2962        self.assertEqual(ac.cost, 3456.0)
2963
2964        # Students can open the pdf payment slip
2965        self.browser.open(payment_url + '/payment_slip.pdf')
2966        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2967        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2968
2969        # The new CLR-0 pin can be used for starting clearance
2970        # but they have to upload a passport picture first
2971        # which is only possible in state admitted
2972        self.browser.open(self.student_path + '/change_portrait')
2973        self.assertMatches('...form is locked...',
2974                          self.browser.contents)
2975        IWorkflowInfo(self.student).fireTransition('admit')
2976        self.browser.open(self.student_path + '/change_portrait')
2977        image = open(SAMPLE_IMAGE, 'rb')
2978        ctrl = self.browser.getControl(name='passportuploadedit')
2979        file_ctrl = ctrl.mech_control
2980        file_ctrl.add_file(image, filename='my_photo.jpg')
2981        self.browser.getControl(
2982            name='upload_passportuploadedit').click()
2983        self.browser.open(self.student_path + '/start_clearance')
2984        parts = pin.split('-')[1:]
2985        clrseries, clrnumber = parts
2986        self.browser.getControl(name="ac_series").value = clrseries
2987        self.browser.getControl(name="ac_number").value = clrnumber
2988        self.browser.getControl("Start clearance now").click()
2989        self.assertMatches('...Clearance process has been started...',
2990                           self.browser.contents)
2991
2992    def test_student_schoolfee_payment(self):
2993        configuration = createObject('waeup.SessionConfiguration')
2994        configuration.academic_session = 2005
2995        self.app['configuration'].addSessionConfiguration(configuration)
2996        # Login
2997        self.browser.open(self.login_path)
2998        self.browser.getControl(name="form.login").value = self.student_id
2999        self.browser.getControl(name="form.password").value = 'spwd'
3000        self.browser.getControl("Login").click()
3001
3002        # Students can add online school fee payment tickets.
3003        IWorkflowState(self.student).setState('returning')
3004        self.browser.open(self.payments_path)
3005        self.assertRaises(
3006            LookupError, self.browser.getControl, name='val_id')
3007        self.browser.getLink("Add current session payment ticket").click()
3008        self.browser.getControl(name="form.p_category").value = ['schoolfee']
3009        self.browser.getControl("Create ticket").click()
3010        self.assertMatches('...ticket created...',
3011                           self.browser.contents)
3012        ctrl = self.browser.getControl(name='val_id')
3013        value = ctrl.options[0]
3014        self.browser.getLink(value).click()
3015        self.assertMatches('...Amount Authorized...',
3016                           self.browser.contents)
3017        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
3018        # Payment session and will be calculated as defined
3019        # in w.k.students.utils because we set changed the state
3020        # to returning
3021        self.assertEqual(self.student['payments'][value].p_session, 2005)
3022        self.assertEqual(self.student['payments'][value].p_level, 200)
3023
3024        # Student is the payer of the payment ticket.
3025        payer = IPayer(self.student['payments'][value])
3026        self.assertEqual(payer.display_fullname, 'Anna Tester')
3027        self.assertEqual(payer.id, self.student_id)
3028        self.assertEqual(payer.faculty, 'fac1')
3029        self.assertEqual(payer.department, 'dep1')
3030
3031        # We simulate the approval
3032        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
3033        self.browser.open(self.browser.url + '/fake_approve')
3034        self.assertMatches('...Payment approved...',
3035                          self.browser.contents)
3036
3037        ## The new SFE-0 pin can be used for starting new session
3038        #self.browser.open(self.studycourse_path)
3039        #self.browser.getLink('Start new session').click()
3040        #pin = self.app['accesscodes']['SFE-0'].keys()[0]
3041        #parts = pin.split('-')[1:]
3042        #sfeseries, sfenumber = parts
3043        #self.browser.getControl(name="ac_series").value = sfeseries
3044        #self.browser.getControl(name="ac_number").value = sfenumber
3045        #self.browser.getControl("Start now").click()
3046        #self.assertMatches('...Session started...',
3047        #                   self.browser.contents)
3048
3049        self.assertTrue(self.student.state == 'school fee paid')
3050        return
3051
3052    def test_student_bedallocation_payment(self):
3053        # Login
3054        self.browser.open(self.login_path)
3055        self.browser.getControl(name="form.login").value = self.student_id
3056        self.browser.getControl(name="form.password").value = 'spwd'
3057        self.browser.getControl("Login").click()
3058        self.browser.open(self.payments_path)
3059        self.browser.open(self.payments_path + '/addop')
3060        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
3061        self.browser.getControl("Create ticket").click()
3062        self.assertMatches('...ticket created...',
3063                           self.browser.contents)
3064        # Students can remove only online payment tickets which have
3065        # not received a valid callback
3066        self.browser.open(self.payments_path)
3067        ctrl = self.browser.getControl(name='val_id')
3068        value = ctrl.options[0]
3069        ctrl.getControl(value=value).selected = True
3070        self.browser.getControl("Remove selected", index=0).click()
3071        self.assertTrue('Successfully removed' in self.browser.contents)
3072
3073    def test_student_maintenance_payment(self):
3074        # Login
3075        self.browser.open(self.login_path)
3076        self.browser.getControl(name="form.login").value = self.student_id
3077        self.browser.getControl(name="form.password").value = 'spwd'
3078        self.browser.getControl("Login").click()
3079        self.browser.open(self.payments_path)
3080        self.browser.open(self.payments_path + '/addop')
3081        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
3082        self.browser.getControl("Create ticket").click()
3083        self.assertMatches('...You have not yet booked accommodation...',
3084                           self.browser.contents)
3085        # We continue this test in test_student_accommodation
3086
3087    def test_student_previous_payments(self):
3088        configuration = createObject('waeup.SessionConfiguration')
3089        configuration.academic_session = 2000
3090        configuration.clearance_fee = 3456.0
3091        configuration.booking_fee = 123.4
3092        self.app['configuration'].addSessionConfiguration(configuration)
3093        configuration2 = createObject('waeup.SessionConfiguration')
3094        configuration2.academic_session = 2003
3095        configuration2.clearance_fee = 3456.0
3096        configuration2.booking_fee = 123.4
3097        self.app['configuration'].addSessionConfiguration(configuration2)
3098        configuration3 = createObject('waeup.SessionConfiguration')
3099        configuration3.academic_session = 2005
3100        configuration3.clearance_fee = 3456.0
3101        configuration3.booking_fee = 123.4
3102        self.app['configuration'].addSessionConfiguration(configuration3)
3103        self.student['studycourse'].entry_session = 2002
3104
3105        # Login
3106        self.browser.open(self.login_path)
3107        self.browser.getControl(name="form.login").value = self.student_id
3108        self.browser.getControl(name="form.password").value = 'spwd'
3109        self.browser.getControl("Login").click()
3110
3111        # Students can add previous school fee payment tickets in any state.
3112        IWorkflowState(self.student).setState('courses registered')
3113        self.browser.open(self.payments_path)
3114        self.browser.getLink("Add previous session payment ticket").click()
3115
3116        # Previous session payment form is provided
3117        self.assertEqual(self.student.current_session, 2004)
3118        self.browser.getControl(name="form.p_category").value = ['schoolfee']
3119        self.browser.getControl(name="form.p_session").value = ['2000']
3120        self.browser.getControl(name="form.p_level").value = ['300']
3121        self.browser.getControl("Create ticket").click()
3122        self.assertMatches('...The previous session must not fall below...',
3123                           self.browser.contents)
3124        self.browser.getControl(name="form.p_category").value = ['schoolfee']
3125        self.browser.getControl(name="form.p_session").value = ['2005']
3126        self.browser.getControl(name="form.p_level").value = ['300']
3127        self.browser.getControl("Create ticket").click()
3128        self.assertMatches('...This is not a previous session...',
3129                           self.browser.contents)
3130
3131        # Students can pay current session school fee.
3132        self.browser.getControl(name="form.p_category").value = ['schoolfee']
3133        self.browser.getControl(name="form.p_session").value = ['2004']
3134        self.browser.getControl(name="form.p_level").value = ['300']
3135        self.browser.getControl("Create ticket").click()
3136        self.assertMatches('...ticket created...',
3137                           self.browser.contents)
3138        ctrl = self.browser.getControl(name='val_id')
3139        value = ctrl.options[0]
3140        self.browser.getLink(value).click()
3141        self.assertMatches('...Amount Authorized...',
3142                           self.browser.contents)
3143        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
3144
3145        # Ticket creation is logged.
3146        logfile = os.path.join(
3147            self.app['datacenter'].storage, 'logs', 'students.log')
3148        logcontent = open(logfile).read()
3149        self.assertTrue(
3150            'K1000000 - students.browser.PreviousPaymentAddFormPage - '
3151            'K1000000 - added: %s' % value
3152            in logcontent)
3153
3154        # Payment session is properly set
3155        self.assertEqual(self.student['payments'][value].p_session, 2004)
3156        self.assertEqual(self.student['payments'][value].p_level, 300)
3157
3158        # We simulate the approval
3159        self.browser.open(self.browser.url + '/fake_approve')
3160        self.assertMatches('...Payment approved...',
3161                          self.browser.contents)
3162
3163        # No AC has been created
3164        self.assertEqual(len(self.app['accesscodes']['SFE-0'].keys()), 0)
3165        self.assertTrue(self.student['payments'][value].ac is None)
3166
3167        # Current payment flag is set False
3168        self.assertFalse(self.student['payments'][value].p_current)
3169
3170        # Button and form are not available for students who are in
3171        # states up to cleared
3172        self.student['studycourse'].entry_session = 2004
3173        IWorkflowState(self.student).setState('cleared')
3174        self.browser.open(self.payments_path)
3175        self.assertFalse(
3176            "Add previous session payment ticket" in self.browser.contents)
3177        self.browser.open(self.payments_path + '/addpp')
3178        self.assertTrue(
3179            "No previous payment to be made" in self.browser.contents)
3180        return
3181
3182    def test_postgraduate_student_payments(self):
3183        configuration = createObject('waeup.SessionConfiguration')
3184        configuration.academic_session = 2005
3185        self.app['configuration'].addSessionConfiguration(configuration)
3186        self.certificate.study_mode = 'pg_ft'
3187        self.certificate.start_level = 999
3188        self.certificate.end_level = 999
3189        self.student['studycourse'].current_level = 999
3190        # Login
3191        self.browser.open(self.login_path)
3192        self.browser.getControl(name="form.login").value = self.student_id
3193        self.browser.getControl(name="form.password").value = 'spwd'
3194        self.browser.getControl("Login").click()
3195        # Students can add online school fee payment tickets.
3196        IWorkflowState(self.student).setState('cleared')
3197        self.browser.open(self.payments_path)
3198        self.browser.getLink("Add current session payment ticket").click()
3199        self.browser.getControl(name="form.p_category").value = ['schoolfee']
3200        self.browser.getControl("Create ticket").click()
3201        self.assertMatches('...ticket created...',
3202                           self.browser.contents)
3203        ctrl = self.browser.getControl(name='val_id')
3204        value = ctrl.options[0]
3205        self.browser.getLink(value).click()
3206        self.assertMatches('...Amount Authorized...',
3207                           self.browser.contents)
3208        # Payment session and level are current ones.
3209        # Postgrads have to pay school_fee_1.
3210        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
3211        self.assertEqual(self.student['payments'][value].p_session, 2004)
3212        self.assertEqual(self.student['payments'][value].p_level, 999)
3213
3214        # We simulate the approval
3215        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
3216        self.browser.open(self.browser.url + '/fake_approve')
3217        self.assertMatches('...Payment approved...',
3218                          self.browser.contents)
3219
3220        ## The new SFE-0 pin can be used for starting session
3221        #self.browser.open(self.studycourse_path)
3222        #self.browser.getLink('Start new session').click()
3223        #pin = self.app['accesscodes']['SFE-0'].keys()[0]
3224        #parts = pin.split('-')[1:]
3225        #sfeseries, sfenumber = parts
3226        #self.browser.getControl(name="ac_series").value = sfeseries
3227        #self.browser.getControl(name="ac_number").value = sfenumber
3228        #self.browser.getControl("Start now").click()
3229        #self.assertMatches('...Session started...',
3230        #                   self.browser.contents)
3231
3232        self.assertTrue(self.student.state == 'school fee paid')
3233
3234        # Postgrad students do not need to register courses the
3235        # can just pay for the next session.
3236        self.browser.open(self.payments_path)
3237        # Remove first payment to be sure that we access the right ticket
3238        del self.student['payments'][value]
3239        self.browser.getLink("Add current session payment ticket").click()
3240        self.browser.getControl(name="form.p_category").value = ['schoolfee']
3241        self.browser.getControl("Create ticket").click()
3242        ctrl = self.browser.getControl(name='val_id')
3243        value = ctrl.options[0]
3244        self.browser.getLink(value).click()
3245        # Payment session has increased by one, payment level remains the same.
3246        # Returning Postgraduates have to pay school_fee_2.
3247        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
3248        self.assertEqual(self.student['payments'][value].p_session, 2005)
3249        self.assertEqual(self.student['payments'][value].p_level, 999)
3250
3251        # Student is still in old session
3252        self.assertEqual(self.student.current_session, 2004)
3253
3254        # We do not need to pay the ticket if any other
3255        # SFE pin is provided
3256        pin_container = self.app['accesscodes']
3257        pin_container.createBatch(
3258            datetime.utcnow(), 'some_userid', 'SFE', 9.99, 1)
3259        pin = pin_container['SFE-1'].values()[0].representation
3260        sfeseries, sfenumber = pin.split('-')[1:]
3261        # The new SFE-1 pin can be used for starting new session
3262        self.browser.open(self.studycourse_path)
3263        self.browser.getLink('Start new session').click()
3264        self.browser.getControl(name="ac_series").value = sfeseries
3265        self.browser.getControl(name="ac_number").value = sfenumber
3266        self.browser.getControl("Start now").click()
3267        self.assertMatches('...Session started...',
3268                           self.browser.contents)
3269        self.assertTrue(self.student.state == 'school fee paid')
3270        # Student is in new session
3271        self.assertEqual(self.student.current_session, 2005)
3272        self.assertEqual(self.student['studycourse'].current_level, 999)
3273        return
3274
3275    def test_student_accommodation(self):
3276        # Create a second hostel with one bed
3277        hostel = Hostel()
3278        hostel.hostel_id = u'hall-2'
3279        hostel.hostel_name = u'Hall 2'
3280        self.app['hostels'].addHostel(hostel)
3281        bed = Bed()
3282        bed.bed_id = u'hall-2_A_101_A'
3283        bed.bed_number = 1
3284        bed.owner = NOT_OCCUPIED
3285        bed.bed_type = u'regular_female_fr'
3286        self.app['hostels'][hostel.hostel_id].addBed(bed)
3287        self.app['hostels'].allocation_expiration = 7
3288
3289        self.browser.open(self.login_path)
3290        self.browser.getControl(name="form.login").value = self.student_id
3291        self.browser.getControl(name="form.password").value = 'spwd'
3292        self.browser.getControl("Login").click()
3293        # Students can add online booking fee payment tickets and open the
3294        # callback view (see test_manage_payments).
3295        self.browser.getLink("Payments").click()
3296        self.browser.getLink("Add current session payment ticket").click()
3297        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
3298        self.browser.getControl("Create ticket").click()
3299        ctrl = self.browser.getControl(name='val_id')
3300        value = ctrl.options[0]
3301        self.browser.getLink(value).click()
3302        self.browser.open(self.browser.url + '/fake_approve')
3303        # The new HOS-0 pin has been created.
3304        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
3305        pin = self.app['accesscodes']['HOS-0'].keys()[0]
3306        ac = self.app['accesscodes']['HOS-0'][pin]
3307        parts = pin.split('-')[1:]
3308        sfeseries, sfenumber = parts
3309        # Students can use HOS code and book a bed space with it ...
3310        self.browser.open(self.acco_path)
3311        # ... but not if booking period has expired ...
3312        self.app['hostels'].enddate = datetime.now(pytz.utc)
3313        self.browser.getControl("Book accommodation").click()
3314        self.assertMatches('...Outside booking period: ...',
3315                           self.browser.contents)
3316        self.app['hostels'].enddate = datetime.now(pytz.utc) + timedelta(days=10)
3317        # ... or student data are incomplete ...
3318        self.student['studycourse'].current_level = None
3319        self.browser.getControl("Book accommodation").click()
3320        self.assertMatches('...Your data are incomplete...',
3321            self.browser.contents)
3322        self.student['studycourse'].current_level = 100
3323        # ... or student is not the an allowed state ...
3324        self.browser.getControl("Book accommodation").click()
3325        self.assertMatches('...You are in the wrong...',
3326                           self.browser.contents)
3327        # Students can still not see the disired hostel selector.
3328        self.assertFalse('desired hostel' in self.browser.contents)
3329        IWorkflowInfo(self.student).fireTransition('admit')
3330        # Students can now see the disired hostel selector.
3331        self.browser.reload()
3332        self.browser.open(self.acco_path)
3333        self.assertTrue('desired hostel' in self.browser.contents)
3334        self.browser.getControl(name="hostel").value = ['hall-2']
3335        self.browser.getControl("Save").click()
3336        self.assertTrue('selection has been saved' in self.browser.contents)
3337        self.assertTrue('<option selected="selected" value="hall-2">'
3338            in self.browser.contents)
3339        self.browser.getControl("Book accommodation").click()
3340        self.assertMatches('...Activation Code:...',
3341                           self.browser.contents)
3342        # Student can't use faked ACs ...
3343        self.browser.getControl(name="ac_series").value = u'nonsense'
3344        self.browser.getControl(name="ac_number").value = sfenumber
3345        self.browser.getControl("Create bed ticket").click()
3346        self.assertMatches('...Activation code is invalid...',
3347                           self.browser.contents)
3348        # ... or ACs owned by somebody else.
3349        ac.owner = u'Anybody'
3350        self.browser.getControl(name="ac_series").value = sfeseries
3351        self.browser.getControl(name="ac_number").value = sfenumber
3352        self.browser.getControl("Create bed ticket").click()
3353        # Hostel 2 has only a bed for women.
3354        self.assertTrue('There is no free bed in your desired hostel'
3355            in self.browser.contents)
3356        self.browser.getControl(name="hostel").value = ['hall-1']
3357        self.browser.getControl("Save").click()
3358        self.browser.getControl("Book accommodation").click()
3359        # Student can't use faked ACs ...
3360        self.browser.getControl(name="ac_series").value = sfeseries
3361        self.browser.getControl(name="ac_number").value = sfenumber
3362        self.browser.getControl("Create bed ticket").click()
3363        self.assertMatches('...You are not the owner of this access code...',
3364                           self.browser.contents)
3365        # The bed remains empty.
3366        bed = self.app['hostels']['hall-1']['hall-1_A_101_A']
3367        self.assertTrue(bed.owner == NOT_OCCUPIED)
3368        ac.owner = self.student_id
3369        self.browser.getControl(name="ac_series").value = sfeseries
3370        self.browser.getControl(name="ac_number").value = sfenumber
3371        self.browser.getControl("Create bed ticket").click()
3372        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
3373                           self.browser.contents)
3374        # Bed has been allocated.
3375        self.assertTrue(bed.owner == self.student_id)
3376        # BedTicketAddPage is now blocked.
3377        self.browser.getControl("Book accommodation").click()
3378        self.assertMatches('...You already booked a bed space...',
3379            self.browser.contents)
3380        # The bed ticket displays the data correctly.
3381        self.browser.open(self.acco_path + '/2004')
3382        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
3383                           self.browser.contents)
3384        self.assertMatches('...2004/2005...', self.browser.contents)
3385        self.assertMatches('...regular_male_fr...', self.browser.contents)
3386        self.assertMatches('...%s...' % pin, self.browser.contents)
3387        # Students can open the pdf slip.
3388        self.browser.open(self.browser.url + '/bed_allocation_slip.pdf')
3389        self.assertEqual(self.browser.headers['Status'], '200 Ok')
3390        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
3391        path = os.path.join(samples_dir(), 'bed_allocation_slip.pdf')
3392        open(path, 'wb').write(self.browser.contents)
3393        print "Sample PDF bed_allocation_slip.pdf written to %s" % path
3394        # Students can't relocate themselves.
3395        self.assertFalse('Relocate' in self.browser.contents)
3396        relocate_path = self.acco_path + '/2004/relocate'
3397        self.assertRaises(
3398            Unauthorized, self.browser.open, relocate_path)
3399        # Students can't see the Remove button and check boxes.
3400        self.browser.open(self.acco_path)
3401        self.assertFalse('Remove' in self.browser.contents)
3402        self.assertFalse('val_id' in self.browser.contents)
3403        # Students can pay maintenance fee now.
3404        self.browser.open(self.payments_path)
3405        self.browser.open(self.payments_path + '/addop')
3406        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
3407        self.browser.getControl("Create ticket").click()
3408        self.assertMatches('...Payment ticket created...',
3409                           self.browser.contents)
3410        ctrl = self.browser.getControl(name='val_id')
3411        value = ctrl.options[0]
3412        # Maintennace fee is taken from the hostel object.
3413        self.assertEqual(self.student['payments'][value].amount_auth, 876.0)
3414        # If the hostel's maintenance fee isn't set, the fee is
3415        # taken from the session configuration object.
3416        self.app['hostels']['hall-1'].maint_fee = 0.0
3417        self.browser.open(self.payments_path + '/addop')
3418        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
3419        self.browser.getControl("Create ticket").click()
3420        ctrl = self.browser.getControl(name='val_id')
3421        value = ctrl.options[1]
3422        self.assertEqual(self.student['payments'][value].amount_auth, 987.0)
3423        # The bedticket is aware of successfull maintenance fee payment
3424        bedticket = self.student['accommodation']['2004']
3425        self.assertFalse(bedticket.maint_payment_made)
3426        self.student['payments'][value].approve()
3427        self.assertTrue(bedticket.maint_payment_made)
3428        return
3429
3430    def test_change_password_request(self):
3431        self.browser.open('http://localhost/app/changepw')
3432        self.browser.getControl(name="form.identifier").value = '123'
3433        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
3434        self.browser.getControl("Send login credentials").click()
3435        self.assertTrue('An email with' in self.browser.contents)
3436
3437    def test_student_expired_personal_data(self):
3438        # Login
3439        IWorkflowState(self.student).setState('school fee paid')
3440        delta = timedelta(days=180)
3441        self.student.personal_updated = datetime.utcnow() - delta
3442        self.browser.open(self.login_path)
3443        self.browser.getControl(name="form.login").value = self.student_id
3444        self.browser.getControl(name="form.password").value = 'spwd'
3445        self.browser.getControl("Login").click()
3446        self.assertEqual(self.browser.url, self.student_path)
3447        self.assertTrue(
3448            'You logged in' in self.browser.contents)
3449        # Students don't see personal_updated field in edit form
3450        self.browser.open(self.edit_personal_path)
3451        self.assertFalse('Updated' in self.browser.contents)
3452        self.browser.open(self.personal_path)
3453        self.assertTrue('Updated' in self.browser.contents)
3454        self.browser.getLink("Logout").click()
3455        delta = timedelta(days=181)
3456        self.student.personal_updated = datetime.utcnow() - delta
3457        self.browser.open(self.login_path)
3458        self.browser.getControl(name="form.login").value = self.student_id
3459        self.browser.getControl(name="form.password").value = 'spwd'
3460        self.browser.getControl("Login").click()
3461        self.assertEqual(self.browser.url, self.edit_personal_path)
3462        self.assertTrue(
3463            'Your personal data record is outdated.' in self.browser.contents)
3464
3465    def test_request_transcript(self):
3466        IWorkflowState(self.student).setState('graduated')
3467        self.browser.open(self.login_path)
3468        self.browser.getControl(name="form.login").value = self.student_id
3469        self.browser.getControl(name="form.password").value = 'spwd'
3470        self.browser.getControl("Login").click()
3471        self.assertMatches(
3472            '...You logged in...', self.browser.contents)
3473        # Create payment ticket
3474        self.browser.open(self.payments_path)
3475        self.browser.open(self.payments_path + '/addop')
3476        self.browser.getControl(name="form.p_category").value = ['transcript']
3477        self.browser.getControl("Create ticket").click()
3478        ctrl = self.browser.getControl(name='val_id')
3479        value = ctrl.options[0]
3480        self.browser.getLink(value).click()
3481        self.assertMatches('...Amount Authorized...',
3482                           self.browser.contents)
3483        self.assertEqual(self.student['payments'][value].amount_auth, 4567.0)
3484        # Student is the payer of the payment ticket.
3485        payer = IPayer(self.student['payments'][value])
3486        self.assertEqual(payer.display_fullname, 'Anna Tester')
3487        self.assertEqual(payer.id, self.student_id)
3488        self.assertEqual(payer.faculty, 'fac1')
3489        self.assertEqual(payer.department, 'dep1')
3490        # We simulate the approval and fetch the pin
3491        self.assertEqual(len(self.app['accesscodes']['TSC-0']),0)
3492        self.browser.open(self.browser.url + '/fake_approve')
3493        self.assertMatches('...Payment approved...',
3494                          self.browser.contents)
3495        pin = self.app['accesscodes']['TSC-0'].keys()[0]
3496        parts = pin.split('-')[1:]
3497        tscseries, tscnumber = parts
3498        # Student can use the pin to send the transcript request
3499        self.browser.open(self.student_path)
3500        self.browser.getLink("Request transcript").click()
3501        self.browser.getControl(name="ac_series").value = tscseries
3502        self.browser.getControl(name="ac_number").value = tscnumber
3503        self.browser.getControl(name="comment").value = 'Comment line 1 \nComment line2'
3504        self.browser.getControl(name="address").value = 'Address line 1 \nAddress line2'
3505        self.browser.getControl("Submit").click()
3506        self.assertMatches('...Transcript processing has been started...',
3507                          self.browser.contents)
3508        self.assertEqual(self.student.state, 'transcript requested')
3509        self.assertMatches(
3510            '... UTC K1000000 wrote:\n\nComment line 1 \n'
3511            'Comment line2\n\nDispatch Address:\nAddress line 1 \n'
3512            'Address line2\n\n', self.student['studycourse'].transcript_comment)
3513        # The comment has been logged
3514        logfile = os.path.join(
3515            self.app['datacenter'].storage, 'logs', 'students.log')
3516        logcontent = open(logfile).read()
3517        self.assertTrue(
3518            'K1000000 - students.browser.StudentTranscriptRequestPage - '
3519            'K1000000 - comment: Comment line 1 <br>Comment line2\n'
3520            in logcontent)
3521
3522    def test_late_registration(self):
3523        # Login
3524        delta = timedelta(days=10)
3525        self.app['configuration'][
3526            '2004'].coursereg_deadline = datetime.now(pytz.utc) - delta
3527        IWorkflowState(self.student).setState('school fee paid')
3528        self.browser.open(self.login_path)
3529        self.browser.getControl(name="form.login").value = self.student_id
3530        self.browser.getControl(name="form.password").value = 'spwd'
3531        self.browser.getControl("Login").click()
3532        self.browser.open(self.payments_path)
3533        self.browser.open(self.payments_path + '/addop')
3534        self.browser.getControl(name="form.p_category").value = ['late_registration']
3535        self.browser.getControl("Create ticket").click()
3536        self.assertMatches('...ticket created...',
3537                           self.browser.contents)
3538        self.browser.open(self.payments_path)
3539        ctrl = self.browser.getControl(name='val_id')
3540        value = ctrl.options[0]
3541        self.browser.getLink("Study Course").click()
3542        self.browser.getLink("Add course list").click()
3543        self.assertMatches('...Add current level 100 (Year 1)...',
3544                           self.browser.contents)
3545        self.browser.getControl("Create course list now").click()
3546        self.browser.getLink("100").click()
3547        self.browser.getLink("Edit course list").click()
3548        self.browser.getControl("Register course list").click()
3549        self.assertTrue('Course registration has ended. Please pay' in self.browser.contents)
3550        self.student['payments'][value].approve()
3551        self.browser.getControl("Register course list").click()
3552        self.assertTrue('Course list has been registered' in self.browser.contents)
3553        self.assertEqual(self.student.state, 'courses registered')
3554
3555
3556class StudentRequestPWTests(StudentsFullSetup):
3557    # Tests for student registration
3558
3559    layer = FunctionalLayer
3560
3561    def test_request_pw(self):
3562        # Student with wrong number can't be found.
3563        self.browser.open('http://localhost/app/requestpw')
3564        self.browser.getControl(name="form.lastname").value = 'Tester'
3565        self.browser.getControl(name="form.number").value = 'anynumber'
3566        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
3567        self.browser.getControl("Send login credentials").click()
3568        self.assertTrue('No student record found.'
3569            in self.browser.contents)
3570        # Anonymous is not informed that lastname verification failed.
3571        # It seems that the record doesn't exist.
3572        self.browser.open('http://localhost/app/requestpw')
3573        self.browser.getControl(name="form.lastname").value = 'Johnny'
3574        self.browser.getControl(name="form.number").value = '123'
3575        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
3576        self.browser.getControl("Send login credentials").click()
3577        self.assertTrue('No student record found.'
3578            in self.browser.contents)
3579        # Even with the correct lastname we can't register if a
3580        # password has been set and used.
3581        self.browser.getControl(name="form.lastname").value = 'Tester'
3582        self.browser.getControl(name="form.number").value = '123'
3583        self.browser.getControl("Send login credentials").click()
3584        self.assertTrue('Your password has already been set and used.'
3585            in self.browser.contents)
3586        self.browser.open('http://localhost/app/requestpw')
3587        self.app['students'][self.student_id].password = None
3588        # The lastname field, used for verification, is not case-sensitive.
3589        self.browser.getControl(name="form.lastname").value = 'tESTer'
3590        self.browser.getControl(name="form.number").value = '123'
3591        self.browser.getControl(name="form.email").value = 'new@yy.zz'
3592        self.browser.getControl("Send login credentials").click()
3593        # Yeah, we succeded ...
3594        self.assertTrue('Your password request was successful.'
3595            in self.browser.contents)
3596        # We can also use the matric_number instead.
3597        self.browser.open('http://localhost/app/requestpw')
3598        self.browser.getControl(name="form.lastname").value = 'tESTer'
3599        self.browser.getControl(name="form.number").value = '234'
3600        self.browser.getControl(name="form.email").value = 'new@yy.zz'
3601        self.browser.getControl("Send login credentials").click()
3602        self.assertTrue('Your password request was successful.'
3603            in self.browser.contents)
3604        # ... and  student can be found in the catalog via the email address
3605        cat = queryUtility(ICatalog, name='students_catalog')
3606        results = list(
3607            cat.searchResults(
3608            email=('new@yy.zz', 'new@yy.zz')))
3609        self.assertEqual(self.student,results[0])
3610        logfile = os.path.join(
3611            self.app['datacenter'].storage, 'logs', 'main.log')
3612        logcontent = open(logfile).read()
3613        self.assertTrue('zope.anybody - students.browser.StudentRequestPasswordPage - '
3614                        '234 (K1000000) - new@yy.zz' in logcontent)
3615        return
3616
3617    def test_student_locked_level_forms(self):
3618
3619        # Add two study levels, one current and one previous
3620        studylevel = createObject(u'waeup.StudentStudyLevel')
3621        studylevel.level = 100
3622        self.student['studycourse'].addStudentStudyLevel(
3623            self.certificate, studylevel)
3624        studylevel = createObject(u'waeup.StudentStudyLevel')
3625        studylevel.level = 200
3626        self.student['studycourse'].addStudentStudyLevel(
3627            self.certificate, studylevel)
3628        IWorkflowState(self.student).setState('school fee paid')
3629        self.student['studycourse'].current_level = 200
3630
3631        self.browser.open(self.login_path)
3632        self.browser.getControl(name="form.login").value = self.student_id
3633        self.browser.getControl(name="form.password").value = 'spwd'
3634        self.browser.getControl("Login").click()
3635
3636        self.browser.open(self.student_path + '/studycourse/200/edit')
3637        self.assertFalse('The requested form is locked' in self.browser.contents)
3638        self.browser.open(self.student_path + '/studycourse/100/edit')
3639        self.assertTrue('The requested form is locked' in self.browser.contents)
3640
3641        self.browser.open(self.student_path + '/studycourse/200/ctadd')
3642        self.assertFalse('The requested form is locked' in self.browser.contents)
3643        self.browser.open(self.student_path + '/studycourse/100/ctadd')
3644        self.assertTrue('The requested form is locked' in self.browser.contents)
3645
3646        IWorkflowState(self.student).setState('courses registered')
3647        self.browser.open(self.student_path + '/studycourse/200/edit')
3648        self.assertTrue('The requested form is locked' in self.browser.contents)
3649        self.browser.open(self.student_path + '/studycourse/200/ctadd')
3650        self.assertTrue('The requested form is locked' in self.browser.contents)
3651
3652
3653class PublicPagesTests(StudentsFullSetup):
3654    # Tests for simple webservices
3655
3656    layer = FunctionalLayer
3657
3658    def test_paymentrequest(self):
3659        payment = createObject('waeup.StudentOnlinePayment')
3660        payment.p_category = u'schoolfee'
3661        payment.p_session = self.student.current_session
3662        payment.p_item = u'My Certificate'
3663        payment.p_id = u'anyid'
3664        self.student['payments']['anykey'] = payment
3665        # Request information about unpaid payment ticket
3666        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
3667        self.assertEqual(self.browser.contents, '-1')
3668        # Request information about paid payment ticket
3669        payment.p_state = u'paid'
3670        notify(grok.ObjectModifiedEvent(payment))
3671        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
3672        self.assertEqual(self.browser.contents,
3673            'FULL_NAME=Anna Tester&FACULTY=fac1&DEPARTMENT=dep1'
3674            '&PAYMENT_ITEM=My Certificate&PAYMENT_CATEGORY=School Fee'
3675            '&ACADEMIC_SESSION=2004/2005&MATRIC_NUMBER=234&REG_NUMBER=123'
3676            '&FEE_AMOUNT=0.0')
3677        self.browser.open('http://localhost/app/paymentrequest?NONSENSE=nonsense')
3678        self.assertEqual(self.browser.contents, '-1')
3679        self.browser.open('http://localhost/app/paymentrequest?P_ID=nonsense')
3680        self.assertEqual(self.browser.contents, '-1')
3681
3682class StudentDataExportTests(StudentsFullSetup, FunctionalAsyncTestCase):
3683    # Tests for StudentsContainer class views and pages
3684
3685    layer = FunctionalLayer
3686
3687    def wait_for_export_job_completed(self):
3688        # helper function waiting until the current export job is completed
3689        manager = getUtility(IJobManager)
3690        job_id = self.app['datacenter'].running_exports[0][0]
3691        job = manager.get(job_id)
3692        wait_for_result(job)
3693        return job_id
3694
3695    def add_payment(self, student):
3696        # get a payment with all fields set
3697        payment = StudentOnlinePayment()
3698        payment.creation_date = datetime(2012, 12, 13)
3699        payment.p_id = 'my-id'
3700        payment.p_category = u'schoolfee'
3701        payment.p_state = 'paid'
3702        payment.ac = u'666'
3703        payment.p_item = u'p-item'
3704        payment.p_level = 100
3705        payment.p_session = curr_year - 6
3706        payment.payment_date = datetime(2012, 12, 13)
3707        payment.amount_auth = 12.12
3708        payment.r_amount_approved = 12.12
3709        payment.r_code = u'r-code'
3710        # XXX: there is no addPayment method to give predictable names
3711        self.payment = student['payments']['my-payment'] = payment
3712        return payment
3713
3714    def test_datacenter_export(self):
3715        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3716        self.browser.open('http://localhost/app/datacenter/@@exportconfig')
3717        self.browser.getControl(name="exporter").value = ['bursary']
3718        self.browser.getControl(name="session").value = ['2004']
3719        self.browser.getControl(name="level").value = ['100']
3720        self.browser.getControl(name="mode").value = ['ug_ft']
3721        self.browser.getControl(name="payments_start").value = '13/12/2012'
3722        self.browser.getControl(name="payments_end").value = '14/12/2012'
3723        self.browser.getControl("Create CSV file").click()
3724
3725        # When the job is finished and we reload the page...
3726        job_id = self.wait_for_export_job_completed()
3727        # ... the csv file can be downloaded ...
3728        self.browser.open('http://localhost/app/datacenter/@@export')
3729        self.browser.getLink("Download").click()
3730        self.assertEqual(self.browser.headers['content-type'],
3731            'text/csv; charset=UTF-8')
3732        self.assertTrue(
3733            'filename="WAeUP.Kofa_bursary_%s.csv' % job_id in
3734            self.browser.headers['content-disposition'])
3735        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3736        job_id = self.app['datacenter'].running_exports[0][0]
3737        # ... and discarded
3738        self.browser.open('http://localhost/app/datacenter/@@export')
3739        self.browser.getControl("Discard").click()
3740        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3741        # Creation, downloading and discarding is logged
3742        logfile = os.path.join(
3743            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3744        logcontent = open(logfile).read()
3745        self.assertTrue(
3746            'zope.mgr - students.browser.DatacenterExportJobContainerJobConfig '
3747            '- exported: bursary (2004, 100, ug_ft, None, None, None, '
3748            '13/12/2012, 14/12/2012, all, all, all, all), job_id=%s'
3749            % job_id in logcontent
3750            )
3751        self.assertTrue(
3752            'zope.mgr - browser.pages.ExportCSVView '
3753            '- downloaded: WAeUP.Kofa_bursary_%s.csv, job_id=%s'
3754            % (job_id, job_id) in logcontent
3755            )
3756        self.assertTrue(
3757            'zope.mgr - browser.pages.ExportCSVPage '
3758            '- discarded: job_id=%s' % job_id in logcontent
3759            )
3760
3761    def test_datacenter_export_selected(self):
3762        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3763        self.browser.open('http://localhost/app/datacenter/@@exportselected')
3764        self.browser.getControl(name="exporter").value = ['students']
3765        self.browser.getControl(name="students").value = 'K1000000'
3766        self.browser.getControl("Create CSV file").click()
3767        # When the job is finished and we reload the page...
3768        job_id = self.wait_for_export_job_completed()
3769        # ... the csv file can be downloaded ...
3770        self.browser.open('http://localhost/app/datacenter/@@export')
3771        self.browser.getLink("Download").click()
3772        self.assertEqual(self.browser.headers['content-type'],
3773            'text/csv; charset=UTF-8')
3774        self.assertTrue(
3775            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3776            self.browser.headers['content-disposition'])
3777        self.assertTrue(
3778            'adm_code,clr_code,date_of_birth,email,employer,'
3779            'firstname,flash_notice,lastname,matric_number,middlename,nationality,'
3780            'officer_comment,perm_address,personal_updated,phone,reg_number,'
3781            'sex,student_id,suspended,suspended_comment,'
3782            'password,state,history,certcode,is_postgrad,'
3783            'current_level,current_session\r\n'
3784            ',,1981-02-04#,aa@aa.ng,,Anna,,Tester,234,,,,,,'
3785            '1234#,123,m,K1000000,0,,{SSHA}' in self.browser.contents)
3786
3787    def test_payment_dates(self):
3788        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3789        self.browser.open('http://localhost/app/datacenter/@@exportconfig')
3790        self.browser.getControl(name="exporter").value = ['bursary']
3791        self.browser.getControl(name="session").value = ['2004']
3792        self.browser.getControl(name="level").value = ['100']
3793        self.browser.getControl(name="mode").value = ['ug_ft']
3794        self.browser.getControl(name="payments_start").value = '13/12/2012'
3795        # If one payment date is missing, an error message appears
3796        self.browser.getControl(name="payments_end").value = ''
3797        self.browser.getControl("Create CSV file").click()
3798        self.assertTrue('Payment dates do not match format d/m/Y'
3799            in self.browser.contents)
3800
3801    def test_faculties_export(self):
3802        self.add_payment(self.student)
3803        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3804        facs_path = 'http://localhost/app/faculties'
3805        self.browser.open(facs_path)
3806        self.browser.getLink("Export student data").click()
3807        self.browser.getControl("Configure new export").click()
3808        self.browser.getControl(name="exporter").value = ['bursary']
3809        self.browser.getControl(name="session").value = ['2004']
3810        self.browser.getControl(name="level").value = ['100']
3811        self.browser.getControl(name="mode").value = ['ug_ft']
3812        self.browser.getControl(name="payments_start").value = '13/12/2012'
3813        self.browser.getControl(name="payments_end").value = '14/12/2012'
3814        self.browser.getControl(name="paycat").value = ['schoolfee']
3815        self.browser.getControl("Create CSV file").click()
3816
3817        # When the job is finished and we reload the page...
3818        job_id = self.wait_for_export_job_completed()
3819        self.browser.open(facs_path + '/exports')
3820        # ... the csv file can be downloaded ...
3821        self.browser.getLink("Download").click()
3822        self.assertEqual(self.browser.headers['content-type'],
3823            'text/csv; charset=UTF-8')
3824        self.assertTrue(
3825            'filename="WAeUP.Kofa_bursary_%s.csv' % job_id in
3826            self.browser.headers['content-disposition'])
3827        self.assertTrue(
3828            '666,12.12,2012-12-13 00:00:00#,schoolfee,1,my-id,p-item,100,2013,'
3829            'paid,2012-12-13 00:00:00#,12.12,r-code,,K1000000,234,123,Anna,,'
3830            'Tester,created,2004,2004,,fac1,dep1,CERT1' in self.browser.contents)
3831        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3832        job_id = self.app['datacenter'].running_exports[0][0]
3833        # ... and discarded
3834        self.browser.open(facs_path + '/exports')
3835        self.browser.getControl("Discard").click()
3836        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3837        # Creation, downloading and discarding is logged
3838        logfile = os.path.join(
3839            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3840        logcontent = open(logfile).read()
3841        self.assertTrue(
3842            'zope.mgr - students.browser.FacultiesExportJobContainerJobConfig '
3843            '- exported: bursary (2004, 100, ug_ft, None, None, None, '
3844            '13/12/2012, 14/12/2012, all, all, schoolfee, all), job_id=%s'
3845            % job_id in logcontent
3846            )
3847        self.assertTrue(
3848            'zope.mgr - students.browser.ExportJobContainerDownload '
3849            '- downloaded: WAeUP.Kofa_bursary_%s.csv, job_id=%s'
3850            % (job_id, job_id) in logcontent
3851            )
3852        self.assertTrue(
3853            'zope.mgr - students.browser.ExportJobContainerOverview '
3854            '- discarded: job_id=%s' % job_id in logcontent
3855            )
3856
3857    def test_faculty_export(self):
3858        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3859        fac1_path = 'http://localhost/app/faculties/fac1'
3860        self.browser.open(fac1_path)
3861        self.browser.getLink("Export student data").click()
3862        self.browser.getControl("Configure new export").click()
3863        self.browser.getControl(name="exporter").value = ['students']
3864        self.browser.getControl(name="session").value = ['2004']
3865        self.browser.getControl(name="level").value = ['100']
3866        self.browser.getControl(name="mode").value = ['ug_ft']
3867        # The testbrowser does not hide the payment period fields, but
3868        # values are ignored when using the students exporter.
3869        self.browser.getControl(name="payments_start").value = '13/12/2012'
3870        self.browser.getControl(name="payments_end").value = '14/12/2012'
3871        self.browser.getControl("Create CSV file").click()
3872
3873        # When the job is finished and we reload the page...
3874        job_id = self.wait_for_export_job_completed()
3875        self.browser.open(fac1_path + '/exports')
3876        # ... the csv file can be downloaded ...
3877        self.browser.getLink("Download").click()
3878        self.assertEqual(self.browser.headers['content-type'],
3879            'text/csv; charset=UTF-8')
3880        self.assertTrue(
3881            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3882            self.browser.headers['content-disposition'])
3883        self.assertTrue(
3884            'adm_code,clr_code,date_of_birth,email,employer,'
3885            'firstname,flash_notice,lastname,matric_number,middlename,nationality,'
3886            'officer_comment,perm_address,personal_updated,phone,reg_number,'
3887            'sex,student_id,suspended,suspended_comment,'
3888            'password,state,history,certcode,is_postgrad,'
3889            'current_level,current_session\r\n'
3890            ',,1981-02-04#,aa@aa.ng,,Anna,,Tester,234,,,,,,'
3891            '1234#,123,m,K1000000,0,,{SSHA}' in self.browser.contents)
3892        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3893        job_id = self.app['datacenter'].running_exports[0][0]
3894        # ... and discarded
3895        self.browser.open(fac1_path + '/exports')
3896        self.browser.getControl("Discard").click()
3897        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3898        # Creation, downloading and discarding is logged
3899        logfile = os.path.join(
3900            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3901        logcontent = open(logfile).read()
3902        self.assertTrue(
3903            'zope.mgr - students.browser.FacultyExportJobContainerJobConfig '
3904            '- exported: students (2004, 100, ug_ft, fac1, None, None, '
3905            '13/12/2012, 14/12/2012, all, all, all, all), job_id=%s'
3906            % job_id in logcontent
3907            )
3908        self.assertTrue(
3909            'zope.mgr - students.browser.ExportJobContainerDownload '
3910            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3911            % (job_id, job_id) in logcontent
3912            )
3913        self.assertTrue(
3914            'zope.mgr - students.browser.ExportJobContainerOverview '
3915            '- discarded: job_id=%s' % job_id in logcontent
3916            )
3917
3918    def test_department_export(self):
3919        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3920        dep1_path = 'http://localhost/app/faculties/fac1/dep1'
3921        self.browser.open(dep1_path)
3922        self.browser.getLink("Export student data").click()
3923        self.browser.getControl("Configure new export").click()
3924        self.browser.getControl(name="exporter").value = ['students']
3925        self.browser.getControl(name="session").value = ['2004']
3926        self.browser.getControl(name="level").value = ['100']
3927        self.browser.getControl(name="mode").value = ['ug_ft']
3928        # The testbrowser does not hide the payment period fields, but
3929        # values are ignored when using the students exporter.
3930        self.browser.getControl(name="payments_start").value = '13/12/2012'
3931        self.browser.getControl(name="payments_end").value = '14/12/2012'
3932        self.browser.getControl("Create CSV file").click()
3933
3934        # When the job is finished and we reload the page...
3935        job_id = self.wait_for_export_job_completed()
3936        self.browser.open(dep1_path + '/exports')
3937        # ... the csv file can be downloaded ...
3938        self.browser.getLink("Download").click()
3939        self.assertEqual(self.browser.headers['content-type'],
3940            'text/csv; charset=UTF-8')
3941        self.assertTrue(
3942            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3943            self.browser.headers['content-disposition'])
3944        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3945        job_id = self.app['datacenter'].running_exports[0][0]
3946        # ... and discarded
3947        self.browser.open(dep1_path + '/exports')
3948        self.browser.getControl("Discard").click()
3949        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3950        # Creation, downloading and discarding is logged
3951        logfile = os.path.join(
3952            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3953        logcontent = open(logfile).read()
3954        self.assertTrue(
3955            'zope.mgr - students.browser.DepartmentExportJobContainerJobConfig '
3956            '- exported: students (2004, 100, ug_ft, None, dep1, None, '
3957            '13/12/2012, 14/12/2012, all, all, all, all), job_id=%s'
3958            % job_id in logcontent
3959            )
3960        self.assertTrue(
3961            'zope.mgr - students.browser.ExportJobContainerDownload '
3962            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3963            % (job_id, job_id) in logcontent
3964            )
3965        self.assertTrue(
3966            'zope.mgr - students.browser.ExportJobContainerOverview '
3967            '- discarded: job_id=%s' % job_id in logcontent
3968            )
3969
3970    def test_certificate_export(self):
3971        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3972        cert1_path = 'http://localhost/app/faculties/fac1/dep1/certificates/CERT1'
3973        self.browser.open(cert1_path)
3974        self.browser.getLink("Export student data").click()
3975        self.browser.getControl("Configure new export").click()
3976        self.browser.getControl(name="exporter").value = ['students']
3977        self.browser.getControl(name="session").value = ['2004']
3978        self.browser.getControl(name="level").value = ['100']
3979        self.browser.getControl("Create CSV file").click()
3980
3981        # When the job is finished and we reload the page...
3982        job_id = self.wait_for_export_job_completed()
3983        self.browser.open(cert1_path + '/exports')
3984        # ... the csv file can be downloaded ...
3985        self.browser.getLink("Download").click()
3986        self.assertEqual(self.browser.headers['content-type'],
3987            'text/csv; charset=UTF-8')
3988        self.assertTrue(
3989            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3990            self.browser.headers['content-disposition'])
3991        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3992        job_id = self.app['datacenter'].running_exports[0][0]
3993        # ... and discarded
3994        self.browser.open(cert1_path + '/exports')
3995        self.browser.getControl("Discard").click()
3996        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3997        # Creation, downloading and discarding is logged
3998        logfile = os.path.join(
3999            self.app['datacenter'].storage, 'logs', 'datacenter.log')
4000        logcontent = open(logfile).read()
4001        self.assertTrue(
4002            'zope.mgr - students.browser.CertificateExportJobContainerJobConfig '
4003            '- exported: students '
4004            '(2004, 100, None, None, None, CERT1, , , None, None, None, None), '
4005            'job_id=%s'
4006            % job_id in logcontent
4007            )
4008        self.assertTrue(
4009            'zope.mgr - students.browser.ExportJobContainerDownload '
4010            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
4011            % (job_id, job_id) in logcontent
4012            )
4013        self.assertTrue(
4014            'zope.mgr - students.browser.ExportJobContainerOverview '
4015            '- discarded: job_id=%s' % job_id in logcontent
4016            )
4017
4018    def deprecated_test_course_export_students(self):
4019        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
4020        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
4021        self.browser.open(course1_path)
4022        self.browser.getLink("Export student data").click()
4023        self.browser.getControl("Configure new export").click()
4024        self.browser.getControl(name="exporter").value = ['students']
4025        self.browser.getControl(name="session").value = ['2004']
4026        self.browser.getControl(name="level").value = ['100']
4027        self.browser.getControl("Create CSV file").click()
4028
4029        # When the job is finished and we reload the page...
4030        job_id = self.wait_for_export_job_completed()
4031        self.browser.open(course1_path + '/exports')
4032        # ... the csv file can be downloaded ...
4033        self.browser.getLink("Download").click()
4034        self.assertEqual(self.browser.headers['content-type'],
4035            'text/csv; charset=UTF-8')
4036        self.assertTrue(
4037            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
4038            self.browser.headers['content-disposition'])
4039        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
4040        job_id = self.app['datacenter'].running_exports[0][0]
4041        # ... and discarded
4042        self.browser.open(course1_path + '/exports')
4043        self.browser.getControl("Discard").click()
4044        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
4045        # Creation, downloading and discarding is logged
4046        logfile = os.path.join(
4047            self.app['datacenter'].storage, 'logs', 'datacenter.log')
4048        logcontent = open(logfile).read()
4049        self.assertTrue(
4050            'zope.mgr - students.browser.CourseExportJobContainerJobConfig '
4051            '- exported: students (2004, 100, COURSE1), job_id=%s'
4052            % job_id in logcontent
4053            )
4054        self.assertTrue(
4055            'zope.mgr - students.browser.ExportJobContainerDownload '
4056            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
4057            % (job_id, job_id) in logcontent
4058            )
4059        self.assertTrue(
4060            'zope.mgr - students.browser.ExportJobContainerOverview '
4061            '- discarded: job_id=%s' % job_id in logcontent
4062            )
4063
4064    def test_course_export_lecturer(self):
4065        # We add study level 100 to the student's studycourse
4066        studylevel = StudentStudyLevel()
4067        studylevel.level = 100
4068        studylevel.level_session = 2004
4069        IWorkflowState(self.student).setState('courses validated')
4070        self.student['studycourse'].addStudentStudyLevel(
4071            self.certificate,studylevel)
4072        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
4073        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
4074        self.browser.open(course1_path)
4075        self.browser.getLink("Export student data").click()
4076        self.browser.getControl("Configure new export").click()
4077        self.assertTrue(
4078            'Academic session not set. Please contact the administrator.'
4079            in self.browser.contents)
4080        self.app['configuration'].current_academic_session = 2004
4081        self.browser.getControl("Configure new export").click()
4082        self.browser.getControl(name="exporter").value = ['lecturer']
4083        self.browser.getControl(name="session").value = ['2004']
4084        self.browser.getControl(name="level").value = ['100']
4085        self.browser.getControl("Create CSV file").click()
4086        # When the job is finished and we reload the page...
4087        job_id = self.wait_for_export_job_completed()
4088        self.browser.open(course1_path + '/exports')
4089        # ... the csv file can be downloaded ...
4090        self.browser.getLink("Download").click()
4091        self.assertEqual(self.browser.headers['content-type'],
4092            'text/csv; charset=UTF-8')
4093        self.assertTrue(
4094            'filename="WAeUP.Kofa_lecturer_%s.csv' % job_id in
4095            self.browser.headers['content-disposition'])
4096        # ... and contains the course ticket COURSE1
4097        self.assertEqual(self.browser.contents,
4098            'matric_number,student_id,display_fullname,level,code,'
4099            'level_session,score\r\n234,K1000000,Anna Tester,'
4100            '100,COURSE1,2004,\r\n')
4101        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
4102        job_id = self.app['datacenter'].running_exports[0][0]
4103        # Thew job can be discarded
4104        self.browser.open(course1_path + '/exports')
4105        self.browser.getControl("Discard").click()
4106        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
4107        # Creation, downloading and discarding is logged
4108        logfile = os.path.join(
4109            self.app['datacenter'].storage, 'logs', 'datacenter.log')
4110        logcontent = open(logfile).read()
4111        self.assertTrue(
4112            'zope.mgr - students.browser.CourseExportJobContainerJobConfig '
4113            '- exported: lecturer (2004, 100, COURSE1), job_id=%s'
4114            % job_id in logcontent
4115            )
4116        self.assertTrue(
4117            'zope.mgr - students.browser.ExportJobContainerDownload '
4118            '- downloaded: WAeUP.Kofa_lecturer_%s.csv, job_id=%s'
4119            % (job_id, job_id) in logcontent
4120            )
4121        self.assertTrue(
4122            'zope.mgr - students.browser.ExportJobContainerOverview '
4123            '- discarded: job_id=%s' % job_id in logcontent
4124            )
4125
4126    def test_export_departmet_officers(self):
4127        # Create department officer
4128        self.app['users'].addUser('mrdepartment', SECRET)
4129        self.app['users']['mrdepartment'].email = 'mrdepartment@foo.ng'
4130        self.app['users']['mrdepartment'].title = 'Carlo Pitter'
4131        # Assign local role
4132        department = self.app['faculties']['fac1']['dep1']
4133        prmlocal = IPrincipalRoleManager(department)
4134        prmlocal.assignRoleToPrincipal('waeup.local.DepartmentOfficer', 'mrdepartment')
4135        # Login as department officer
4136        self.browser.open(self.login_path)
4137        self.browser.getControl(name="form.login").value = 'mrdepartment'
4138        self.browser.getControl(name="form.password").value = SECRET
4139        self.browser.getControl("Login").click()
4140        self.assertMatches('...You logged in...', self.browser.contents)
4141        self.browser.open("http://localhost/app/faculties/fac1/dep1")
4142        self.browser.getLink("Export student data").click()
4143        self.browser.getControl("Configure new export").click()
4144        # Only the sfpaymentsoverview exporter is available for department officers
4145        self.assertFalse('<option value="students">' in self.browser.contents)
4146        self.assertTrue(
4147            '<option value="sfpaymentsoverview">' in self.browser.contents)
4148        self.browser.getControl(name="exporter").value = ['sfpaymentsoverview']
4149        self.browser.getControl(name="session").value = ['2004']
4150        self.browser.getControl(name="level").value = ['100']
4151        self.browser.getControl("Create CSV file").click()
4152        self.assertTrue('Export started' in self.browser.contents)
4153        # Thew job can be discarded
4154        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
4155        self.wait_for_export_job_completed()
4156        self.browser.open("http://localhost/app/faculties/fac1/dep1/exports")
4157        self.browser.getControl("Discard").click()
4158        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
4159
4160    def test_export_bursary_officers(self):
4161        # Create bursary officer
4162        self.app['users'].addUser('mrbursary', SECRET)
4163        self.app['users']['mrbursary'].email = 'mrbursary@foo.ng'
4164        self.app['users']['mrbursary'].title = 'Carlo Pitter'
4165        prmglobal = IPrincipalRoleManager(self.app)
4166        prmglobal.assignRoleToPrincipal('waeup.BursaryOfficer', 'mrbursary')
4167        # Login as bursary officer
4168        self.browser.open(self.login_path)
4169        self.browser.getControl(name="form.login").value = 'mrbursary'
4170        self.browser.getControl(name="form.password").value = SECRET
4171        self.browser.getControl("Login").click()
4172        self.assertMatches('...You logged in...', self.browser.contents)
4173        self.browser.getLink("Academics").click()
4174        self.browser.getLink("Export student data").click()
4175        self.browser.getControl("Configure new export").click()
4176        # Only the bursary exporter is available for bursary officers
4177        # not only at facultiescontainer level ...
4178        self.assertFalse('<option value="students">' in self.browser.contents)
4179        self.assertTrue('<option value="bursary">' in self.browser.contents)
4180        self.browser.getControl(name="exporter").value = ['bursary']
4181        self.browser.getControl(name="session").value = ['2004']
4182        self.browser.getControl(name="level").value = ['100']
4183        self.browser.getControl("Create CSV file").click()
4184        self.assertTrue('Export started' in self.browser.contents)
4185        # ... but also at other levels
4186        self.browser.open('http://localhost/app/faculties/fac1/dep1')
4187        self.browser.getLink("Export student data").click()
4188        self.browser.getControl("Configure new export").click()
4189        self.assertFalse('<option value="students">' in self.browser.contents)
4190        self.assertTrue('<option value="bursary">' in self.browser.contents)
4191        # Thew job can be discarded
4192        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
4193        self.wait_for_export_job_completed()
4194        self.browser.open('http://localhost/app/faculties/exports')
4195        self.browser.getControl("Discard").click()
4196        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
4197
4198    def test_export_accommodation_officers(self):
4199        # Create bursary officer
4200        self.app['users'].addUser('mracco', SECRET)
4201        self.app['users']['mracco'].email = 'mracco@foo.ng'
4202        self.app['users']['mracco'].title = 'Carlo Pitter'
4203        prmglobal = IPrincipalRoleManager(self.app)
4204        prmglobal.assignRoleToPrincipal('waeup.AccommodationOfficer', 'mracco')
4205        # Login as bursary officer
4206        self.browser.open(self.login_path)
4207        self.browser.getControl(name="form.login").value = 'mracco'
4208        self.browser.getControl(name="form.password").value = SECRET
4209        self.browser.getControl("Login").click()
4210        self.assertMatches('...You logged in...', self.browser.contents)
4211        self.browser.getLink("Academics").click()
4212        self.browser.getLink("Export student data").click()
4213        self.browser.getControl("Configure new export").click()
4214        # accommodationpayments and beds exporters are available
4215        # not only at facultiescontainer level ...
4216        self.assertFalse('<option value="students">' in self.browser.contents)
4217        self.assertTrue('<option value="accommodationpayments">'
4218            in self.browser.contents)
4219        self.assertTrue('<option value="bedtickets">' in self.browser.contents)
4220        self.browser.getControl(
4221            name="exporter").value = ['accommodationpayments']
4222        self.browser.getControl(name="session").value = ['2004']
4223        self.browser.getControl(name="level").value = ['100']
4224        self.browser.getControl("Create CSV file").click()
4225        self.assertTrue('Export started' in self.browser.contents)
4226        # ... but also at other levels
4227        self.browser.open('http://localhost/app/faculties/fac1/dep1')
4228        self.browser.getLink("Export student data").click()
4229        self.browser.getControl("Configure new export").click()
4230        self.assertFalse('<option value="students">' in self.browser.contents)
4231        self.assertTrue('<option value="accommodationpayments">'
4232            in self.browser.contents)
4233        self.assertTrue('<option value="bedtickets">' in self.browser.contents)
4234        # Thew job can be discarded
4235        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
4236        self.wait_for_export_job_completed()
4237        self.browser.open('http://localhost/app/faculties/exports')
4238        self.browser.getControl("Discard").click()
4239        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
4240
4241
4242UPLOAD_CSV_TEMPLATE = (
4243    'matric_number,student_id,display_fullname,level,code,level_session,'
4244    'score\r\n'
4245    '234,K1000000,Anna Tester,100,COURSE1,2004,%s\r\n')
4246
4247class LecturerUITests(StudentsFullSetup):
4248    # Tests for UI actions when acting as lecturer.
4249
4250    def login_as_lecturer(self):
4251        self.app['users'].addUser('mrslecturer', SECRET)
4252        self.app['users']['mrslecturer'].email = 'mrslecturer@foo.ng'
4253        self.app['users']['mrslecturer'].title = u'Mercedes Benz'
4254        # Add course ticket
4255        studylevel = createObject(u'waeup.StudentStudyLevel')
4256        studylevel.level = 100
4257        studylevel.level_session = 2004
4258        self.student['studycourse'].addStudentStudyLevel(
4259            self.certificate, studylevel)
4260        # Assign local Lecturer role for a course.
4261        course = self.app['faculties']['fac1']['dep1'].courses['COURSE1']
4262        prmlocal = IPrincipalRoleManager(course)
4263        prmlocal.assignRoleToPrincipal('waeup.local.Lecturer', 'mrslecturer')
4264        notify(LocalRoleSetEvent(
4265            course, 'waeup.local.Lecturer', 'mrslecturer', granted=True))
4266        # Login as lecturer.
4267        self.browser.open(self.login_path)
4268        self.browser.getControl(name="form.login").value = 'mrslecturer'
4269        self.browser.getControl(
4270            name="form.password").value = SECRET
4271        self.browser.getControl("Login").click()
4272        # Store reused urls/paths
4273        self.course_url = (
4274            'http://localhost/app/faculties/fac1/dep1/courses/COURSE1')
4275        self.edit_scores_url = '%s/edit_scores' % self.course_url
4276        # Set standard parameters
4277        self.app['configuration'].current_academic_session = 2004
4278        self.app['faculties']['fac1']['dep1'].score_editing_disabled = False
4279        IWorkflowState(self.student).setState(VALIDATED)
4280
4281    @property
4282    def stud_log_path(self):
4283        return os.path.join(
4284            self.app['datacenter'].storage, 'logs', 'students.log')
4285
4286    def test_lecturer_lands_on_landing_page(self):
4287        # lecturers can login and will be led to landing page.
4288        self.login_as_lecturer()
4289        self.assertMatches('...You logged in...', self.browser.contents)
4290        self.assertEqual(self.browser.url, URL_LECTURER_LANDING)
4291        self.assertTrue(
4292            "<span>Unnamed Course (COURSE1)</span>"
4293            in self.browser.contents)
4294
4295    def test_lecturer_department_role(self):
4296        # lecturers can login and will be led to landing page also if
4297        # role is assigned at department level.
4298        self.login_as_lecturer()
4299        # we remove the role granted above
4300        course = self.app['faculties']['fac1']['dep1'].courses['COURSE1']
4301        prmlocal = IPrincipalRoleManager(course)
4302        prmlocal.removeRoleFromPrincipal('waeup.local.Lecturer', 'mrslecturer')
4303        notify(LocalRoleSetEvent(
4304            course, 'waeup.local.Lecturer', 'mrslecturer', granted=False))
4305        self.browser.open(URL_LECTURER_LANDING)
4306        # no course appears
4307        self.assertFalse(
4308            "<span>Unnamed Course (COURSE1)</span>"
4309            in self.browser.contents)
4310        # we assign lecturer at department level
4311        dep = self.app['faculties']['fac1']['dep1']
4312        prmlocal = IPrincipalRoleManager(dep)
4313        prmlocal.assignRoleToPrincipal('waeup.local.Lecturer', 'mrslecturer')
4314        notify(LocalRoleSetEvent(
4315            dep, 'waeup.local.Lecturer', 'mrslecturer', granted=True))
4316        self.browser.open(URL_LECTURER_LANDING)
4317        # course appears again
4318        self.assertTrue(
4319            "<span>Unnamed Course (COURSE1)</span>"
4320            in self.browser.contents)
4321
4322    def test_my_roles_link_works(self):
4323        # lecturers can see their roles
4324        self.login_as_lecturer()
4325        self.browser.getLink("My Roles").click()
4326        self.assertTrue(
4327            "<div>Academics Officer (view only)</div>"
4328            in self.browser.contents)
4329        self.assertTrue(
4330            '<a href="%s">' % self.course_url in self.browser.contents)
4331
4332    def test_my_roles_page_contains_backlink(self):
4333        # we can get back from 'My Roles' view to landing page
4334        self.login_as_lecturer()
4335        self.browser.getLink("My Roles").click()
4336        self.browser.getLink("My Courses").click()
4337        self.assertEqual(self.browser.url, URL_LECTURER_LANDING)
4338
4339    def test_lecturers_can_reach_their_courses(self):
4340        # lecturers get links to their courses on the landing page
4341        self.login_as_lecturer()
4342        self.browser.getLink("COURSE1").click()
4343        self.assertEqual(self.browser.url, self.course_url)
4344
4345    def test_lecturers_student_access_is_restricted(self):
4346        # lecturers are not able to change other student data
4347        self.login_as_lecturer()
4348        # Lecturers can neither filter students ...
4349        self.assertRaises(
4350            Unauthorized, self.browser.open, '%s/students' % self.course_url)
4351        # ... nor access the student ...
4352        self.assertRaises(
4353            Unauthorized, self.browser.open, self.student_path)
4354        # ... nor the respective course ticket since editing course
4355        # tickets by lecturers is not feasible.
4356        self.assertTrue('COURSE1' in self.student['studycourse']['100'].keys())
4357        course_ticket_path = self.student_path + '/studycourse/100/COURSE1'
4358        self.assertRaises(
4359            Unauthorized, self.browser.open, course_ticket_path)
4360
4361    def test_score_editing_requires_department_permit(self):
4362        # we get a warning if we try to update score while we are not allowed
4363        self.login_as_lecturer()
4364        self.app['faculties']['fac1']['dep1'].score_editing_disabled = True
4365        self.browser.open(self.course_url)
4366        self.browser.getLink("Update session 2004/2005 scores").click()
4367        self.assertTrue('Score editing disabled' in self.browser.contents)
4368        self.app['faculties']['fac1']['dep1'].score_editing_disabled = False
4369        self.browser.open(self.course_url)
4370        self.browser.getLink("Update session 2004/2005 scores").click()
4371        self.assertFalse('Score editing disabled' in self.browser.contents)
4372
4373    def test_score_editing_requires_validated_students(self):
4374        # we can edit only scores of students whose courses have been
4375        # validated.
4376        self.login_as_lecturer()
4377        # set invalid student state
4378        IWorkflowState(self.student).setState(CREATED)
4379        self.browser.open(self.edit_scores_url)
4380        self.assertRaises(
4381            LookupError, self.browser.getControl, name="scores")
4382        # set valid student state
4383        IWorkflowState(self.student).setState(VALIDATED)
4384        self.browser.open(self.edit_scores_url)
4385        self.assertTrue(
4386            self.browser.getControl(name="scores:list") is not None)
4387
4388    def test_score_editing_offers_only_current_scores(self):
4389        # only scores from current academic session can be edited
4390        self.login_as_lecturer()
4391        IWorkflowState(self.student).setState('courses validated')
4392        # with no academic session set
4393        self.app['configuration'].current_academic_session = None
4394        self.browser.open(self.edit_scores_url)
4395        self.assertRaises(
4396            LookupError, self.browser.getControl, name="scores")
4397        # with wrong academic session set
4398        self.app['configuration'].current_academic_session = 1999
4399        self.browser.open(self.edit_scores_url)
4400        self.assertRaises(
4401            LookupError, self.browser.getControl, name="scores")
4402        # with right academic session set
4403        self.app['configuration'].current_academic_session = 2004
4404        self.browser.reload()
4405        self.assertTrue(
4406            self.browser.getControl(name="scores:list") is not None)
4407
4408    def test_score_editing_can_change_scores(self):
4409        # we can really change scores via edit_scores view
4410        self.login_as_lecturer()
4411        self.assertEqual(
4412            self.student['studycourse']['100']['COURSE1'].score, None)
4413        self.browser.open(self.edit_scores_url)
4414        self.browser.getControl(name="scores:list", index=0).value = '55'
4415        self.browser.getControl("Update scores").click()
4416        # the new value is stored in data
4417        self.assertEqual(
4418            self.student['studycourse']['100']['COURSE1'].score, 55)
4419        # the new value is displayed on page/prefilled in form
4420        self.assertEqual(
4421            self.browser.getControl(name="scores:list", index=0).value, '55')
4422        # The change has been logged
4423        with open(self.stud_log_path, 'r') as fd:
4424            self.assertTrue(
4425                'mrslecturer - students.browser.EditScoresPage - '
4426                'K1000000 100/COURSE1 score updated (55)' in fd.read())
4427
4428    def test_scores_editing_scores_must_be_integers(self):
4429        # Non-integer scores won't be accepted.
4430        self.login_as_lecturer()
4431        self.browser.open(self.edit_scores_url)
4432        self.browser.getControl(name="scores:list", index=0).value = 'abc'
4433        self.browser.getControl("Update scores").click()
4434        self.assertTrue(
4435            'Error: Score(s) of following students have not been updated '
4436            '(only integers are allowed): Anna Tester.'
4437            in self.browser.contents)
4438
4439    def test_scores_editing_allows_score_removal(self):
4440        # we can remove scores, once they were set
4441        self.login_as_lecturer()
4442        # without a prior value, we cannot remove
4443        self.student['studycourse']['100']['COURSE1'].score = None
4444        self.browser.open(self.edit_scores_url)
4445        self.browser.getControl(name="scores:list", index=0).value = ''
4446        self.browser.getControl("Update scores").click()
4447        logcontent = open(self.stud_log_path, 'r').read()
4448        self.assertFalse('COURSE1 score updated (None)' in logcontent)
4449        # now retry with some value set
4450        self.student['studycourse']['100']['COURSE1'].score = 55
4451        self.browser.getControl(name="scores:list", index=0).value = ''
4452        self.browser.getControl("Update scores").click()
4453        logcontent = open(self.stud_log_path, 'r').read()
4454        self.assertTrue('COURSE1 score updated (None)' in logcontent)
4455
4456    def test_lecturers_can_download_course_tickets(self):
4457        # A course ticket slip can be downloaded
4458        self.course.title = (u'Lorem ipsum     dolor sit amet, consectetur     adipisici, '
4459                             u'sed         eiusmod tempor    incidunt ut  labore et dolore')
4460        self.login_as_lecturer()
4461        pdf_url = '%s/coursetickets.pdf' % self.course_url
4462        self.browser.open(pdf_url)
4463        self.assertEqual(self.browser.headers['Status'], '200 Ok')
4464        self.assertEqual(
4465            self.browser.headers['Content-Type'], 'application/pdf')
4466        path = os.path.join(samples_dir(), 'coursetickets.pdf')
4467        open(path, 'wb').write(self.browser.contents)
4468        print "Sample PDF coursetickets.pdf written to %s" % path
4469
4470    def test_lecturers_can_download_scores_as_csv(self):
4471        # Lecturers can download course scores as CSV.
4472        self.login_as_lecturer()
4473        self.browser.open(self.edit_scores_url)
4474        self.browser.getLink("Download csv file").click()
4475        self.assertEqual(self.browser.headers['Status'], '200 Ok')
4476        self.assertEqual(self.browser.headers['Content-Type'],
4477                         'text/csv; charset=UTF-8')
4478        self.assertEqual(self.browser.contents, 'matric_number,student_id,'
4479            'display_fullname,level,code,level_session,score\r\n234,'
4480            'K1000000,Anna Tester,100,COURSE1,2004,\r\n')
4481
4482    def test_scores_csv_upload_available(self):
4483        # lecturers can upload a CSV file to set values.
4484        self.login_as_lecturer()
4485        # set value to change from
4486        self.student['studycourse']['100']['COURSE1'].score = 55
4487        self.browser.open(self.edit_scores_url)
4488        upload_ctrl = self.browser.getControl(name='uploadfile:file')
4489        upload_file = StringIO(UPLOAD_CSV_TEMPLATE % '65')
4490        upload_ctrl.add_file(upload_file, 'text/csv', 'myscores.csv')
4491        self.browser.getControl("Update editable scores from").click()
4492        # value changed
4493        self.assertEqual(
4494            self.student['studycourse']['100']['COURSE1'].score, 65)
4495
4496    def test_scores_csv_upload_ignored(self):
4497        # for many type of file contents we simply ignore uploaded data
4498        self.login_as_lecturer()
4499        self.student['studycourse']['100']['COURSE1'].score = 55
4500        self.browser.open(self.edit_scores_url)
4501        for content, mimetype, name in (
4502                # empty file
4503                ('', 'text/foo', 'my.foo'),
4504                # plain ASCII text, w/o comma
4505                ('abcdef' * 200, 'text/plain', 'my.txt'),
4506                # plain UTF-8 text, with umlauts
4507                ('umlauts: äöü', 'text/plain', 'my.txt'),
4508                # csv file with only a header row
4509                ('student_id,score', 'text/csv', 'my.csv'),
4510                # csv with student_id column missing
4511                ('foo,score\r\nbar,66\r\n', 'text/csv', 'my.csv'),
4512                # csv with score column missing
4513                ('student_id,foo\r\nK1000000,bar\r\n', 'text/csv', 'my.csv'),
4514                # csv with non number as score value
4515                (UPLOAD_CSV_TEMPLATE % 'not-a-number', 'text/csv', 'my.csv'),
4516                ):
4517            upload_ctrl = self.browser.getControl(name='uploadfile:file')
4518            upload_ctrl.add_file(StringIO(content), mimetype, name)
4519            self.browser.getControl("Update scores").click()
4520            self.assertEqual(
4521                self.student['studycourse']['100']['COURSE1'].score, 55)
4522            self.assertFalse(
4523                'Uploaded file contains illegal data' in self.browser.contents)
4524
4525    def test_scores_csv_upload_warn_illegal_chars(self):
4526        # for some types of files we issue a warning if upload data
4527        # contains illegal chars (and ignore the data)
4528        self.login_as_lecturer()
4529        self.student['studycourse']['100']['COURSE1'].score = 55
4530        self.browser.open(self.edit_scores_url)
4531        for content, mimetype, name in (
4532                # plain ASCII text, commas, control chars
4533                ('abv,qwe\n\r\r\t\b\n' * 20, 'text/plain', 'my.txt'),
4534                # image data (like a JPEG image)
4535                (open(SAMPLE_IMAGE, 'rb').read(), 'image/jpg', 'my.jpg'),
4536                ):
4537            upload_ctrl = self.browser.getControl(name='uploadfile:file')
4538            upload_ctrl.add_file(StringIO(content), mimetype, name)
4539            self.browser.getControl("Update editable scores").click()
4540            self.assertEqual(
4541                self.student['studycourse']['100']['COURSE1'].score, 55)
4542            self.assertTrue(
4543                'Uploaded file contains illegal data' in self.browser.contents)
Note: See TracBrowser for help on using the repository browser.