source: main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/tests/test_browser.py

Last change on this file was 15162, checked in by Henrik Bettermann, 6 years ago

Remove manageStudent permission.
Show tables on landing pages.
Adjust tests.

  • Property svn:keywords set to Id
File size: 227.3 KB
Line 
1# -*- coding: utf-8 -*-
2## $Id: test_browser.py 15162 2018-09-21 09:32:42Z 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            '...: Study Level 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            '...: Study Level 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        # After validation all manage forms are locked.
2182        self.browser.open(self.studycourse_path + '/manage')
2183        self.assertTrue('The requested form is locked' in self.browser.contents)
2184        self.assertFalse('Undergraduate Full-Time</option>'
2185            in self.browser.contents)
2186        self.browser.open(self.studycourse_path + '/100/manage')
2187        self.assertTrue('The requested form is locked' in self.browser.contents)
2188        self.browser.open(self.studycourse_path + '/100/COURSE1/manage')
2189        self.assertTrue('The requested form is locked' in self.browser.contents)
2190
2191        # Transcript can be signed if officer has the permission to sign
2192        #self.browser.open(self.studycourse_path + '/transcript')
2193        #self.assertFalse('Sign transcript' in self.browser.contents)
2194        #prmglobal = IPrincipalRoleManager(self.app)
2195        #prmglobal.assignRoleToPrincipal('waeup.TranscriptSignee', 'mrtranscript')
2196
2197        self.browser.open(self.studycourse_path + '/transcript')
2198        self.browser.getLink("Sign transcript electronically").click()
2199        # Transcript signing has been logged ...
2200        logfile = os.path.join(
2201            self.app['datacenter'].storage, 'logs', 'students.log')
2202        logcontent = open(logfile).read()
2203        self.assertTrue(
2204            'mrtranscript - students.browser.StudentTranscriptSignView - '
2205            'K1000000 - Transcript signed' in logcontent)
2206        # ... appears in the student's history ...
2207        self.browser.open(self.history_path)
2208        self.assertTrue('Transcript signed by Ruth Gordon'
2209            in self.browser.contents)
2210        # ... and is also stored in the transcript_signee attribute.
2211        self.assertTrue(
2212            u'Electronically signed by Ruth Gordon (mrtranscript) on '
2213            in self.student['studycourse'].transcript_signees)
2214        # Officer can release the transcript
2215        self.browser.open(self.studycourse_path + '/transcript')
2216        self.browser.getLink("Release transcript").click()
2217        self.assertTrue(' UTC K1000000 wrote:<br><br>Comment line 1 <br>'
2218        'Comment line2<br><br>Dispatch Address:<br>Address line 1 <br>'
2219        'Address line2<br><br></p>' in self.browser.contents)
2220        self.browser.getControl(name="comment").value = (
2221            'Hello,\nYour transcript has been sent to the address provided.')
2222        self.browser.getControl("Save comment and release transcript").click()
2223        self.assertTrue(
2224            'UTC mrtranscript wrote:\n\nHello,\nYour transcript has '
2225            'been sent to the address provided.\n\n'
2226            in self.student['studycourse'].transcript_comment)
2227        # The comment has been logged
2228        logfile = os.path.join(
2229            self.app['datacenter'].storage, 'logs', 'students.log')
2230        logcontent = open(logfile).read()
2231        self.assertTrue(
2232            'mrtranscript - students.browser.StudentTranscriptReleaseFormPage - '
2233            'K1000000 - comment: Hello,<br>'
2234            'Your transcript has been sent to the address provided'
2235            in logcontent)
2236        # File has been stored in the file system
2237        # Check if transcript exists in the file system and is a PDF file
2238        storage = getUtility(IExtFileStore)
2239        file_id = IFileStoreNameChooser(
2240            self.student).chooseName(attr='final_transcript.pdf')
2241        pdf = storage.getFile(file_id).read()
2242        self.assertTrue(len(pdf) > 0)
2243        self.assertEqual(pdf[:8], '%PDF-1.4')
2244        # Copy the file to samples_dir
2245        path = os.path.join(samples_dir(), 'final_transcript.pdf')
2246        open(path, 'wb').write(pdf)
2247        print "Sample PDF final_transcript.pdf written to %s" % path
2248        # Check if there is an transcript pdf link in UI
2249        self.browser.open(self.student_path)
2250        self.assertTrue('Final Transcript' in self.browser.contents)
2251        self.browser.getLink("Final Transcript").click()
2252        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2253        self.assertEqual(self.browser.headers['Content-Type'],
2254                         'application/pdf')
2255        # Transcript views are no longer accessible
2256        self.browser.open(self.studycourse_path)
2257        self.assertFalse('studycourse/transcript' in self.browser.contents)
2258        self.browser.open(self.studycourse_path + '/transcript')
2259        self.assertTrue('Forbidden!' in self.browser.contents)
2260        self.browser.open(self.studycourse_path + '/transcript.pdf')
2261        self.assertTrue('Forbidden!' in self.browser.contents)
2262        # If we reset the transcript process
2263        # (can't be done by transcript officer), the file will be deleted
2264        IWorkflowInfo(self.student).fireTransition('reset11')
2265        self.browser.open(self.student_path)
2266        self.assertFalse('Final Transcript' in self.browser.contents)
2267        # ... and transcript process information has been removed
2268        self.assertEqual(self.student['studycourse'].transcript_comment, None)
2269        self.assertEqual(self.student['studycourse'].transcript_signees, None)
2270
2271    def test_landingpage_transcript_officer(self):
2272        IWorkflowState(self.student).setState('transcript requested')
2273        notify(grok.ObjectModifiedEvent(self.student))
2274        # Create transcript officer
2275        self.app['users'].addUser('mrtranscript', 'mrtranscriptsecret')
2276        self.app['users']['mrtranscript'].email = 'mrtranscript@foo.ng'
2277        self.app['users']['mrtranscript'].title = 'Ruth Gordon'
2278        # We assign transcript officer role at faculty level
2279        fac = self.app['faculties']['fac1']
2280        prmlocal = IPrincipalRoleManager(fac)
2281        prmlocal.assignRoleToPrincipal(
2282            'waeup.local.TranscriptOfficer', 'mrtranscript')
2283        notify(LocalRoleSetEvent(
2284            fac, 'waeup.local.TranscriptOfficer', 'mrtranscript', granted=True))
2285        # Login as transcript officer
2286        self.browser.open(self.login_path)
2287        self.browser.getControl(name="form.login").value = 'mrtranscript'
2288        self.browser.getControl(name="form.password").value = 'mrtranscriptsecret'
2289        self.browser.getControl("Login").click()
2290        self.assertMatches('...You logged in...', self.browser.contents)
2291        # Officer is on landing page and does see the transcript link
2292        self.assertTrue(
2293            'http://localhost/app/students/K1000000/studycourse/transcript'
2294            in self.browser.contents)
2295        self.browser.getLink("K1000000").click()
2296        self.assertTrue(
2297            'Anna Tester: Transcript Data' in self.browser.contents)
2298        # Officer is on transcript page and can validate the transcript
2299        self.browser.getLink("Validate transcript").click()
2300        self.assertTrue(
2301            '<div class="alert alert-success">Transcript validated.</div>'
2302            in self.browser.contents)
2303        # Officer is still on transcript page and can release the transcript
2304        self.browser.getLink("Release transcript").click()
2305        self.browser.getControl("Save comment and release transcript").click()
2306        self.assertTrue(
2307            '<div class="alert alert-success">'
2308            'Transcript released and final transcript file saved.</div>'
2309            in self.browser.contents)
2310
2311    def test_landingpage_transcript_signee(self):
2312        IWorkflowState(self.student).setState('transcript validated')
2313        notify(grok.ObjectModifiedEvent(self.student))
2314        # Create transcript signee
2315        self.app['users'].addUser('mrtranscript', 'mrtranscriptsecret')
2316        self.app['users']['mrtranscript'].email = 'mrtranscript@foo.ng'
2317        self.app['users']['mrtranscript'].title = 'Ruth Gordon'
2318        # We assign transcript officer role at faculty level
2319        fac = self.app['faculties']['fac1']
2320        prmlocal = IPrincipalRoleManager(fac)
2321        prmlocal.assignRoleToPrincipal(
2322            'waeup.local.TranscriptSignee', 'mrtranscript')
2323        notify(LocalRoleSetEvent(
2324            fac, 'waeup.local.TranscriptSignee', 'mrtranscript', granted=True))
2325        # Login as transcript officer
2326        self.browser.open(self.login_path)
2327        self.browser.getControl(name="form.login").value = 'mrtranscript'
2328        self.browser.getControl(name="form.password").value = 'mrtranscriptsecret'
2329        self.browser.getControl("Login").click()
2330        self.assertMatches('...You logged in...', self.browser.contents)
2331        # Officer is on landing page and does see the transcript link
2332        self.assertTrue(
2333            'http://localhost/app/students/K1000000/studycourse/transcript'
2334            in self.browser.contents)
2335        self.browser.getLink("K1000000").click()
2336        self.assertTrue(
2337            'Anna Tester: Transcript Data' in self.browser.contents)
2338        # Officer is on transcript page and can sign the transcript
2339        self.browser.getLink("Sign transcript").click()
2340        self.assertTrue(
2341            '<div class="alert alert-success">Transcript signed.</div>'
2342            in self.browser.contents)
2343        # Officer is still on transcript page
2344        self.assertTrue(
2345            'Anna Tester: Transcript Data' in self.browser.contents)
2346        # Officer can sign the transcript only once
2347        self.browser.getLink("Sign transcript").click()
2348        self.assertTrue(
2349            '<div class="alert alert-warning">'
2350            'You have already signed this transcript.</div>'
2351            in self.browser.contents)
2352        # Signature can be seen on transcript page
2353        self.assertTrue(
2354            'Electronically signed by Ruth Gordon (mrtranscript) on'
2355            in self.browser.contents)
2356
2357
2358class StudentUITests(StudentsFullSetup):
2359    # Tests for Student class views and pages
2360
2361    def test_student_change_password(self):
2362        # Students can change the password
2363        self.student.personal_updated = datetime.utcnow()
2364        self.browser.open(self.login_path)
2365        self.browser.getControl(name="form.login").value = self.student_id
2366        self.browser.getControl(name="form.password").value = 'spwd'
2367        self.browser.getControl("Login").click()
2368        self.assertEqual(self.browser.url, self.student_path)
2369        self.assertTrue('You logged in' in self.browser.contents)
2370        # Change password
2371        self.browser.getLink("Change password").click()
2372        self.browser.getControl(name="change_password").value = 'pw'
2373        self.browser.getControl(
2374            name="change_password_repeat").value = 'pw'
2375        self.browser.getControl("Save").click()
2376        self.assertTrue('Password must have at least' in self.browser.contents)
2377        self.browser.getControl(name="change_password").value = 'new_password'
2378        self.browser.getControl(
2379            name="change_password_repeat").value = 'new_passssword'
2380        self.browser.getControl("Save").click()
2381        self.assertTrue('Passwords do not match' in self.browser.contents)
2382        self.browser.getControl(name="change_password").value = 'new_password'
2383        self.browser.getControl(
2384            name="change_password_repeat").value = 'new_password'
2385        self.browser.getControl("Save").click()
2386        self.assertTrue('Password changed' in self.browser.contents)
2387        # We are still logged in. Changing the password hasn't thrown us out.
2388        self.browser.getLink("Base Data").click()
2389        self.assertEqual(self.browser.url, self.student_path)
2390        # We can logout
2391        self.browser.getLink("Logout").click()
2392        self.assertTrue('You have been logged out' in self.browser.contents)
2393        self.assertEqual(self.browser.url, 'http://localhost/app/index')
2394        # We can login again with the new password
2395        self.browser.getLink("Login").click()
2396        self.browser.open(self.login_path)
2397        self.browser.getControl(name="form.login").value = self.student_id
2398        self.browser.getControl(name="form.password").value = 'new_password'
2399        self.browser.getControl("Login").click()
2400        self.assertEqual(self.browser.url, self.student_path)
2401        self.assertTrue('You logged in' in self.browser.contents)
2402        return
2403
2404    def test_forbidden_name(self):
2405        self.student.lastname = u'<TAG>Tester</TAG>'
2406        self.browser.open(self.login_path)
2407        self.browser.getControl(name="form.login").value = self.student_id
2408        self.browser.getControl(name="form.password").value = 'spwd'
2409        self.browser.getControl("Login").click()
2410        self.assertTrue('XXX: Base Data' in self.browser.contents)
2411        self.assertTrue('&lt;TAG&gt;Tester&lt;/TAG&gt;' in self.browser.contents)
2412        self.assertFalse('<TAG>Tester</TAG>' in self.browser.contents)
2413        return
2414
2415    def test_setpassword(self):
2416        # Set password for first-time access
2417        student = Student()
2418        student.reg_number = u'123456'
2419        student.firstname = u'Klaus'
2420        student.lastname = u'Tester'
2421        self.app['students'].addStudent(student)
2422        setpassword_path = 'http://localhost/app/setpassword'
2423        student_path = 'http://localhost/app/students/%s' % student.student_id
2424        self.browser.open(setpassword_path)
2425        self.browser.getControl(name="ac_series").value = self.existing_pwdseries
2426        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
2427        self.browser.getControl(name="reg_number").value = '223456'
2428        self.browser.getControl("Set").click()
2429        self.assertMatches('...No student found...',
2430                           self.browser.contents)
2431        self.browser.getControl(name="reg_number").value = '123456'
2432        self.browser.getControl(name="ac_number").value = '999999'
2433        self.browser.getControl("Set").click()
2434        self.assertMatches('...Access code is invalid...',
2435                           self.browser.contents)
2436        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
2437        self.browser.getControl("Set").click()
2438        self.assertMatches('...Password has been set. Your Student Id is...',
2439                           self.browser.contents)
2440        self.browser.getControl("Set").click()
2441        self.assertMatches(
2442            '...Password has already been set. Your Student Id is...',
2443            self.browser.contents)
2444        existing_pwdpin = self.pwdpins[1]
2445        parts = existing_pwdpin.split('-')[1:]
2446        existing_pwdseries, existing_pwdnumber = parts
2447        self.browser.getControl(name="ac_series").value = existing_pwdseries
2448        self.browser.getControl(name="ac_number").value = existing_pwdnumber
2449        self.browser.getControl(name="reg_number").value = '123456'
2450        self.browser.getControl("Set").click()
2451        self.assertMatches(
2452            '...You are using the wrong Access Code...',
2453            self.browser.contents)
2454        # The student can login with the new credentials
2455        self.browser.open(self.login_path)
2456        self.browser.getControl(name="form.login").value = student.student_id
2457        self.browser.getControl(
2458            name="form.password").value = self.existing_pwdnumber
2459        self.browser.getControl("Login").click()
2460        self.assertEqual(self.browser.url, student_path)
2461        self.assertTrue('You logged in' in self.browser.contents)
2462        return
2463
2464    def test_student_login(self):
2465        # Student cant login if their password is not set
2466        self.student.password = None
2467        self.browser.open(self.login_path)
2468        self.browser.getControl(name="form.login").value = self.student_id
2469        self.browser.getControl(name="form.password").value = 'spwd'
2470        self.browser.getControl("Login").click()
2471        self.assertTrue(
2472            'You entered invalid credentials.' in self.browser.contents)
2473        # We set the password again
2474        IUserAccount(
2475            self.app['students'][self.student_id]).setPassword('spwd')
2476        # Students can't login if their account is suspended/deactivated
2477        self.student.suspended = True
2478        self.browser.open(self.login_path)
2479        self.browser.getControl(name="form.login").value = self.student_id
2480        self.browser.getControl(name="form.password").value = 'spwd'
2481        self.browser.getControl("Login").click()
2482        self.assertMatches(
2483            '...<div class="alert alert-warning">'
2484            'Your account has been deactivated.</div>...', self.browser.contents)
2485        # If suspended_comment is set this message will be flashed instead
2486        self.student.suspended_comment = u'Aetsch baetsch!'
2487        self.browser.getControl(name="form.login").value = self.student_id
2488        self.browser.getControl(name="form.password").value = 'spwd'
2489        self.browser.getControl("Login").click()
2490        self.assertMatches(
2491            '...<div class="alert alert-warning">Aetsch baetsch!</div>...',
2492            self.browser.contents)
2493        self.student.suspended = False
2494        # Students can't login if a temporary password has been set and
2495        # is not expired
2496        self.app['students'][self.student_id].setTempPassword(
2497            'anybody', 'temp_spwd')
2498        self.browser.open(self.login_path)
2499        self.browser.getControl(name="form.login").value = self.student_id
2500        self.browser.getControl(name="form.password").value = 'spwd'
2501        self.browser.getControl("Login").click()
2502        self.assertMatches(
2503            '...Your account has been temporarily deactivated...',
2504            self.browser.contents)
2505        # The student can login with the temporary password
2506        self.browser.open(self.login_path)
2507        self.browser.getControl(name="form.login").value = self.student_id
2508        self.browser.getControl(name="form.password").value = 'temp_spwd'
2509        self.browser.getControl("Login").click()
2510        self.assertMatches(
2511            '...You logged in...', self.browser.contents)
2512        # Student can view the base data
2513        self.browser.open(self.student_path)
2514        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2515        self.assertEqual(self.browser.url, self.student_path)
2516        # When the password expires ...
2517        delta = timedelta(minutes=11)
2518        self.app['students'][self.student_id].temp_password[
2519            'timestamp'] = datetime.utcnow() - delta
2520        self.app['students'][self.student_id]._p_changed = True
2521        # ... the student will be automatically logged out
2522        self.assertRaises(
2523            Unauthorized, self.browser.open, self.student_path)
2524        # Then the student can login with the original password
2525        self.browser.open(self.login_path)
2526        self.browser.getControl(name="form.login").value = self.student_id
2527        self.browser.getControl(name="form.password").value = 'spwd'
2528        self.browser.getControl("Login").click()
2529        self.assertMatches(
2530            '...You logged in...', self.browser.contents)
2531
2532    def test_maintenance_mode(self):
2533        config = grok.getSite()['configuration']
2534        self.browser.open(self.login_path)
2535        self.browser.getControl(name="form.login").value = self.student_id
2536        self.browser.getControl(name="form.password").value = 'spwd'
2537        self.browser.getControl("Login").click()
2538        # Student logged in.
2539        self.assertTrue('You logged in' in self.browser.contents)
2540        self.assertTrue("Anna Tester" in self.browser.contents)
2541        # If maintenance mode is enabled, student is immediately logged out.
2542        config.maintmode_enabled_by = u'any_user'
2543        self.assertRaises(
2544            Unauthorized, self.browser.open, 'http://localhost/app/faculties')
2545        self.browser.open('http://localhost/app/login')
2546        self.assertTrue('The portal is in maintenance mode' in self.browser.contents)
2547        # Student really can't login if maintenance mode is enabled.
2548        self.browser.open(self.login_path)
2549        self.browser.getControl(name="form.login").value = self.student_id
2550        self.browser.getControl(name="form.password").value = 'spwd'
2551        self.browser.getControl("Login").click()
2552        # A second warning is raised.
2553        self.assertTrue(
2554            'The portal is in maintenance mode. You can\'t login!'
2555            in self.browser.contents)
2556        return
2557
2558    def test_student_clearance(self):
2559        # Student cant login if their password is not set
2560        IWorkflowInfo(self.student).fireTransition('admit')
2561        self.browser.open(self.login_path)
2562        self.browser.getControl(name="form.login").value = self.student_id
2563        self.browser.getControl(name="form.password").value = 'spwd'
2564        self.browser.getControl("Login").click()
2565        self.assertMatches(
2566            '...You logged in...', self.browser.contents)
2567        # Admitted student can upload a passport picture
2568        self.browser.open(self.student_path + '/change_portrait')
2569        ctrl = self.browser.getControl(name='passportuploadedit')
2570        file_obj = open(SAMPLE_IMAGE, 'rb')
2571        file_ctrl = ctrl.mech_control
2572        file_ctrl.add_file(file_obj, filename='my_photo.jpg')
2573        self.browser.getControl(
2574            name='upload_passportuploadedit').click()
2575        self.assertTrue(
2576            'src="http://localhost/app/students/K1000000/passport.jpg"'
2577            in self.browser.contents)
2578        # Students can open admission letter
2579        self.browser.getLink("Base Data").click()
2580        self.browser.getLink("Download admission letter").click()
2581        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2582        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2583        # Student can view the clearance data
2584        self.browser.open(self.student_path)
2585        self.browser.getLink("Clearance Data").click()
2586        # Student can't open clearance edit form before starting clearance
2587        self.browser.open(self.student_path + '/cedit')
2588        self.assertMatches('...The requested form is locked...',
2589                           self.browser.contents)
2590        self.browser.getLink("Clearance Data").click()
2591        self.browser.getLink("Start clearance").click()
2592        self.student.phone = None
2593        # Uups, we forgot to fill the phone fields
2594        self.browser.getControl("Start clearance").click()
2595        self.assertMatches('...Phone number is missing...',
2596                           self.browser.contents)
2597        self.browser.open(self.student_path + '/edit_base')
2598        self.browser.getControl(name="form.phone.ext").value = '12345'
2599        self.browser.getControl("Save").click()
2600        self.browser.open(self.student_path + '/start_clearance')
2601        self.browser.getControl(name="ac_series").value = '3'
2602        self.browser.getControl(name="ac_number").value = '4444444'
2603        self.browser.getControl("Start clearance now").click()
2604        self.assertMatches('...Activation code is invalid...',
2605                           self.browser.contents)
2606        self.browser.getControl(name="ac_series").value = self.existing_clrseries
2607        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
2608        # Owner is Hans Wurst, AC can't be invalidated
2609        self.browser.getControl("Start clearance now").click()
2610        self.assertMatches('...You are not the owner of this access code...',
2611                           self.browser.contents)
2612        # Set the correct owner
2613        self.existing_clrac.owner = self.student_id
2614        # clr_code might be set (and thus returns None) due importing
2615        # an empty clr_code column.
2616        self.student.clr_code = None
2617        self.browser.getControl("Start clearance now").click()
2618        self.assertMatches('...Clearance process has been started...',
2619                           self.browser.contents)
2620        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
2621        self.browser.getControl("Save", index=0).click()
2622        # Student can view the clearance data
2623        self.browser.getLink("Clearance Data").click()
2624        # and go back to the edit form
2625        self.browser.getLink("Edit").click()
2626        # Students can upload documents
2627        ctrl = self.browser.getControl(name='birthcertificateupload')
2628        file_obj = open(SAMPLE_IMAGE, 'rb')
2629        file_ctrl = ctrl.mech_control
2630        file_ctrl.add_file(file_obj, filename='my_birth_certificate.jpg')
2631        self.browser.getControl(
2632            name='upload_birthcertificateupload').click()
2633        self.assertTrue(
2634            'href="http://localhost/app/students/K1000000/birth_certificate"'
2635            in self.browser.contents)
2636        # Students can open clearance slip
2637        self.browser.getLink("View").click()
2638        self.browser.getLink("Download clearance slip").click()
2639        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2640        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2641        # Students can request clearance
2642        self.browser.open(self.edit_clearance_path)
2643        self.browser.getControl("Save and request clearance").click()
2644        self.browser.getControl(name="ac_series").value = self.existing_clrseries
2645        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
2646        self.browser.getControl("Request clearance now").click()
2647        self.assertMatches('...Clearance has been requested...',
2648                           self.browser.contents)
2649        # Student can't reopen clearance form after requesting clearance
2650        self.browser.open(self.student_path + '/cedit')
2651        self.assertMatches('...The requested form is locked...',
2652                           self.browser.contents)
2653
2654    def test_student_course_registration(self):
2655        # Student cant login if their password is not set
2656        IWorkflowInfo(self.student).fireTransition('admit')
2657        self.browser.open(self.login_path)
2658        self.browser.getControl(name="form.login").value = self.student_id
2659        self.browser.getControl(name="form.password").value = 'spwd'
2660        self.browser.getControl("Login").click()
2661        # Student can't add study level if not in state 'school fee paid'
2662        self.browser.open(self.student_path + '/studycourse/add')
2663        self.assertMatches('...The requested form is locked...',
2664                           self.browser.contents)
2665        # ... and must be transferred first
2666        IWorkflowState(self.student).setState('school fee paid')
2667        # Now students can add the current study level
2668        self.browser.getLink("Study Course").click()
2669        self.student['studycourse'].current_level = None
2670        self.browser.getLink("Add course list").click()
2671        self.assertMatches('...Your data are incomplete...',
2672                           self.browser.contents)
2673        self.student['studycourse'].current_level = 100
2674        self.browser.getLink("Add course list").click()
2675        self.assertMatches('...Add current level 100 (Year 1)...',
2676                           self.browser.contents)
2677        self.browser.getControl("Create course list now").click()
2678        # A level with one course ticket was created
2679        self.assertEqual(self.student['studycourse']['100'].number_of_tickets, 1)
2680        self.browser.getLink("100").click()
2681        self.browser.getLink("Edit course list").click()
2682        self.browser.getLink("here").click()
2683        self.browser.getControl(name="form.course").value = ['COURSE1']
2684        self.browser.getControl("Add course ticket").click()
2685        self.assertMatches('...The ticket exists...',
2686                           self.browser.contents)
2687        self.student['studycourse'].current_level = 200
2688        self.browser.getLink("Study Course").click()
2689        self.browser.getLink("Add course list").click()
2690        self.assertMatches('...Add current level 200 (Year 2)...',
2691                           self.browser.contents)
2692        self.browser.getControl("Create course list now").click()
2693        self.browser.getLink("200").click()
2694        self.browser.getLink("Edit course list").click()
2695        self.browser.getLink("here").click()
2696        self.browser.getControl(name="form.course").value = ['COURSE1']
2697        self.course.credits = 100
2698        self.browser.getControl("Add course ticket").click()
2699        self.assertMatches(
2700            '...Maximum credits exceeded...', self.browser.contents)
2701        self.course.credits = 10
2702        self.browser.getControl("Add course ticket").click()
2703        self.assertMatches('...The ticket exists...',
2704                           self.browser.contents)
2705        # Indeed the ticket exists as carry-over course from level 100
2706        # since its score was 0
2707        self.assertTrue(
2708            self.student['studycourse']['200']['COURSE1'].carry_over is True)
2709        self.assertTrue(
2710            self.student['studycourse']['200']['COURSE1'].course_category is None)
2711        # Students can open the pdf course registration slip
2712        self.browser.open(self.student_path + '/studycourse/200')
2713        self.browser.getLink("Download course registration slip").click()
2714        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2715        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2716        # Students can remove course tickets
2717        self.browser.open(self.student_path + '/studycourse/200/edit')
2718        self.browser.getControl("Remove selected", index=0).click()
2719        self.assertTrue('No ticket selected' in self.browser.contents)
2720        # No ticket can be selected since the carry-over course is a core course
2721        self.assertRaises(
2722            LookupError, self.browser.getControl, name='val_id')
2723        self.student['studycourse']['200']['COURSE1'].mandatory = False
2724        self.browser.open(self.student_path + '/studycourse/200/edit')
2725        # Course list can't be registered if total_credits exceeds max_credits
2726        self.student['studycourse']['200']['COURSE1'].credits = 60
2727        self.browser.getControl("Register course list").click()
2728        self.assertTrue('Maximum credits exceeded' in self.browser.contents)
2729        # Student can now remove the ticket
2730        ctrl = self.browser.getControl(name='val_id')
2731        ctrl.getControl(value='COURSE1').selected = True
2732        self.browser.getControl("Remove selected", index=0).click()
2733        self.assertTrue('Successfully removed' in self.browser.contents)
2734        # Removing course tickets is properly logged
2735        logfile = os.path.join(
2736            self.app['datacenter'].storage, 'logs', 'students.log')
2737        logcontent = open(logfile).read()
2738        self.assertTrue('K1000000 - students.browser.StudyLevelEditFormPage '
2739        '- K1000000 - removed: COURSE1 at 200' in logcontent)
2740        # They can add the same ticket using the edit page directly.
2741        # We can do the same by adding the course on the manage page directly
2742        self.browser.getControl(name="course").value = 'COURSE1'
2743        self.browser.getControl("Add course ticket").click()
2744        # Adding course tickets is logged
2745        logfile = os.path.join(
2746            self.app['datacenter'].storage, 'logs', 'students.log')
2747        logcontent = open(logfile).read()
2748        self.assertTrue('K1000000 - students.browser.StudyLevelEditFormPage - '
2749            'K1000000 - added: COURSE1|200|2004' in logcontent)
2750        # Course list can be registered
2751        self.browser.getControl("Register course list").click()
2752        self.assertTrue('Course list has been registered' in self.browser.contents)
2753        self.assertEqual(self.student.state, 'courses registered')
2754        # Course list can be unregistered
2755        self.browser.getLink("Unregister courses").click()
2756        self.assertEqual(self.student.state, 'school fee paid')
2757        self.assertTrue('Course list has been unregistered' in self.browser.contents)
2758        self.browser.open(self.student_path + '/studycourse/200/unregister_courses')
2759        self.assertTrue('You are in the wrong state' in self.browser.contents)
2760        # Students can view the transcript
2761        #self.browser.open(self.studycourse_path)
2762        #self.browser.getLink("Transcript").click()
2763        #self.browser.getLink("Academic Transcript").click()
2764        #self.assertEqual(self.browser.headers['Status'], '200 Ok')
2765        #self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2766        return
2767
2768    def test_student_ticket_update(self):
2769        IWorkflowState(self.student).setState('school fee paid')
2770        self.student['studycourse'].current_level = 100
2771        self.browser.open(self.login_path)
2772        self.browser.getControl(name="form.login").value = self.student_id
2773        self.browser.getControl(name="form.password").value = 'spwd'
2774        self.browser.getControl("Login").click()
2775        # Now students can add the current study level
2776        self.browser.getLink("Study Course").click()
2777        self.browser.getLink("Add course list").click()
2778        self.assertMatches('...Add current level 100 (Year 1)...',
2779                           self.browser.contents)
2780        self.browser.getControl("Create course list now").click()
2781        # A level with one course ticket was created
2782        self.assertEqual(
2783            self.student['studycourse']['100'].number_of_tickets, 1)
2784        self.browser.getLink("100").click()
2785        self.assertTrue('<td>Unnamed Course</td>' in self.browser.contents)
2786        self.browser.getLink("Edit course list").click()
2787        self.browser.getControl("Update all tickets").click()
2788        self.assertTrue('All course tickets updated.' in self.browser.contents)
2789        # ... nothing has changed
2790        self.assertTrue('<td>Unnamed Course</td>' in self.browser.contents)
2791        # We change the title of the course
2792        self.course.title = u'New Title'
2793        self.browser.getControl("Update all tickets").click()
2794        self.assertTrue('<td>New Title</td>' in self.browser.contents)
2795        # We remove the course
2796        del self.app['faculties']['fac1']['dep1'].courses['COURSE1']
2797        self.browser.getControl("Update all tickets").click()
2798        self.assertTrue(' <td>New Title (course cancelled)</td>'
2799            in self.browser.contents)
2800        # Course ticket invalidation has been logged
2801        logfile = os.path.join(
2802            self.app['datacenter'].storage, 'logs', 'students.log')
2803        logcontent = open(logfile).read()
2804        self.assertTrue(
2805            'K1000000 - students.browser.StudyLevelEditFormPage - '
2806            'K1000000 - course tickets invalidated: COURSE1'
2807            in logcontent)
2808        return
2809
2810    def test_student_course_registration_outstanding(self):
2811        self.course = createObject('waeup.Course')
2812        self.course.code = 'COURSE2'
2813        self.course.semester = 1
2814        self.course.credits = 45
2815        self.course.passmark = 40
2816        self.app['faculties']['fac1']['dep1'].courses.addCourse(
2817            self.course)
2818        IWorkflowState(self.student).setState('school fee paid')
2819        self.browser.open(self.login_path)
2820        self.browser.getControl(name="form.login").value = self.student_id
2821        self.browser.getControl(name="form.password").value = 'spwd'
2822        self.browser.getControl("Login").click()
2823        self.browser.open(self.student_path + '/studycourse/add')
2824        self.browser.getControl("Create course list now").click()
2825        self.assertEqual(self.student['studycourse']['100'].number_of_tickets, 1)
2826        self.student['studycourse'].current_level = 200
2827        self.browser.getLink("Study Course").click()
2828        self.browser.getLink("Add course list").click()
2829        self.assertMatches('...Add current level 200 (Year 2)...',
2830                           self.browser.contents)
2831        self.browser.getControl("Create course list now").click()
2832        self.browser.getLink("200").click()
2833        self.browser.getLink("Edit course list").click()
2834        self.browser.getLink("here").click()
2835        self.browser.getControl(name="form.course").value = ['COURSE2']
2836        self.browser.getControl("Add course ticket").click()
2837        # Carryover COURSE1 in level 200 already has 10 credits
2838        self.assertMatches(
2839            '...Maximum credits exceeded...', self.browser.contents)
2840        # If COURSE1 is outstanding, its credits won't be considered
2841        self.student['studycourse']['200']['COURSE1'].outstanding = True
2842        self.browser.getControl("Add course ticket").click()
2843        self.assertMatches(
2844            '...Successfully added COURSE2...', self.browser.contents)
2845        return
2846
2847    def test_postgraduate_student_access(self):
2848        self.certificate.study_mode = 'pg_ft'
2849        self.certificate.start_level = 999
2850        self.certificate.end_level = 999
2851        self.student['studycourse'].current_level = 999
2852        IWorkflowState(self.student).setState('school fee paid')
2853        self.browser.open(self.login_path)
2854        self.browser.getControl(name="form.login").value = self.student_id
2855        self.browser.getControl(name="form.password").value = 'spwd'
2856        self.browser.getControl("Login").click()
2857        self.assertTrue(
2858            'You logged in.' in self.browser.contents)
2859        # Now students can add the current study level
2860        self.browser.getLink("Study Course").click()
2861        self.browser.getLink("Add course list").click()
2862        self.assertMatches('...Add current level Postgraduate Level...',
2863                           self.browser.contents)
2864        self.browser.getControl("Create course list now").click()
2865        self.assertTrue("You successfully created a new course list"
2866            in self.browser.contents)
2867        # A level with one course ticket was created
2868        self.assertEqual(self.student['studycourse']['999'].number_of_tickets, 0)
2869        self.browser.getLink("Edit course list").click()
2870        self.browser.getLink("here").click()
2871        self.browser.getControl(name="form.course").value = ['COURSE1']
2872        self.browser.getControl("Add course ticket").click()
2873        self.assertMatches('...Successfully added COURSE1...',
2874                           self.browser.contents)
2875        # Postgraduate students can't register course lists
2876        self.browser.getControl("Register course list").click()
2877        self.assertTrue("your course list can't bee registered"
2878            in self.browser.contents)
2879        self.assertEqual(self.student.state, 'school fee paid')
2880        return
2881
2882    def test_student_clearance_wo_clrcode(self):
2883        IWorkflowState(self.student).setState('clearance started')
2884        self.browser.open(self.login_path)
2885        self.browser.getControl(name="form.login").value = self.student_id
2886        self.browser.getControl(name="form.password").value = 'spwd'
2887        self.browser.getControl("Login").click()
2888        self.browser.open(self.edit_clearance_path)
2889        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
2890        self.browser.getControl("Save and request clearance").click()
2891        self.assertMatches('...Clearance has been requested...',
2892                           self.browser.contents)
2893
2894    def test_student_clearance_payment(self):
2895        # Login
2896        self.browser.open(self.login_path)
2897        self.browser.getControl(name="form.login").value = self.student_id
2898        self.browser.getControl(name="form.password").value = 'spwd'
2899        self.browser.getControl("Login").click()
2900
2901        # Students can add online clearance payment tickets
2902        self.browser.open(self.payments_path + '/addop')
2903        self.browser.getControl(name="form.p_category").value = ['clearance']
2904        self.browser.getControl("Create ticket").click()
2905        self.assertMatches('...ticket created...',
2906                           self.browser.contents)
2907
2908        # Students can't approve the payment
2909        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
2910        ctrl = self.browser.getControl(name='val_id')
2911        value = ctrl.options[0]
2912        self.browser.getLink(value).click()
2913        payment_url = self.browser.url
2914        self.assertRaises(
2915            Unauthorized, self.browser.open, payment_url + '/approve')
2916        # In the base package they can 'use' a fake approval view.
2917        # XXX: I tried to use
2918        # self.student['payments'][value].approveStudentPayment() instead.
2919        # But this function fails in
2920        # w.k.accesscodes.accesscode.create_accesscode.
2921        # grok.getSite returns None in tests.
2922        self.browser.open(payment_url + '/fake_approve')
2923        self.assertMatches('...Payment approved...',
2924                          self.browser.contents)
2925        expected = '''...
2926        <td>
2927          <span>Paid</span>
2928        </td>...'''
2929        expected = '''...
2930        <td>
2931          <span>Paid</span>
2932        </td>...'''
2933        self.assertMatches(expected,self.browser.contents)
2934        payment_id = self.student['payments'].keys()[0]
2935        payment = self.student['payments'][payment_id]
2936        self.assertEqual(payment.p_state, 'paid')
2937        self.assertEqual(payment.r_amount_approved, 3456.0)
2938        self.assertEqual(payment.r_code, 'AP')
2939        self.assertEqual(payment.r_desc, u'Payment approved by Anna Tester')
2940        # The new CLR-0 pin has been created
2941        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
2942        pin = self.app['accesscodes']['CLR-0'].keys()[0]
2943        ac = self.app['accesscodes']['CLR-0'][pin]
2944        self.assertEqual(ac.owner, self.student_id)
2945        self.assertEqual(ac.cost, 3456.0)
2946
2947        # Students can open the pdf payment slip
2948        self.browser.open(payment_url + '/payment_slip.pdf')
2949        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2950        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2951
2952        # The new CLR-0 pin can be used for starting clearance
2953        # but they have to upload a passport picture first
2954        # which is only possible in state admitted
2955        self.browser.open(self.student_path + '/change_portrait')
2956        self.assertMatches('...form is locked...',
2957                          self.browser.contents)
2958        IWorkflowInfo(self.student).fireTransition('admit')
2959        self.browser.open(self.student_path + '/change_portrait')
2960        image = open(SAMPLE_IMAGE, 'rb')
2961        ctrl = self.browser.getControl(name='passportuploadedit')
2962        file_ctrl = ctrl.mech_control
2963        file_ctrl.add_file(image, filename='my_photo.jpg')
2964        self.browser.getControl(
2965            name='upload_passportuploadedit').click()
2966        self.browser.open(self.student_path + '/start_clearance')
2967        parts = pin.split('-')[1:]
2968        clrseries, clrnumber = parts
2969        self.browser.getControl(name="ac_series").value = clrseries
2970        self.browser.getControl(name="ac_number").value = clrnumber
2971        self.browser.getControl("Start clearance now").click()
2972        self.assertMatches('...Clearance process has been started...',
2973                           self.browser.contents)
2974
2975    def test_student_schoolfee_payment(self):
2976        configuration = createObject('waeup.SessionConfiguration')
2977        configuration.academic_session = 2005
2978        self.app['configuration'].addSessionConfiguration(configuration)
2979        # Login
2980        self.browser.open(self.login_path)
2981        self.browser.getControl(name="form.login").value = self.student_id
2982        self.browser.getControl(name="form.password").value = 'spwd'
2983        self.browser.getControl("Login").click()
2984
2985        # Students can add online school fee payment tickets.
2986        IWorkflowState(self.student).setState('returning')
2987        self.browser.open(self.payments_path)
2988        self.assertRaises(
2989            LookupError, self.browser.getControl, name='val_id')
2990        self.browser.getLink("Add current session payment ticket").click()
2991        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2992        self.browser.getControl("Create ticket").click()
2993        self.assertMatches('...ticket created...',
2994                           self.browser.contents)
2995        ctrl = self.browser.getControl(name='val_id')
2996        value = ctrl.options[0]
2997        self.browser.getLink(value).click()
2998        self.assertMatches('...Amount Authorized...',
2999                           self.browser.contents)
3000        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
3001        # Payment session and will be calculated as defined
3002        # in w.k.students.utils because we set changed the state
3003        # to returning
3004        self.assertEqual(self.student['payments'][value].p_session, 2005)
3005        self.assertEqual(self.student['payments'][value].p_level, 200)
3006
3007        # Student is the payer of the payment ticket.
3008        payer = IPayer(self.student['payments'][value])
3009        self.assertEqual(payer.display_fullname, 'Anna Tester')
3010        self.assertEqual(payer.id, self.student_id)
3011        self.assertEqual(payer.faculty, 'fac1')
3012        self.assertEqual(payer.department, 'dep1')
3013
3014        # We simulate the approval
3015        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
3016        self.browser.open(self.browser.url + '/fake_approve')
3017        self.assertMatches('...Payment approved...',
3018                          self.browser.contents)
3019
3020        ## The new SFE-0 pin can be used for starting new session
3021        #self.browser.open(self.studycourse_path)
3022        #self.browser.getLink('Start new session').click()
3023        #pin = self.app['accesscodes']['SFE-0'].keys()[0]
3024        #parts = pin.split('-')[1:]
3025        #sfeseries, sfenumber = parts
3026        #self.browser.getControl(name="ac_series").value = sfeseries
3027        #self.browser.getControl(name="ac_number").value = sfenumber
3028        #self.browser.getControl("Start now").click()
3029        #self.assertMatches('...Session started...',
3030        #                   self.browser.contents)
3031
3032        self.assertTrue(self.student.state == 'school fee paid')
3033        return
3034
3035    def test_student_bedallocation_payment(self):
3036        # Login
3037        self.browser.open(self.login_path)
3038        self.browser.getControl(name="form.login").value = self.student_id
3039        self.browser.getControl(name="form.password").value = 'spwd'
3040        self.browser.getControl("Login").click()
3041        self.browser.open(self.payments_path)
3042        self.browser.open(self.payments_path + '/addop')
3043        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
3044        self.browser.getControl("Create ticket").click()
3045        self.assertMatches('...ticket created...',
3046                           self.browser.contents)
3047        # Students can remove only online payment tickets which have
3048        # not received a valid callback
3049        self.browser.open(self.payments_path)
3050        ctrl = self.browser.getControl(name='val_id')
3051        value = ctrl.options[0]
3052        ctrl.getControl(value=value).selected = True
3053        self.browser.getControl("Remove selected", index=0).click()
3054        self.assertTrue('Successfully removed' in self.browser.contents)
3055
3056    def test_student_maintenance_payment(self):
3057        # Login
3058        self.browser.open(self.login_path)
3059        self.browser.getControl(name="form.login").value = self.student_id
3060        self.browser.getControl(name="form.password").value = 'spwd'
3061        self.browser.getControl("Login").click()
3062        self.browser.open(self.payments_path)
3063        self.browser.open(self.payments_path + '/addop')
3064        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
3065        self.browser.getControl("Create ticket").click()
3066        self.assertMatches('...You have not yet booked accommodation...',
3067                           self.browser.contents)
3068        # We continue this test in test_student_accommodation
3069
3070    def test_student_previous_payments(self):
3071        configuration = createObject('waeup.SessionConfiguration')
3072        configuration.academic_session = 2000
3073        configuration.clearance_fee = 3456.0
3074        configuration.booking_fee = 123.4
3075        self.app['configuration'].addSessionConfiguration(configuration)
3076        configuration2 = createObject('waeup.SessionConfiguration')
3077        configuration2.academic_session = 2003
3078        configuration2.clearance_fee = 3456.0
3079        configuration2.booking_fee = 123.4
3080        self.app['configuration'].addSessionConfiguration(configuration2)
3081        configuration3 = createObject('waeup.SessionConfiguration')
3082        configuration3.academic_session = 2005
3083        configuration3.clearance_fee = 3456.0
3084        configuration3.booking_fee = 123.4
3085        self.app['configuration'].addSessionConfiguration(configuration3)
3086        self.student['studycourse'].entry_session = 2002
3087
3088        # Login
3089        self.browser.open(self.login_path)
3090        self.browser.getControl(name="form.login").value = self.student_id
3091        self.browser.getControl(name="form.password").value = 'spwd'
3092        self.browser.getControl("Login").click()
3093
3094        # Students can add previous school fee payment tickets in any state.
3095        IWorkflowState(self.student).setState('courses registered')
3096        self.browser.open(self.payments_path)
3097        self.browser.getLink("Add previous session payment ticket").click()
3098
3099        # Previous session payment form is provided
3100        self.assertEqual(self.student.current_session, 2004)
3101        self.browser.getControl(name="form.p_category").value = ['schoolfee']
3102        self.browser.getControl(name="form.p_session").value = ['2000']
3103        self.browser.getControl(name="form.p_level").value = ['300']
3104        self.browser.getControl("Create ticket").click()
3105        self.assertMatches('...The previous session must not fall below...',
3106                           self.browser.contents)
3107        self.browser.getControl(name="form.p_category").value = ['schoolfee']
3108        self.browser.getControl(name="form.p_session").value = ['2005']
3109        self.browser.getControl(name="form.p_level").value = ['300']
3110        self.browser.getControl("Create ticket").click()
3111        self.assertMatches('...This is not a previous session...',
3112                           self.browser.contents)
3113
3114        # Students can pay current session school fee.
3115        self.browser.getControl(name="form.p_category").value = ['schoolfee']
3116        self.browser.getControl(name="form.p_session").value = ['2004']
3117        self.browser.getControl(name="form.p_level").value = ['300']
3118        self.browser.getControl("Create ticket").click()
3119        self.assertMatches('...ticket created...',
3120                           self.browser.contents)
3121        ctrl = self.browser.getControl(name='val_id')
3122        value = ctrl.options[0]
3123        self.browser.getLink(value).click()
3124        self.assertMatches('...Amount Authorized...',
3125                           self.browser.contents)
3126        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
3127
3128        # Ticket creation is logged.
3129        logfile = os.path.join(
3130            self.app['datacenter'].storage, 'logs', 'students.log')
3131        logcontent = open(logfile).read()
3132        self.assertTrue(
3133            'K1000000 - students.browser.PreviousPaymentAddFormPage - '
3134            'K1000000 - added: %s' % value
3135            in logcontent)
3136
3137        # Payment session is properly set
3138        self.assertEqual(self.student['payments'][value].p_session, 2004)
3139        self.assertEqual(self.student['payments'][value].p_level, 300)
3140
3141        # We simulate the approval
3142        self.browser.open(self.browser.url + '/fake_approve')
3143        self.assertMatches('...Payment approved...',
3144                          self.browser.contents)
3145
3146        # No AC has been created
3147        self.assertEqual(len(self.app['accesscodes']['SFE-0'].keys()), 0)
3148        self.assertTrue(self.student['payments'][value].ac is None)
3149
3150        # Current payment flag is set False
3151        self.assertFalse(self.student['payments'][value].p_current)
3152
3153        # Button and form are not available for students who are in
3154        # states up to cleared
3155        self.student['studycourse'].entry_session = 2004
3156        IWorkflowState(self.student).setState('cleared')
3157        self.browser.open(self.payments_path)
3158        self.assertFalse(
3159            "Add previous session payment ticket" in self.browser.contents)
3160        self.browser.open(self.payments_path + '/addpp')
3161        self.assertTrue(
3162            "No previous payment to be made" in self.browser.contents)
3163        return
3164
3165    def test_postgraduate_student_payments(self):
3166        configuration = createObject('waeup.SessionConfiguration')
3167        configuration.academic_session = 2005
3168        self.app['configuration'].addSessionConfiguration(configuration)
3169        self.certificate.study_mode = 'pg_ft'
3170        self.certificate.start_level = 999
3171        self.certificate.end_level = 999
3172        self.student['studycourse'].current_level = 999
3173        # Login
3174        self.browser.open(self.login_path)
3175        self.browser.getControl(name="form.login").value = self.student_id
3176        self.browser.getControl(name="form.password").value = 'spwd'
3177        self.browser.getControl("Login").click()
3178        # Students can add online school fee payment tickets.
3179        IWorkflowState(self.student).setState('cleared')
3180        self.browser.open(self.payments_path)
3181        self.browser.getLink("Add current session payment ticket").click()
3182        self.browser.getControl(name="form.p_category").value = ['schoolfee']
3183        self.browser.getControl("Create ticket").click()
3184        self.assertMatches('...ticket created...',
3185                           self.browser.contents)
3186        ctrl = self.browser.getControl(name='val_id')
3187        value = ctrl.options[0]
3188        self.browser.getLink(value).click()
3189        self.assertMatches('...Amount Authorized...',
3190                           self.browser.contents)
3191        # Payment session and level are current ones.
3192        # Postgrads have to pay school_fee_1.
3193        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
3194        self.assertEqual(self.student['payments'][value].p_session, 2004)
3195        self.assertEqual(self.student['payments'][value].p_level, 999)
3196
3197        # We simulate the approval
3198        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
3199        self.browser.open(self.browser.url + '/fake_approve')
3200        self.assertMatches('...Payment approved...',
3201                          self.browser.contents)
3202
3203        ## The new SFE-0 pin can be used for starting session
3204        #self.browser.open(self.studycourse_path)
3205        #self.browser.getLink('Start new session').click()
3206        #pin = self.app['accesscodes']['SFE-0'].keys()[0]
3207        #parts = pin.split('-')[1:]
3208        #sfeseries, sfenumber = parts
3209        #self.browser.getControl(name="ac_series").value = sfeseries
3210        #self.browser.getControl(name="ac_number").value = sfenumber
3211        #self.browser.getControl("Start now").click()
3212        #self.assertMatches('...Session started...',
3213        #                   self.browser.contents)
3214
3215        self.assertTrue(self.student.state == 'school fee paid')
3216
3217        # Postgrad students do not need to register courses the
3218        # can just pay for the next session.
3219        self.browser.open(self.payments_path)
3220        # Remove first payment to be sure that we access the right ticket
3221        del self.student['payments'][value]
3222        self.browser.getLink("Add current session payment ticket").click()
3223        self.browser.getControl(name="form.p_category").value = ['schoolfee']
3224        self.browser.getControl("Create ticket").click()
3225        ctrl = self.browser.getControl(name='val_id')
3226        value = ctrl.options[0]
3227        self.browser.getLink(value).click()
3228        # Payment session has increased by one, payment level remains the same.
3229        # Returning Postgraduates have to pay school_fee_2.
3230        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
3231        self.assertEqual(self.student['payments'][value].p_session, 2005)
3232        self.assertEqual(self.student['payments'][value].p_level, 999)
3233
3234        # Student is still in old session
3235        self.assertEqual(self.student.current_session, 2004)
3236
3237        # We do not need to pay the ticket if any other
3238        # SFE pin is provided
3239        pin_container = self.app['accesscodes']
3240        pin_container.createBatch(
3241            datetime.utcnow(), 'some_userid', 'SFE', 9.99, 1)
3242        pin = pin_container['SFE-1'].values()[0].representation
3243        sfeseries, sfenumber = pin.split('-')[1:]
3244        # The new SFE-1 pin can be used for starting new session
3245        self.browser.open(self.studycourse_path)
3246        self.browser.getLink('Start new session').click()
3247        self.browser.getControl(name="ac_series").value = sfeseries
3248        self.browser.getControl(name="ac_number").value = sfenumber
3249        self.browser.getControl("Start now").click()
3250        self.assertMatches('...Session started...',
3251                           self.browser.contents)
3252        self.assertTrue(self.student.state == 'school fee paid')
3253        # Student is in new session
3254        self.assertEqual(self.student.current_session, 2005)
3255        self.assertEqual(self.student['studycourse'].current_level, 999)
3256        return
3257
3258    def test_student_accommodation(self):
3259        # Create a second hostel with one bed
3260        hostel = Hostel()
3261        hostel.hostel_id = u'hall-2'
3262        hostel.hostel_name = u'Hall 2'
3263        self.app['hostels'].addHostel(hostel)
3264        bed = Bed()
3265        bed.bed_id = u'hall-2_A_101_A'
3266        bed.bed_number = 1
3267        bed.owner = NOT_OCCUPIED
3268        bed.bed_type = u'regular_female_fr'
3269        self.app['hostels'][hostel.hostel_id].addBed(bed)
3270
3271        self.browser.open(self.login_path)
3272        self.browser.getControl(name="form.login").value = self.student_id
3273        self.browser.getControl(name="form.password").value = 'spwd'
3274        self.browser.getControl("Login").click()
3275        # Students can add online booking fee payment tickets and open the
3276        # callback view (see test_manage_payments).
3277        self.browser.getLink("Payments").click()
3278        self.browser.getLink("Add current session payment ticket").click()
3279        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
3280        self.browser.getControl("Create ticket").click()
3281        ctrl = self.browser.getControl(name='val_id')
3282        value = ctrl.options[0]
3283        self.browser.getLink(value).click()
3284        self.browser.open(self.browser.url + '/fake_approve')
3285        # The new HOS-0 pin has been created.
3286        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
3287        pin = self.app['accesscodes']['HOS-0'].keys()[0]
3288        ac = self.app['accesscodes']['HOS-0'][pin]
3289        parts = pin.split('-')[1:]
3290        sfeseries, sfenumber = parts
3291        # Students can use HOS code and book a bed space with it ...
3292        self.browser.open(self.acco_path)
3293        # ... but not if booking period has expired ...
3294        self.app['hostels'].enddate = datetime.now(pytz.utc)
3295        self.browser.getControl("Book accommodation").click()
3296        self.assertMatches('...Outside booking period: ...',
3297                           self.browser.contents)
3298        self.app['hostels'].enddate = datetime.now(pytz.utc) + timedelta(days=10)
3299        # ... or student data are incomplete ...
3300        self.student['studycourse'].current_level = None
3301        self.browser.getControl("Book accommodation").click()
3302        self.assertMatches('...Your data are incomplete...',
3303            self.browser.contents)
3304        self.student['studycourse'].current_level = 100
3305        # ... or student is not the an allowed state ...
3306        self.browser.getControl("Book accommodation").click()
3307        self.assertMatches('...You are in the wrong...',
3308                           self.browser.contents)
3309        # Students can still not see the disired hostel selector.
3310        self.assertFalse('desired hostel' in self.browser.contents)
3311        IWorkflowInfo(self.student).fireTransition('admit')
3312        # Students can now see the disired hostel selector.
3313        self.browser.reload()
3314        self.browser.open(self.acco_path)
3315        self.assertTrue('desired hostel' in self.browser.contents)
3316        self.browser.getControl(name="hostel").value = ['hall-2']
3317        self.browser.getControl("Save").click()
3318        self.assertTrue('selection has been saved' in self.browser.contents)
3319        self.assertTrue('<option selected="selected" value="hall-2">'
3320            in self.browser.contents)
3321        self.browser.getControl("Book accommodation").click()
3322        self.assertMatches('...Activation Code:...',
3323                           self.browser.contents)
3324        # Student can't use faked ACs ...
3325        self.browser.getControl(name="ac_series").value = u'nonsense'
3326        self.browser.getControl(name="ac_number").value = sfenumber
3327        self.browser.getControl("Create bed ticket").click()
3328        self.assertMatches('...Activation code is invalid...',
3329                           self.browser.contents)
3330        # ... or ACs owned by somebody else.
3331        ac.owner = u'Anybody'
3332        self.browser.getControl(name="ac_series").value = sfeseries
3333        self.browser.getControl(name="ac_number").value = sfenumber
3334        self.browser.getControl("Create bed ticket").click()
3335        # Hostel 2 has only a bed for women.
3336        self.assertTrue('There is no free bed in your desired hostel'
3337            in self.browser.contents)
3338        self.browser.getControl(name="hostel").value = ['hall-1']
3339        self.browser.getControl("Save").click()
3340        self.browser.getControl("Book accommodation").click()
3341        # Student can't use faked ACs ...
3342        self.browser.getControl(name="ac_series").value = sfeseries
3343        self.browser.getControl(name="ac_number").value = sfenumber
3344        self.browser.getControl("Create bed ticket").click()
3345        self.assertMatches('...You are not the owner of this access code...',
3346                           self.browser.contents)
3347        # The bed remains empty.
3348        bed = self.app['hostels']['hall-1']['hall-1_A_101_A']
3349        self.assertTrue(bed.owner == NOT_OCCUPIED)
3350        ac.owner = self.student_id
3351        self.browser.getControl(name="ac_series").value = sfeseries
3352        self.browser.getControl(name="ac_number").value = sfenumber
3353        self.browser.getControl("Create bed ticket").click()
3354        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
3355                           self.browser.contents)
3356        # Bed has been allocated.
3357        self.assertTrue(bed.owner == self.student_id)
3358        # BedTicketAddPage is now blocked.
3359        self.browser.getControl("Book accommodation").click()
3360        self.assertMatches('...You already booked a bed space...',
3361            self.browser.contents)
3362        # The bed ticket displays the data correctly.
3363        self.browser.open(self.acco_path + '/2004')
3364        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
3365                           self.browser.contents)
3366        self.assertMatches('...2004/2005...', self.browser.contents)
3367        self.assertMatches('...regular_male_fr...', self.browser.contents)
3368        self.assertMatches('...%s...' % pin, self.browser.contents)
3369        # Students can open the pdf slip.
3370        self.browser.open(self.browser.url + '/bed_allocation_slip.pdf')
3371        self.assertEqual(self.browser.headers['Status'], '200 Ok')
3372        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
3373        # Students can't relocate themselves.
3374        self.assertFalse('Relocate' in self.browser.contents)
3375        relocate_path = self.acco_path + '/2004/relocate'
3376        self.assertRaises(
3377            Unauthorized, self.browser.open, relocate_path)
3378        # Students can't see the Remove button and check boxes.
3379        self.browser.open(self.acco_path)
3380        self.assertFalse('Remove' in self.browser.contents)
3381        self.assertFalse('val_id' in self.browser.contents)
3382        # Students can pay maintenance fee now.
3383        self.browser.open(self.payments_path)
3384        self.browser.open(self.payments_path + '/addop')
3385        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
3386        self.browser.getControl("Create ticket").click()
3387        self.assertMatches('...Payment ticket created...',
3388                           self.browser.contents)
3389        ctrl = self.browser.getControl(name='val_id')
3390        value = ctrl.options[0]
3391        # Maintennace fee is taken from the hostel object.
3392        self.assertEqual(self.student['payments'][value].amount_auth, 876.0)
3393        # If the hostel's maintenance fee isn't set, the fee is
3394        # taken from the session configuration object.
3395        self.app['hostels']['hall-1'].maint_fee = 0.0
3396        self.browser.open(self.payments_path + '/addop')
3397        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
3398        self.browser.getControl("Create ticket").click()
3399        ctrl = self.browser.getControl(name='val_id')
3400        value = ctrl.options[1]
3401        self.assertEqual(self.student['payments'][value].amount_auth, 987.0)
3402        # The bedticket is aware of successfull maintenance fee payment
3403        bedticket = self.student['accommodation']['2004']
3404        self.assertFalse(bedticket.maint_payment_made)
3405        self.student['payments'][value].approve()
3406        self.assertTrue(bedticket.maint_payment_made)
3407        return
3408
3409    def test_change_password_request(self):
3410        self.browser.open('http://localhost/app/changepw')
3411        self.browser.getControl(name="form.identifier").value = '123'
3412        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
3413        self.browser.getControl("Send login credentials").click()
3414        self.assertTrue('An email with' in self.browser.contents)
3415
3416    def test_student_expired_personal_data(self):
3417        # Login
3418        IWorkflowState(self.student).setState('school fee paid')
3419        delta = timedelta(days=180)
3420        self.student.personal_updated = datetime.utcnow() - delta
3421        self.browser.open(self.login_path)
3422        self.browser.getControl(name="form.login").value = self.student_id
3423        self.browser.getControl(name="form.password").value = 'spwd'
3424        self.browser.getControl("Login").click()
3425        self.assertEqual(self.browser.url, self.student_path)
3426        self.assertTrue(
3427            'You logged in' in self.browser.contents)
3428        # Students don't see personal_updated field in edit form
3429        self.browser.open(self.edit_personal_path)
3430        self.assertFalse('Updated' in self.browser.contents)
3431        self.browser.open(self.personal_path)
3432        self.assertTrue('Updated' in self.browser.contents)
3433        self.browser.getLink("Logout").click()
3434        delta = timedelta(days=181)
3435        self.student.personal_updated = datetime.utcnow() - delta
3436        self.browser.open(self.login_path)
3437        self.browser.getControl(name="form.login").value = self.student_id
3438        self.browser.getControl(name="form.password").value = 'spwd'
3439        self.browser.getControl("Login").click()
3440        self.assertEqual(self.browser.url, self.edit_personal_path)
3441        self.assertTrue(
3442            'Your personal data record is outdated.' in self.browser.contents)
3443
3444    def test_request_transcript(self):
3445        IWorkflowState(self.student).setState('graduated')
3446        self.browser.open(self.login_path)
3447        self.browser.getControl(name="form.login").value = self.student_id
3448        self.browser.getControl(name="form.password").value = 'spwd'
3449        self.browser.getControl("Login").click()
3450        self.assertMatches(
3451            '...You logged in...', self.browser.contents)
3452        # Create payment ticket
3453        self.browser.open(self.payments_path)
3454        self.browser.open(self.payments_path + '/addop')
3455        self.browser.getControl(name="form.p_category").value = ['transcript']
3456        self.browser.getControl("Create ticket").click()
3457        ctrl = self.browser.getControl(name='val_id')
3458        value = ctrl.options[0]
3459        self.browser.getLink(value).click()
3460        self.assertMatches('...Amount Authorized...',
3461                           self.browser.contents)
3462        self.assertEqual(self.student['payments'][value].amount_auth, 4567.0)
3463        # Student is the payer of the payment ticket.
3464        payer = IPayer(self.student['payments'][value])
3465        self.assertEqual(payer.display_fullname, 'Anna Tester')
3466        self.assertEqual(payer.id, self.student_id)
3467        self.assertEqual(payer.faculty, 'fac1')
3468        self.assertEqual(payer.department, 'dep1')
3469        # We simulate the approval and fetch the pin
3470        self.assertEqual(len(self.app['accesscodes']['TSC-0']),0)
3471        self.browser.open(self.browser.url + '/fake_approve')
3472        self.assertMatches('...Payment approved...',
3473                          self.browser.contents)
3474        pin = self.app['accesscodes']['TSC-0'].keys()[0]
3475        parts = pin.split('-')[1:]
3476        tscseries, tscnumber = parts
3477        # Student can use the pin to send the transcript request
3478        self.browser.open(self.student_path)
3479        self.browser.getLink("Request transcript").click()
3480        self.browser.getControl(name="ac_series").value = tscseries
3481        self.browser.getControl(name="ac_number").value = tscnumber
3482        self.browser.getControl(name="comment").value = 'Comment line 1 \nComment line2'
3483        self.browser.getControl(name="address").value = 'Address line 1 \nAddress line2'
3484        self.browser.getControl("Submit").click()
3485        self.assertMatches('...Transcript processing has been started...',
3486                          self.browser.contents)
3487        self.assertEqual(self.student.state, 'transcript requested')
3488        self.assertMatches(
3489            '... UTC K1000000 wrote:\n\nComment line 1 \n'
3490            'Comment line2\n\nDispatch Address:\nAddress line 1 \n'
3491            'Address line2\n\n', self.student['studycourse'].transcript_comment)
3492        # The comment has been logged
3493        logfile = os.path.join(
3494            self.app['datacenter'].storage, 'logs', 'students.log')
3495        logcontent = open(logfile).read()
3496        self.assertTrue(
3497            'K1000000 - students.browser.StudentTranscriptRequestPage - '
3498            'K1000000 - comment: Comment line 1 <br>Comment line2\n'
3499            in logcontent)
3500
3501    def test_late_registration(self):
3502        # Login
3503        delta = timedelta(days=10)
3504        self.app['configuration'][
3505            '2004'].coursereg_deadline = datetime.now(pytz.utc) - delta
3506        IWorkflowState(self.student).setState('school fee paid')
3507        self.browser.open(self.login_path)
3508        self.browser.getControl(name="form.login").value = self.student_id
3509        self.browser.getControl(name="form.password").value = 'spwd'
3510        self.browser.getControl("Login").click()
3511        self.browser.open(self.payments_path)
3512        self.browser.open(self.payments_path + '/addop')
3513        self.browser.getControl(name="form.p_category").value = ['late_registration']
3514        self.browser.getControl("Create ticket").click()
3515        self.assertMatches('...ticket created...',
3516                           self.browser.contents)
3517        self.browser.open(self.payments_path)
3518        ctrl = self.browser.getControl(name='val_id')
3519        value = ctrl.options[0]
3520        self.browser.getLink("Study Course").click()
3521        self.browser.getLink("Add course list").click()
3522        self.assertMatches('...Add current level 100 (Year 1)...',
3523                           self.browser.contents)
3524        self.browser.getControl("Create course list now").click()
3525        self.browser.getLink("100").click()
3526        self.browser.getLink("Edit course list").click()
3527        self.browser.getControl("Register course list").click()
3528        self.assertTrue('Course registration has ended. Please pay' in self.browser.contents)
3529        self.student['payments'][value].approve()
3530        self.browser.getControl("Register course list").click()
3531        self.assertTrue('Course list has been registered' in self.browser.contents)
3532        self.assertEqual(self.student.state, 'courses registered')
3533
3534
3535class StudentRequestPWTests(StudentsFullSetup):
3536    # Tests for student registration
3537
3538    layer = FunctionalLayer
3539
3540    def test_request_pw(self):
3541        # Student with wrong number can't be found.
3542        self.browser.open('http://localhost/app/requestpw')
3543        self.browser.getControl(name="form.lastname").value = 'Tester'
3544        self.browser.getControl(name="form.number").value = 'anynumber'
3545        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
3546        self.browser.getControl("Send login credentials").click()
3547        self.assertTrue('No student record found.'
3548            in self.browser.contents)
3549        # Anonymous is not informed that lastname verification failed.
3550        # It seems that the record doesn't exist.
3551        self.browser.open('http://localhost/app/requestpw')
3552        self.browser.getControl(name="form.lastname").value = 'Johnny'
3553        self.browser.getControl(name="form.number").value = '123'
3554        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
3555        self.browser.getControl("Send login credentials").click()
3556        self.assertTrue('No student record found.'
3557            in self.browser.contents)
3558        # Even with the correct lastname we can't register if a
3559        # password has been set and used.
3560        self.browser.getControl(name="form.lastname").value = 'Tester'
3561        self.browser.getControl(name="form.number").value = '123'
3562        self.browser.getControl("Send login credentials").click()
3563        self.assertTrue('Your password has already been set and used.'
3564            in self.browser.contents)
3565        self.browser.open('http://localhost/app/requestpw')
3566        self.app['students'][self.student_id].password = None
3567        # The lastname field, used for verification, is not case-sensitive.
3568        self.browser.getControl(name="form.lastname").value = 'tESTer'
3569        self.browser.getControl(name="form.number").value = '123'
3570        self.browser.getControl(name="form.email").value = 'new@yy.zz'
3571        self.browser.getControl("Send login credentials").click()
3572        # Yeah, we succeded ...
3573        self.assertTrue('Your password request was successful.'
3574            in self.browser.contents)
3575        # We can also use the matric_number instead.
3576        self.browser.open('http://localhost/app/requestpw')
3577        self.browser.getControl(name="form.lastname").value = 'tESTer'
3578        self.browser.getControl(name="form.number").value = '234'
3579        self.browser.getControl(name="form.email").value = 'new@yy.zz'
3580        self.browser.getControl("Send login credentials").click()
3581        self.assertTrue('Your password request was successful.'
3582            in self.browser.contents)
3583        # ... and  student can be found in the catalog via the email address
3584        cat = queryUtility(ICatalog, name='students_catalog')
3585        results = list(
3586            cat.searchResults(
3587            email=('new@yy.zz', 'new@yy.zz')))
3588        self.assertEqual(self.student,results[0])
3589        logfile = os.path.join(
3590            self.app['datacenter'].storage, 'logs', 'main.log')
3591        logcontent = open(logfile).read()
3592        self.assertTrue('zope.anybody - students.browser.StudentRequestPasswordPage - '
3593                        '234 (K1000000) - new@yy.zz' in logcontent)
3594        return
3595
3596    def test_student_locked_level_forms(self):
3597
3598        # Add two study levels, one current and one previous
3599        studylevel = createObject(u'waeup.StudentStudyLevel')
3600        studylevel.level = 100
3601        self.student['studycourse'].addStudentStudyLevel(
3602            self.certificate, studylevel)
3603        studylevel = createObject(u'waeup.StudentStudyLevel')
3604        studylevel.level = 200
3605        self.student['studycourse'].addStudentStudyLevel(
3606            self.certificate, studylevel)
3607        IWorkflowState(self.student).setState('school fee paid')
3608        self.student['studycourse'].current_level = 200
3609
3610        self.browser.open(self.login_path)
3611        self.browser.getControl(name="form.login").value = self.student_id
3612        self.browser.getControl(name="form.password").value = 'spwd'
3613        self.browser.getControl("Login").click()
3614
3615        self.browser.open(self.student_path + '/studycourse/200/edit')
3616        self.assertFalse('The requested form is locked' in self.browser.contents)
3617        self.browser.open(self.student_path + '/studycourse/100/edit')
3618        self.assertTrue('The requested form is locked' in self.browser.contents)
3619
3620        self.browser.open(self.student_path + '/studycourse/200/ctadd')
3621        self.assertFalse('The requested form is locked' in self.browser.contents)
3622        self.browser.open(self.student_path + '/studycourse/100/ctadd')
3623        self.assertTrue('The requested form is locked' in self.browser.contents)
3624
3625        IWorkflowState(self.student).setState('courses registered')
3626        self.browser.open(self.student_path + '/studycourse/200/edit')
3627        self.assertTrue('The requested form is locked' in self.browser.contents)
3628        self.browser.open(self.student_path + '/studycourse/200/ctadd')
3629        self.assertTrue('The requested form is locked' in self.browser.contents)
3630
3631
3632class PublicPagesTests(StudentsFullSetup):
3633    # Tests for simple webservices
3634
3635    layer = FunctionalLayer
3636
3637    def test_paymentrequest(self):
3638        payment = createObject('waeup.StudentOnlinePayment')
3639        payment.p_category = u'schoolfee'
3640        payment.p_session = self.student.current_session
3641        payment.p_item = u'My Certificate'
3642        payment.p_id = u'anyid'
3643        self.student['payments']['anykey'] = payment
3644        # Request information about unpaid payment ticket
3645        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
3646        self.assertEqual(self.browser.contents, '-1')
3647        # Request information about paid payment ticket
3648        payment.p_state = u'paid'
3649        notify(grok.ObjectModifiedEvent(payment))
3650        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
3651        self.assertEqual(self.browser.contents,
3652            'FULL_NAME=Anna Tester&FACULTY=fac1&DEPARTMENT=dep1'
3653            '&PAYMENT_ITEM=My Certificate&PAYMENT_CATEGORY=School Fee'
3654            '&ACADEMIC_SESSION=2004/2005&MATRIC_NUMBER=234&REG_NUMBER=123'
3655            '&FEE_AMOUNT=0.0')
3656        self.browser.open('http://localhost/app/paymentrequest?NONSENSE=nonsense')
3657        self.assertEqual(self.browser.contents, '-1')
3658        self.browser.open('http://localhost/app/paymentrequest?P_ID=nonsense')
3659        self.assertEqual(self.browser.contents, '-1')
3660
3661class StudentDataExportTests(StudentsFullSetup, FunctionalAsyncTestCase):
3662    # Tests for StudentsContainer class views and pages
3663
3664    layer = FunctionalLayer
3665
3666    def wait_for_export_job_completed(self):
3667        # helper function waiting until the current export job is completed
3668        manager = getUtility(IJobManager)
3669        job_id = self.app['datacenter'].running_exports[0][0]
3670        job = manager.get(job_id)
3671        wait_for_result(job)
3672        return job_id
3673
3674    def add_payment(self, student):
3675        # get a payment with all fields set
3676        payment = StudentOnlinePayment()
3677        payment.creation_date = datetime(2012, 12, 13)
3678        payment.p_id = 'my-id'
3679        payment.p_category = u'schoolfee'
3680        payment.p_state = 'paid'
3681        payment.ac = u'666'
3682        payment.p_item = u'p-item'
3683        payment.p_level = 100
3684        payment.p_session = curr_year - 6
3685        payment.payment_date = datetime(2012, 12, 13)
3686        payment.amount_auth = 12.12
3687        payment.r_amount_approved = 12.12
3688        payment.r_code = u'r-code'
3689        # XXX: there is no addPayment method to give predictable names
3690        self.payment = student['payments']['my-payment'] = payment
3691        return payment
3692
3693    def test_datacenter_export(self):
3694        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3695        self.browser.open('http://localhost/app/datacenter/@@exportconfig')
3696        self.browser.getControl(name="exporter").value = ['bursary']
3697        self.browser.getControl(name="session").value = ['2004']
3698        self.browser.getControl(name="level").value = ['100']
3699        self.browser.getControl(name="mode").value = ['ug_ft']
3700        self.browser.getControl(name="payments_start").value = '13/12/2012'
3701        self.browser.getControl(name="payments_end").value = '14/12/2012'
3702        self.browser.getControl("Create CSV file").click()
3703
3704        # When the job is finished and we reload the page...
3705        job_id = self.wait_for_export_job_completed()
3706        # ... the csv file can be downloaded ...
3707        self.browser.open('http://localhost/app/datacenter/@@export')
3708        self.browser.getLink("Download").click()
3709        self.assertEqual(self.browser.headers['content-type'],
3710            'text/csv; charset=UTF-8')
3711        self.assertTrue(
3712            'filename="WAeUP.Kofa_bursary_%s.csv' % job_id in
3713            self.browser.headers['content-disposition'])
3714        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3715        job_id = self.app['datacenter'].running_exports[0][0]
3716        # ... and discarded
3717        self.browser.open('http://localhost/app/datacenter/@@export')
3718        self.browser.getControl("Discard").click()
3719        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3720        # Creation, downloading and discarding is logged
3721        logfile = os.path.join(
3722            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3723        logcontent = open(logfile).read()
3724        self.assertTrue(
3725            'zope.mgr - students.browser.DatacenterExportJobContainerJobConfig '
3726            '- exported: bursary (2004, 100, ug_ft, None, None, None, '
3727            '13/12/2012, 14/12/2012, all, all, all, all), job_id=%s'
3728            % job_id in logcontent
3729            )
3730        self.assertTrue(
3731            'zope.mgr - browser.pages.ExportCSVView '
3732            '- downloaded: WAeUP.Kofa_bursary_%s.csv, job_id=%s'
3733            % (job_id, job_id) in logcontent
3734            )
3735        self.assertTrue(
3736            'zope.mgr - browser.pages.ExportCSVPage '
3737            '- discarded: job_id=%s' % job_id in logcontent
3738            )
3739
3740    def test_datacenter_export_selected(self):
3741        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3742        self.browser.open('http://localhost/app/datacenter/@@exportselected')
3743        self.browser.getControl(name="exporter").value = ['students']
3744        self.browser.getControl(name="students").value = 'K1000000'
3745        self.browser.getControl("Create CSV file").click()
3746        # When the job is finished and we reload the page...
3747        job_id = self.wait_for_export_job_completed()
3748        # ... the csv file can be downloaded ...
3749        self.browser.open('http://localhost/app/datacenter/@@export')
3750        self.browser.getLink("Download").click()
3751        self.assertEqual(self.browser.headers['content-type'],
3752            'text/csv; charset=UTF-8')
3753        self.assertTrue(
3754            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3755            self.browser.headers['content-disposition'])
3756        self.assertTrue(
3757            'adm_code,clr_code,date_of_birth,email,employer,'
3758            'firstname,flash_notice,lastname,matric_number,middlename,nationality,'
3759            'officer_comment,perm_address,personal_updated,phone,reg_number,'
3760            'sex,student_id,suspended,suspended_comment,'
3761            'password,state,history,certcode,is_postgrad,'
3762            'current_level,current_session\r\n'
3763            ',,1981-02-04#,aa@aa.ng,,Anna,,Tester,234,,,,,,'
3764            '1234#,123,m,K1000000,0,,{SSHA}' in self.browser.contents)
3765
3766    def test_payment_dates(self):
3767        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3768        self.browser.open('http://localhost/app/datacenter/@@exportconfig')
3769        self.browser.getControl(name="exporter").value = ['bursary']
3770        self.browser.getControl(name="session").value = ['2004']
3771        self.browser.getControl(name="level").value = ['100']
3772        self.browser.getControl(name="mode").value = ['ug_ft']
3773        self.browser.getControl(name="payments_start").value = '13/12/2012'
3774        # If one payment date is missing, an error message appears
3775        self.browser.getControl(name="payments_end").value = ''
3776        self.browser.getControl("Create CSV file").click()
3777        self.assertTrue('Payment dates do not match format d/m/Y'
3778            in self.browser.contents)
3779
3780    def test_faculties_export(self):
3781        self.add_payment(self.student)
3782        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3783        facs_path = 'http://localhost/app/faculties'
3784        self.browser.open(facs_path)
3785        self.browser.getLink("Export student data").click()
3786        self.browser.getControl("Configure new export").click()
3787        self.browser.getControl(name="exporter").value = ['bursary']
3788        self.browser.getControl(name="session").value = ['2004']
3789        self.browser.getControl(name="level").value = ['100']
3790        self.browser.getControl(name="mode").value = ['ug_ft']
3791        self.browser.getControl(name="payments_start").value = '13/12/2012'
3792        self.browser.getControl(name="payments_end").value = '14/12/2012'
3793        self.browser.getControl(name="paycat").value = ['schoolfee']
3794        self.browser.getControl("Create CSV file").click()
3795
3796        # When the job is finished and we reload the page...
3797        job_id = self.wait_for_export_job_completed()
3798        self.browser.open(facs_path + '/exports')
3799        # ... the csv file can be downloaded ...
3800        self.browser.getLink("Download").click()
3801        self.assertEqual(self.browser.headers['content-type'],
3802            'text/csv; charset=UTF-8')
3803        self.assertTrue(
3804            'filename="WAeUP.Kofa_bursary_%s.csv' % job_id in
3805            self.browser.headers['content-disposition'])
3806        self.assertTrue(
3807            '666,12.12,2012-12-13 00:00:00#,schoolfee,1,my-id,p-item,100,2012,'
3808            'paid,2012-12-13 00:00:00#,12.12,r-code,,K1000000,234,123,Anna,,'
3809            'Tester,created,2004,2004,,fac1,dep1,CERT1' in self.browser.contents)
3810        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3811        job_id = self.app['datacenter'].running_exports[0][0]
3812        # ... and discarded
3813        self.browser.open(facs_path + '/exports')
3814        self.browser.getControl("Discard").click()
3815        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3816        # Creation, downloading and discarding is logged
3817        logfile = os.path.join(
3818            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3819        logcontent = open(logfile).read()
3820        self.assertTrue(
3821            'zope.mgr - students.browser.FacultiesExportJobContainerJobConfig '
3822            '- exported: bursary (2004, 100, ug_ft, None, None, None, '
3823            '13/12/2012, 14/12/2012, all, all, schoolfee, all), job_id=%s'
3824            % job_id in logcontent
3825            )
3826        self.assertTrue(
3827            'zope.mgr - students.browser.ExportJobContainerDownload '
3828            '- downloaded: WAeUP.Kofa_bursary_%s.csv, job_id=%s'
3829            % (job_id, job_id) in logcontent
3830            )
3831        self.assertTrue(
3832            'zope.mgr - students.browser.ExportJobContainerOverview '
3833            '- discarded: job_id=%s' % job_id in logcontent
3834            )
3835
3836    def test_faculty_export(self):
3837        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3838        fac1_path = 'http://localhost/app/faculties/fac1'
3839        self.browser.open(fac1_path)
3840        self.browser.getLink("Export student data").click()
3841        self.browser.getControl("Configure new export").click()
3842        self.browser.getControl(name="exporter").value = ['students']
3843        self.browser.getControl(name="session").value = ['2004']
3844        self.browser.getControl(name="level").value = ['100']
3845        self.browser.getControl(name="mode").value = ['ug_ft']
3846        # The testbrowser does not hide the payment period fields, but
3847        # values are ignored when using the students exporter.
3848        self.browser.getControl(name="payments_start").value = '13/12/2012'
3849        self.browser.getControl(name="payments_end").value = '14/12/2012'
3850        self.browser.getControl("Create CSV file").click()
3851
3852        # When the job is finished and we reload the page...
3853        job_id = self.wait_for_export_job_completed()
3854        self.browser.open(fac1_path + '/exports')
3855        # ... the csv file can be downloaded ...
3856        self.browser.getLink("Download").click()
3857        self.assertEqual(self.browser.headers['content-type'],
3858            'text/csv; charset=UTF-8')
3859        self.assertTrue(
3860            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3861            self.browser.headers['content-disposition'])
3862        self.assertTrue(
3863            'adm_code,clr_code,date_of_birth,email,employer,'
3864            'firstname,flash_notice,lastname,matric_number,middlename,nationality,'
3865            'officer_comment,perm_address,personal_updated,phone,reg_number,'
3866            'sex,student_id,suspended,suspended_comment,'
3867            'password,state,history,certcode,is_postgrad,'
3868            'current_level,current_session\r\n'
3869            ',,1981-02-04#,aa@aa.ng,,Anna,,Tester,234,,,,,,'
3870            '1234#,123,m,K1000000,0,,{SSHA}' in self.browser.contents)
3871        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3872        job_id = self.app['datacenter'].running_exports[0][0]
3873        # ... and discarded
3874        self.browser.open(fac1_path + '/exports')
3875        self.browser.getControl("Discard").click()
3876        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3877        # Creation, downloading and discarding is logged
3878        logfile = os.path.join(
3879            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3880        logcontent = open(logfile).read()
3881        self.assertTrue(
3882            'zope.mgr - students.browser.FacultyExportJobContainerJobConfig '
3883            '- exported: students (2004, 100, ug_ft, fac1, None, None, '
3884            '13/12/2012, 14/12/2012, all, all, all, all), job_id=%s'
3885            % job_id in logcontent
3886            )
3887        self.assertTrue(
3888            'zope.mgr - students.browser.ExportJobContainerDownload '
3889            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3890            % (job_id, job_id) in logcontent
3891            )
3892        self.assertTrue(
3893            'zope.mgr - students.browser.ExportJobContainerOverview '
3894            '- discarded: job_id=%s' % job_id in logcontent
3895            )
3896
3897    def test_department_export(self):
3898        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3899        dep1_path = 'http://localhost/app/faculties/fac1/dep1'
3900        self.browser.open(dep1_path)
3901        self.browser.getLink("Export student data").click()
3902        self.browser.getControl("Configure new export").click()
3903        self.browser.getControl(name="exporter").value = ['students']
3904        self.browser.getControl(name="session").value = ['2004']
3905        self.browser.getControl(name="level").value = ['100']
3906        self.browser.getControl(name="mode").value = ['ug_ft']
3907        # The testbrowser does not hide the payment period fields, but
3908        # values are ignored when using the students exporter.
3909        self.browser.getControl(name="payments_start").value = '13/12/2012'
3910        self.browser.getControl(name="payments_end").value = '14/12/2012'
3911        self.browser.getControl("Create CSV file").click()
3912
3913        # When the job is finished and we reload the page...
3914        job_id = self.wait_for_export_job_completed()
3915        self.browser.open(dep1_path + '/exports')
3916        # ... the csv file can be downloaded ...
3917        self.browser.getLink("Download").click()
3918        self.assertEqual(self.browser.headers['content-type'],
3919            'text/csv; charset=UTF-8')
3920        self.assertTrue(
3921            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3922            self.browser.headers['content-disposition'])
3923        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3924        job_id = self.app['datacenter'].running_exports[0][0]
3925        # ... and discarded
3926        self.browser.open(dep1_path + '/exports')
3927        self.browser.getControl("Discard").click()
3928        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3929        # Creation, downloading and discarding is logged
3930        logfile = os.path.join(
3931            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3932        logcontent = open(logfile).read()
3933        self.assertTrue(
3934            'zope.mgr - students.browser.DepartmentExportJobContainerJobConfig '
3935            '- exported: students (2004, 100, ug_ft, None, dep1, None, '
3936            '13/12/2012, 14/12/2012, all, all, all, all), job_id=%s'
3937            % job_id in logcontent
3938            )
3939        self.assertTrue(
3940            'zope.mgr - students.browser.ExportJobContainerDownload '
3941            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3942            % (job_id, job_id) in logcontent
3943            )
3944        self.assertTrue(
3945            'zope.mgr - students.browser.ExportJobContainerOverview '
3946            '- discarded: job_id=%s' % job_id in logcontent
3947            )
3948
3949    def test_certificate_export(self):
3950        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3951        cert1_path = 'http://localhost/app/faculties/fac1/dep1/certificates/CERT1'
3952        self.browser.open(cert1_path)
3953        self.browser.getLink("Export student data").click()
3954        self.browser.getControl("Configure new export").click()
3955        self.browser.getControl(name="exporter").value = ['students']
3956        self.browser.getControl(name="session").value = ['2004']
3957        self.browser.getControl(name="level").value = ['100']
3958        self.browser.getControl("Create CSV file").click()
3959
3960        # When the job is finished and we reload the page...
3961        job_id = self.wait_for_export_job_completed()
3962        self.browser.open(cert1_path + '/exports')
3963        # ... the csv file can be downloaded ...
3964        self.browser.getLink("Download").click()
3965        self.assertEqual(self.browser.headers['content-type'],
3966            'text/csv; charset=UTF-8')
3967        self.assertTrue(
3968            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3969            self.browser.headers['content-disposition'])
3970        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3971        job_id = self.app['datacenter'].running_exports[0][0]
3972        # ... and discarded
3973        self.browser.open(cert1_path + '/exports')
3974        self.browser.getControl("Discard").click()
3975        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3976        # Creation, downloading and discarding is logged
3977        logfile = os.path.join(
3978            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3979        logcontent = open(logfile).read()
3980        self.assertTrue(
3981            'zope.mgr - students.browser.CertificateExportJobContainerJobConfig '
3982            '- exported: students '
3983            '(2004, 100, None, None, None, CERT1, , , None, None, None, None), '
3984            'job_id=%s'
3985            % job_id in logcontent
3986            )
3987        self.assertTrue(
3988            'zope.mgr - students.browser.ExportJobContainerDownload '
3989            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3990            % (job_id, job_id) in logcontent
3991            )
3992        self.assertTrue(
3993            'zope.mgr - students.browser.ExportJobContainerOverview '
3994            '- discarded: job_id=%s' % job_id in logcontent
3995            )
3996
3997    def deprecated_test_course_export_students(self):
3998        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3999        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
4000        self.browser.open(course1_path)
4001        self.browser.getLink("Export student data").click()
4002        self.browser.getControl("Configure new export").click()
4003        self.browser.getControl(name="exporter").value = ['students']
4004        self.browser.getControl(name="session").value = ['2004']
4005        self.browser.getControl(name="level").value = ['100']
4006        self.browser.getControl("Create CSV file").click()
4007
4008        # When the job is finished and we reload the page...
4009        job_id = self.wait_for_export_job_completed()
4010        self.browser.open(course1_path + '/exports')
4011        # ... the csv file can be downloaded ...
4012        self.browser.getLink("Download").click()
4013        self.assertEqual(self.browser.headers['content-type'],
4014            'text/csv; charset=UTF-8')
4015        self.assertTrue(
4016            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
4017            self.browser.headers['content-disposition'])
4018        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
4019        job_id = self.app['datacenter'].running_exports[0][0]
4020        # ... and discarded
4021        self.browser.open(course1_path + '/exports')
4022        self.browser.getControl("Discard").click()
4023        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
4024        # Creation, downloading and discarding is logged
4025        logfile = os.path.join(
4026            self.app['datacenter'].storage, 'logs', 'datacenter.log')
4027        logcontent = open(logfile).read()
4028        self.assertTrue(
4029            'zope.mgr - students.browser.CourseExportJobContainerJobConfig '
4030            '- exported: students (2004, 100, COURSE1), job_id=%s'
4031            % job_id in logcontent
4032            )
4033        self.assertTrue(
4034            'zope.mgr - students.browser.ExportJobContainerDownload '
4035            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
4036            % (job_id, job_id) in logcontent
4037            )
4038        self.assertTrue(
4039            'zope.mgr - students.browser.ExportJobContainerOverview '
4040            '- discarded: job_id=%s' % job_id in logcontent
4041            )
4042
4043    def test_course_export_lecturer(self):
4044        # We add study level 100 to the student's studycourse
4045        studylevel = StudentStudyLevel()
4046        studylevel.level = 100
4047        studylevel.level_session = 2004
4048        IWorkflowState(self.student).setState('courses validated')
4049        self.student['studycourse'].addStudentStudyLevel(
4050            self.certificate,studylevel)
4051        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
4052        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
4053        self.browser.open(course1_path)
4054        self.browser.getLink("Export student data").click()
4055        self.browser.getControl("Configure new export").click()
4056        self.assertTrue(
4057            'Academic session not set. Please contact the administrator.'
4058            in self.browser.contents)
4059        self.app['configuration'].current_academic_session = 2004
4060        self.browser.getControl("Configure new export").click()
4061        self.browser.getControl(name="exporter").value = ['lecturer']
4062        self.browser.getControl(name="session").value = ['2004']
4063        self.browser.getControl(name="level").value = ['100']
4064        self.browser.getControl("Create CSV file").click()
4065        # When the job is finished and we reload the page...
4066        job_id = self.wait_for_export_job_completed()
4067        self.browser.open(course1_path + '/exports')
4068        # ... the csv file can be downloaded ...
4069        self.browser.getLink("Download").click()
4070        self.assertEqual(self.browser.headers['content-type'],
4071            'text/csv; charset=UTF-8')
4072        self.assertTrue(
4073            'filename="WAeUP.Kofa_lecturer_%s.csv' % job_id in
4074            self.browser.headers['content-disposition'])
4075        # ... and contains the course ticket COURSE1
4076        self.assertEqual(self.browser.contents,
4077            'matric_number,student_id,display_fullname,level,code,'
4078            'level_session,score\r\n234,K1000000,Anna Tester,'
4079            '100,COURSE1,2004,\r\n')
4080        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
4081        job_id = self.app['datacenter'].running_exports[0][0]
4082        # Thew job can be discarded
4083        self.browser.open(course1_path + '/exports')
4084        self.browser.getControl("Discard").click()
4085        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
4086        # Creation, downloading and discarding is logged
4087        logfile = os.path.join(
4088            self.app['datacenter'].storage, 'logs', 'datacenter.log')
4089        logcontent = open(logfile).read()
4090        self.assertTrue(
4091            'zope.mgr - students.browser.CourseExportJobContainerJobConfig '
4092            '- exported: lecturer (2004, 100, COURSE1), job_id=%s'
4093            % job_id in logcontent
4094            )
4095        self.assertTrue(
4096            'zope.mgr - students.browser.ExportJobContainerDownload '
4097            '- downloaded: WAeUP.Kofa_lecturer_%s.csv, job_id=%s'
4098            % (job_id, job_id) in logcontent
4099            )
4100        self.assertTrue(
4101            'zope.mgr - students.browser.ExportJobContainerOverview '
4102            '- discarded: job_id=%s' % job_id in logcontent
4103            )
4104
4105    def test_export_departmet_officers(self):
4106        # Create department officer
4107        self.app['users'].addUser('mrdepartment', 'mrdepartmentsecret')
4108        self.app['users']['mrdepartment'].email = 'mrdepartment@foo.ng'
4109        self.app['users']['mrdepartment'].title = 'Carlo Pitter'
4110        # Assign local role
4111        department = self.app['faculties']['fac1']['dep1']
4112        prmlocal = IPrincipalRoleManager(department)
4113        prmlocal.assignRoleToPrincipal('waeup.local.DepartmentOfficer', 'mrdepartment')
4114        # Login as department officer
4115        self.browser.open(self.login_path)
4116        self.browser.getControl(name="form.login").value = 'mrdepartment'
4117        self.browser.getControl(name="form.password").value = 'mrdepartmentsecret'
4118        self.browser.getControl("Login").click()
4119        self.assertMatches('...You logged in...', self.browser.contents)
4120        self.browser.open("http://localhost/app/faculties/fac1/dep1")
4121        self.browser.getLink("Export student data").click()
4122        self.browser.getControl("Configure new export").click()
4123        # Only the sfpaymentsoverview exporter is available for department officers
4124        self.assertFalse('<option value="students">' in self.browser.contents)
4125        self.assertTrue(
4126            '<option value="sfpaymentsoverview">' in self.browser.contents)
4127        self.browser.getControl(name="exporter").value = ['sfpaymentsoverview']
4128        self.browser.getControl(name="session").value = ['2004']
4129        self.browser.getControl(name="level").value = ['100']
4130        self.browser.getControl("Create CSV file").click()
4131        self.assertTrue('Export started' in self.browser.contents)
4132        # Thew job can be discarded
4133        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
4134        self.wait_for_export_job_completed()
4135        self.browser.open("http://localhost/app/faculties/fac1/dep1/exports")
4136        self.browser.getControl("Discard").click()
4137        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
4138
4139    def test_export_bursary_officers(self):
4140        # Create bursary officer
4141        self.app['users'].addUser('mrbursary', 'mrbursarysecret')
4142        self.app['users']['mrbursary'].email = 'mrbursary@foo.ng'
4143        self.app['users']['mrbursary'].title = 'Carlo Pitter'
4144        prmglobal = IPrincipalRoleManager(self.app)
4145        prmglobal.assignRoleToPrincipal('waeup.BursaryOfficer', 'mrbursary')
4146        # Login as bursary officer
4147        self.browser.open(self.login_path)
4148        self.browser.getControl(name="form.login").value = 'mrbursary'
4149        self.browser.getControl(name="form.password").value = 'mrbursarysecret'
4150        self.browser.getControl("Login").click()
4151        self.assertMatches('...You logged in...', self.browser.contents)
4152        self.browser.getLink("Academics").click()
4153        self.browser.getLink("Export student data").click()
4154        self.browser.getControl("Configure new export").click()
4155        # Only the bursary exporter is available for bursary officers
4156        # not only at facultiescontainer level ...
4157        self.assertFalse('<option value="students">' in self.browser.contents)
4158        self.assertTrue('<option value="bursary">' in self.browser.contents)
4159        self.browser.getControl(name="exporter").value = ['bursary']
4160        self.browser.getControl(name="session").value = ['2004']
4161        self.browser.getControl(name="level").value = ['100']
4162        self.browser.getControl("Create CSV file").click()
4163        self.assertTrue('Export started' in self.browser.contents)
4164        # ... but also at other levels
4165        self.browser.open('http://localhost/app/faculties/fac1/dep1')
4166        self.browser.getLink("Export student data").click()
4167        self.browser.getControl("Configure new export").click()
4168        self.assertFalse('<option value="students">' in self.browser.contents)
4169        self.assertTrue('<option value="bursary">' in self.browser.contents)
4170        # Thew job can be discarded
4171        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
4172        self.wait_for_export_job_completed()
4173        self.browser.open('http://localhost/app/faculties/exports')
4174        self.browser.getControl("Discard").click()
4175        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
4176
4177
4178UPLOAD_CSV_TEMPLATE = (
4179    'matric_number,student_id,display_fullname,level,code,level_session,'
4180    'score\r\n'
4181    '234,K1000000,Anna Tester,100,COURSE1,2004,%s\r\n')
4182
4183class LecturerUITests(StudentsFullSetup):
4184    # Tests for UI actions when acting as lecturer.
4185
4186    def login_as_lecturer(self):
4187        self.app['users'].addUser('mrslecturer', 'mrslecturersecret')
4188        self.app['users']['mrslecturer'].email = 'mrslecturer@foo.ng'
4189        self.app['users']['mrslecturer'].title = u'Mercedes Benz'
4190        # Add course ticket
4191        studylevel = createObject(u'waeup.StudentStudyLevel')
4192        studylevel.level = 100
4193        studylevel.level_session = 2004
4194        self.student['studycourse'].addStudentStudyLevel(
4195            self.certificate, studylevel)
4196        # Assign local Lecturer role for a course.
4197        course = self.app['faculties']['fac1']['dep1'].courses['COURSE1']
4198        prmlocal = IPrincipalRoleManager(course)
4199        prmlocal.assignRoleToPrincipal('waeup.local.Lecturer', 'mrslecturer')
4200        notify(LocalRoleSetEvent(
4201            course, 'waeup.local.Lecturer', 'mrslecturer', granted=True))
4202        # Login as lecturer.
4203        self.browser.open(self.login_path)
4204        self.browser.getControl(name="form.login").value = 'mrslecturer'
4205        self.browser.getControl(
4206            name="form.password").value = 'mrslecturersecret'
4207        self.browser.getControl("Login").click()
4208        # Store reused urls/paths
4209        self.course_url = (
4210            'http://localhost/app/faculties/fac1/dep1/courses/COURSE1')
4211        self.edit_scores_url = '%s/edit_scores' % self.course_url
4212        # Set standard parameters
4213        self.app['configuration'].current_academic_session = 2004
4214        self.app['faculties']['fac1']['dep1'].score_editing_disabled = False
4215        IWorkflowState(self.student).setState(VALIDATED)
4216
4217    @property
4218    def stud_log_path(self):
4219        return os.path.join(
4220            self.app['datacenter'].storage, 'logs', 'students.log')
4221
4222    def test_lecturer_lands_on_landing_page(self):
4223        # lecturers can login and will be led to landing page.
4224        self.login_as_lecturer()
4225        self.assertMatches('...You logged in...', self.browser.contents)
4226        self.assertEqual(self.browser.url, URL_LECTURER_LANDING)
4227        self.assertTrue(
4228            "<span>Unnamed Course (COURSE1)</span>"
4229            in self.browser.contents)
4230
4231    def test_lecturer_department_role(self):
4232        # lecturers can login and will be led to landing page also if
4233        # role is assigned at department level.
4234        self.login_as_lecturer()
4235        # we remove the role granted above
4236        course = self.app['faculties']['fac1']['dep1'].courses['COURSE1']
4237        prmlocal = IPrincipalRoleManager(course)
4238        prmlocal.removeRoleFromPrincipal('waeup.local.Lecturer', 'mrslecturer')
4239        notify(LocalRoleSetEvent(
4240            course, 'waeup.local.Lecturer', 'mrslecturer', granted=False))
4241        self.browser.open(URL_LECTURER_LANDING)
4242        # no course appears
4243        self.assertFalse(
4244            "<span>Unnamed Course (COURSE1)</span>"
4245            in self.browser.contents)
4246        # we assign lecturer at department level
4247        dep = self.app['faculties']['fac1']['dep1']
4248        prmlocal = IPrincipalRoleManager(dep)
4249        prmlocal.assignRoleToPrincipal('waeup.local.Lecturer', 'mrslecturer')
4250        notify(LocalRoleSetEvent(
4251            dep, 'waeup.local.Lecturer', 'mrslecturer', granted=True))
4252        self.browser.open(URL_LECTURER_LANDING)
4253        # course appears again
4254        self.assertTrue(
4255            "<span>Unnamed Course (COURSE1)</span>"
4256            in self.browser.contents)
4257
4258    def test_my_roles_link_works(self):
4259        # lecturers can see their roles
4260        self.login_as_lecturer()
4261        self.browser.getLink("My Roles").click()
4262        self.assertTrue(
4263            "<div>Academics Officer (view only)</div>"
4264            in self.browser.contents)
4265        self.assertTrue(
4266            '<a href="%s">' % self.course_url in self.browser.contents)
4267
4268    def test_my_roles_page_contains_backlink(self):
4269        # we can get back from 'My Roles' view to landing page
4270        self.login_as_lecturer()
4271        self.browser.getLink("My Roles").click()
4272        self.browser.getLink("My Courses").click()
4273        self.assertEqual(self.browser.url, URL_LECTURER_LANDING)
4274
4275    def test_lecturers_can_reach_their_courses(self):
4276        # lecturers get links to their courses on the landing page
4277        self.login_as_lecturer()
4278        self.browser.getLink("COURSE1").click()
4279        self.assertEqual(self.browser.url, self.course_url)
4280
4281    def test_lecturers_student_access_is_restricted(self):
4282        # lecturers are not able to change other student data
4283        self.login_as_lecturer()
4284        # Lecturers can neither filter students ...
4285        self.assertRaises(
4286            Unauthorized, self.browser.open, '%s/students' % self.course_url)
4287        # ... nor access the student ...
4288        self.assertRaises(
4289            Unauthorized, self.browser.open, self.student_path)
4290        # ... nor the respective course ticket since editing course
4291        # tickets by lecturers is not feasible.
4292        self.assertTrue('COURSE1' in self.student['studycourse']['100'].keys())
4293        course_ticket_path = self.student_path + '/studycourse/100/COURSE1'
4294        self.assertRaises(
4295            Unauthorized, self.browser.open, course_ticket_path)
4296
4297    def test_score_editing_requires_department_permit(self):
4298        # we get a warning if we try to update score while we are not allowed
4299        self.login_as_lecturer()
4300        self.app['faculties']['fac1']['dep1'].score_editing_disabled = True
4301        self.browser.open(self.course_url)
4302        self.browser.getLink("Update session 2004/2005 scores").click()
4303        self.assertTrue('Score editing disabled' in self.browser.contents)
4304        self.app['faculties']['fac1']['dep1'].score_editing_disabled = False
4305        self.browser.open(self.course_url)
4306        self.browser.getLink("Update session 2004/2005 scores").click()
4307        self.assertFalse('Score editing disabled' in self.browser.contents)
4308
4309    def test_score_editing_requires_validated_students(self):
4310        # we can edit only scores of students whose courses have been
4311        # validated.
4312        self.login_as_lecturer()
4313        # set invalid student state
4314        IWorkflowState(self.student).setState(CREATED)
4315        self.browser.open(self.edit_scores_url)
4316        self.assertRaises(
4317            LookupError, self.browser.getControl, name="scores")
4318        # set valid student state
4319        IWorkflowState(self.student).setState(VALIDATED)
4320        self.browser.open(self.edit_scores_url)
4321        self.assertTrue(
4322            self.browser.getControl(name="scores:list") is not None)
4323
4324    def test_score_editing_offers_only_current_scores(self):
4325        # only scores from current academic session can be edited
4326        self.login_as_lecturer()
4327        IWorkflowState(self.student).setState('courses validated')
4328        # with no academic session set
4329        self.app['configuration'].current_academic_session = None
4330        self.browser.open(self.edit_scores_url)
4331        self.assertRaises(
4332            LookupError, self.browser.getControl, name="scores")
4333        # with wrong academic session set
4334        self.app['configuration'].current_academic_session = 1999
4335        self.browser.open(self.edit_scores_url)
4336        self.assertRaises(
4337            LookupError, self.browser.getControl, name="scores")
4338        # with right academic session set
4339        self.app['configuration'].current_academic_session = 2004
4340        self.browser.reload()
4341        self.assertTrue(
4342            self.browser.getControl(name="scores:list") is not None)
4343
4344    def test_score_editing_can_change_scores(self):
4345        # we can really change scores via edit_scores view
4346        self.login_as_lecturer()
4347        self.assertEqual(
4348            self.student['studycourse']['100']['COURSE1'].score, None)
4349        self.browser.open(self.edit_scores_url)
4350        self.browser.getControl(name="scores:list", index=0).value = '55'
4351        self.browser.getControl("Update scores").click()
4352        # the new value is stored in data
4353        self.assertEqual(
4354            self.student['studycourse']['100']['COURSE1'].score, 55)
4355        # the new value is displayed on page/prefilled in form
4356        self.assertEqual(
4357            self.browser.getControl(name="scores:list", index=0).value, '55')
4358        # The change has been logged
4359        with open(self.stud_log_path, 'r') as fd:
4360            self.assertTrue(
4361                'mrslecturer - students.browser.EditScoresPage - '
4362                'K1000000 100/COURSE1 score updated (55)' in fd.read())
4363
4364    def test_scores_editing_scores_must_be_integers(self):
4365        # Non-integer scores won't be accepted.
4366        self.login_as_lecturer()
4367        self.browser.open(self.edit_scores_url)
4368        self.browser.getControl(name="scores:list", index=0).value = 'abc'
4369        self.browser.getControl("Update scores").click()
4370        self.assertTrue(
4371            'Error: Score(s) of following students have not been updated '
4372            '(only integers are allowed): Anna Tester.'
4373            in self.browser.contents)
4374
4375    def test_scores_editing_allows_score_removal(self):
4376        # we can remove scores, once they were set
4377        self.login_as_lecturer()
4378        # without a prior value, we cannot remove
4379        self.student['studycourse']['100']['COURSE1'].score = None
4380        self.browser.open(self.edit_scores_url)
4381        self.browser.getControl(name="scores:list", index=0).value = ''
4382        self.browser.getControl("Update scores").click()
4383        logcontent = open(self.stud_log_path, 'r').read()
4384        self.assertFalse('COURSE1 score updated (None)' in logcontent)
4385        # now retry with some value set
4386        self.student['studycourse']['100']['COURSE1'].score = 55
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.assertTrue('COURSE1 score updated (None)' in logcontent)
4391
4392    def test_lecturers_can_download_course_tickets(self):
4393        # A course ticket slip can be downloaded
4394        self.login_as_lecturer()
4395        pdf_url = '%s/coursetickets.pdf' % self.course_url
4396        self.browser.open(pdf_url)
4397        self.assertEqual(self.browser.headers['Status'], '200 Ok')
4398        self.assertEqual(
4399            self.browser.headers['Content-Type'], 'application/pdf')
4400        path = os.path.join(samples_dir(), 'coursetickets.pdf')
4401        open(path, 'wb').write(self.browser.contents)
4402        print "Sample PDF coursetickets.pdf written to %s" % path
4403
4404    def test_lecturers_can_download_scores_as_csv(self):
4405        # Lecturers can download course scores as CSV.
4406        self.login_as_lecturer()
4407        self.browser.open(self.edit_scores_url)
4408        self.browser.getLink("Download csv file").click()
4409        self.assertEqual(self.browser.headers['Status'], '200 Ok')
4410        self.assertEqual(self.browser.headers['Content-Type'],
4411                         'text/csv; charset=UTF-8')
4412        self.assertEqual(self.browser.contents, 'matric_number,student_id,'
4413            'display_fullname,level,code,level_session,score\r\n234,'
4414            'K1000000,Anna Tester,100,COURSE1,2004,\r\n')
4415
4416    def test_scores_csv_upload_available(self):
4417        # lecturers can upload a CSV file to set values.
4418        self.login_as_lecturer()
4419        # set value to change from
4420        self.student['studycourse']['100']['COURSE1'].score = 55
4421        self.browser.open(self.edit_scores_url)
4422        upload_ctrl = self.browser.getControl(name='uploadfile:file')
4423        upload_file = StringIO(UPLOAD_CSV_TEMPLATE % '65')
4424        upload_ctrl.add_file(upload_file, 'text/csv', 'myscores.csv')
4425        self.browser.getControl("Update editable scores from").click()
4426        # value changed
4427        self.assertEqual(
4428            self.student['studycourse']['100']['COURSE1'].score, 65)
4429
4430    def test_scores_csv_upload_ignored(self):
4431        # for many type of file contents we simply ignore uploaded data
4432        self.login_as_lecturer()
4433        self.student['studycourse']['100']['COURSE1'].score = 55
4434        self.browser.open(self.edit_scores_url)
4435        for content, mimetype, name in (
4436                # empty file
4437                ('', 'text/foo', 'my.foo'),
4438                # plain ASCII text, w/o comma
4439                ('abcdef' * 200, 'text/plain', 'my.txt'),
4440                # plain UTF-8 text, with umlauts
4441                ('umlauts: äöü', 'text/plain', 'my.txt'),
4442                # csv file with only a header row
4443                ('student_id,score', 'text/csv', 'my.csv'),
4444                # csv with student_id column missing
4445                ('foo,score\r\nbar,66\r\n', 'text/csv', 'my.csv'),
4446                # csv with score column missing
4447                ('student_id,foo\r\nK1000000,bar\r\n', 'text/csv', 'my.csv'),
4448                # csv with non number as score value
4449                (UPLOAD_CSV_TEMPLATE % 'not-a-number', 'text/csv', 'my.csv'),
4450                ):
4451            upload_ctrl = self.browser.getControl(name='uploadfile:file')
4452            upload_ctrl.add_file(StringIO(content), mimetype, name)
4453            self.browser.getControl("Update scores").click()
4454            self.assertEqual(
4455                self.student['studycourse']['100']['COURSE1'].score, 55)
4456            self.assertFalse(
4457                'Uploaded file contains illegal data' in self.browser.contents)
4458
4459    def test_scores_csv_upload_warn_illegal_chars(self):
4460        # for some types of files we issue a warning if upload data
4461        # contains illegal chars (and ignore the data)
4462        self.login_as_lecturer()
4463        self.student['studycourse']['100']['COURSE1'].score = 55
4464        self.browser.open(self.edit_scores_url)
4465        for content, mimetype, name in (
4466                # plain ASCII text, commas, control chars
4467                ('abv,qwe\n\r\r\t\b\n' * 20, 'text/plain', 'my.txt'),
4468                # image data (like a JPEG image)
4469                (open(SAMPLE_IMAGE, 'rb').read(), 'image/jpg', 'my.jpg'),
4470                ):
4471            upload_ctrl = self.browser.getControl(name='uploadfile:file')
4472            upload_ctrl.add_file(StringIO(content), mimetype, name)
4473            self.browser.getControl("Update editable scores").click()
4474            self.assertEqual(
4475                self.student['studycourse']['100']['COURSE1'].score, 55)
4476            self.assertTrue(
4477                'Uploaded file contains illegal data' in self.browser.contents)
Note: See TracBrowser for help on using the repository browser.