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

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

Improve AddStudyLevelFormPage.

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