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

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

Stored insecure passwords are no longer accepted.
Officers with an insecure password can't login and are
redirected to the ChangePasswordRequestPage to request a
new password.

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