source: main/waeup.kofa/branches/uli-rm-bootstrap/src/waeup/kofa/students/tests/test_browser.py @ 17651

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

Add payment session filter to payment exporters.

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