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

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

Add ReleaseExpiredAllocationsActionButton? which has previously only been used by Uniben.

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