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

Last change on this file since 13544 was 13492, checked in by Henrik Bettermann, 9 years ago

Disable rendering of HTML tags in fullnames.

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