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

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

Add button and view which allow students to unregister their
current course list unless the courses have not been validated.

  • Property svn:keywords set to Id
File size: 201.6 KB
Line 
1## $Id: test_browser.py 13610 2016-01-13 20:59:17Z 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_manage_contact_student(self):
549        # Managers can contact student
550        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
551        # Remove required FieldProperty attribute first ...
552        delattr(Student, 'email')
553        # ... and replace by None
554        self.student.email = None
555        # Now we have to add  the FieldProperty attribute again. Otherwise
556        # many other tests below will fail.
557        iface = list(implementedBy(Student))[0]
558        field_property = FieldProperty(iface['email'])
559        setattr(Student, 'email', field_property)
560        self.browser.open(self.student_path)
561        self.browser.getLink("Send email").click()
562        self.browser.getControl(name="form.subject").value = 'Important subject'
563        self.browser.getControl(name="form.body").value = 'Hello!'
564        self.browser.getControl("Send message now").click()
565        self.assertTrue('An smtp server error occurred' in self.browser.contents)
566        self.student.email = 'xx@yy.zz'
567        self.browser.getControl("Send message now").click()
568        self.assertTrue('Your message has been sent' in self.browser.contents)
569        return
570
571    def test_manage_remove_department(self):
572        # Lazy student is studying CERT1
573        lazystudent = Student()
574        lazystudent.firstname = u'Lazy'
575        lazystudent.lastname = u'Student'
576        self.app['students'].addStudent(lazystudent)
577        student_id = lazystudent.student_id
578        student_path = self.container_path + '/' + student_id
579        lazystudent['studycourse'].certificate = self.certificate
580        notify(grok.ObjectModifiedEvent(lazystudent))
581        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
582        self.browser.open(student_path + '/studycourse')
583        self.assertTrue('CERT1' in self.browser.contents)
584        # After some years the department is removed
585        del self.app['faculties']['fac1']['dep1']
586        # So CERT1 does no longer exist and lazy student's
587        # certificate reference is removed too
588        self.browser.open(student_path + '/studycourse')
589        self.assertEqual(self.browser.headers['Status'], '200 Ok')
590        self.assertEqual(self.browser.url, student_path + '/studycourse')
591        self.assertFalse('CERT1' in self.browser.contents)
592        self.assertMatches('...<div>--</div>...',
593                           self.browser.contents)
594
595    def test_manage_upload_file(self):
596        # Managers can upload a file via the StudentClearanceManageFormPage
597        # The image is stored even if form has errors
598        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
599        self.browser.open(self.manage_clearance_path)
600        # No birth certificate has been uploaded yet
601        # Browsing the link shows a placerholder image
602        self.browser.open('birth_certificate')
603        self.assertEqual(
604            self.browser.headers['content-type'], 'image/jpeg')
605        self.assertEqual(len(self.browser.contents), PH_LEN)
606        # Create a pseudo image file and select it to be uploaded in form
607        # as birth certificate
608        self.browser.open(self.manage_clearance_path)
609        image = open(SAMPLE_IMAGE, 'rb')
610        ctrl = self.browser.getControl(name='birthcertificateupload')
611        file_ctrl = ctrl.mech_control
612        file_ctrl.add_file(image, filename='my_birth_certificate.jpg')
613        # The Save action does not upload files
614        self.browser.getControl("Save").click() # submit form
615        self.assertFalse(
616            '<a target="image" href="birth_certificate">'
617            in self.browser.contents)
618        # ... but the correct upload submit button does
619        image = open(SAMPLE_IMAGE)
620        ctrl = self.browser.getControl(name='birthcertificateupload')
621        file_ctrl = ctrl.mech_control
622        file_ctrl.add_file(image, filename='my_birth_certificate.jpg')
623        self.browser.getControl(
624            name='upload_birthcertificateupload').click()
625        # There is a correct <img> link included
626        self.assertTrue(
627            'href="http://localhost/app/students/K1000000/birth_certificate"'
628            in self.browser.contents)
629        # Browsing the link shows a real image
630        self.browser.open('birth_certificate')
631        self.assertEqual(
632            self.browser.headers['content-type'], 'image/jpeg')
633        self.assertEqual(len(self.browser.contents), 2787)
634        # We can't reupload a file. The existing file must be deleted first.
635        self.browser.open(self.manage_clearance_path)
636        self.assertFalse(
637            'upload_birthcertificateupload' in self.browser.contents)
638        # File must be deleted first
639        self.browser.getControl(name='delete_birthcertificateupload').click()
640        self.assertTrue(
641            'birth_certificate deleted' in self.browser.contents)
642        # Uploading a file which is bigger than 150k will raise an error
643        big_image = StringIO(open(SAMPLE_IMAGE, 'rb').read() * 75)
644        ctrl = self.browser.getControl(name='birthcertificateupload')
645        file_ctrl = ctrl.mech_control
646        file_ctrl.add_file(big_image, filename='my_birth_certificate.jpg')
647        self.browser.getControl(
648            name='upload_birthcertificateupload').click()
649        self.assertTrue(
650            'Uploaded file is too big' in self.browser.contents)
651        # we do not rely on filename extensions given by uploaders
652        image = open(SAMPLE_IMAGE, 'rb') # a jpg-file
653        ctrl = self.browser.getControl(name='birthcertificateupload')
654        file_ctrl = ctrl.mech_control
655        # tell uploaded file is bmp
656        file_ctrl.add_file(image, filename='my_birth_certificate.bmp')
657        self.browser.getControl(
658            name='upload_birthcertificateupload').click()
659        self.assertTrue(
660            # jpg file was recognized
661            'File birth_certificate.jpg uploaded.' in self.browser.contents)
662        # Delete file again
663        self.browser.getControl(name='delete_birthcertificateupload').click()
664        self.assertTrue(
665            'birth_certificate deleted' in self.browser.contents)
666        # File names must meet several conditions
667        bmp_image = open(SAMPLE_IMAGE_BMP, 'rb')
668        ctrl = self.browser.getControl(name='birthcertificateupload')
669        file_ctrl = ctrl.mech_control
670        file_ctrl.add_file(bmp_image, filename='my_birth_certificate.bmp')
671        self.browser.getControl(
672            name='upload_birthcertificateupload').click()
673        self.assertTrue('Only the following extensions are allowed'
674            in self.browser.contents)
675
676        # Managers can upload a file via the StudentBaseManageFormPage
677        self.browser.open(self.manage_student_path)
678        image = open(SAMPLE_IMAGE_BMP, 'rb')
679        ctrl = self.browser.getControl(name='passportuploadmanage')
680        file_ctrl = ctrl.mech_control
681        file_ctrl.add_file(image, filename='my_photo.bmp')
682        self.browser.getControl(
683            name='upload_passportuploadmanage').click()
684        self.assertTrue('jpg file format expected'
685            in self.browser.contents)
686        ctrl = self.browser.getControl(name='passportuploadmanage')
687        file_ctrl = ctrl.mech_control
688        image = open(SAMPLE_IMAGE, 'rb')
689        file_ctrl.add_file(image, filename='my_photo.jpg')
690        self.browser.getControl(
691            name='upload_passportuploadmanage').click()
692        self.assertTrue(
693            'src="http://localhost/app/students/K1000000/passport.jpg"'
694            in self.browser.contents)
695        # We remove the passport file again
696        self.browser.open(self.manage_student_path)
697        self.browser.getControl('Delete').click()
698        self.browser.open(self.student_path + '/clearance_slip.pdf')
699        self.assertEqual(self.browser.headers['Status'], '200 Ok')
700        self.assertEqual(self.browser.headers['Content-Type'],
701                         'application/pdf')
702        # We want to see the signature fields.
703        IWorkflowState(self.student).setState('cleared')
704        self.browser.open(self.student_path + '/clearance_slip.pdf')
705        self.assertEqual(self.browser.headers['Status'], '200 Ok')
706        self.assertEqual(self.browser.headers['Content-Type'],
707                         'application/pdf')
708        path = os.path.join(samples_dir(), 'clearance_slip.pdf')
709        open(path, 'wb').write(self.browser.contents)
710        print "Sample PDF clearance_slip.pdf written to %s" % path
711
712    def test_manage_course_lists(self):
713        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
714        self.browser.open(self.student_path)
715        self.browser.getLink("Study Course").click()
716        self.assertEqual(self.browser.headers['Status'], '200 Ok')
717        self.assertEqual(self.browser.url, self.studycourse_path)
718        self.assertTrue('Undergraduate Full-Time' in self.browser.contents)
719        self.browser.getLink("Manage").click()
720        self.assertTrue('Manage study course' in self.browser.contents)
721        # Before we can select a level, the certificate must
722        # be selected and saved
723        self.browser.getControl(name="form.certificate").value = ['CERT1']
724        self.browser.getControl(name="form.current_session").value = ['2004']
725        self.browser.getControl(name="form.current_verdict").value = ['A']
726        self.browser.getControl(name="form.entry_mode").value = ['ug_ft']
727        self.browser.getControl("Save").click()
728        # Now we can save also the current level which depends on start and end
729        # level of the certificate
730        self.browser.getControl(name="form.current_level").value = ['100']
731        self.browser.getControl("Save").click()
732        # Managers can add and remove any study level (course list)
733        self.browser.getControl(name="addlevel").value = ['100']
734        self.browser.getControl("Add study level").click()
735        self.assertMatches(
736            '...You must select a session...', self.browser.contents)
737        self.browser.getControl(name="addlevel").value = ['100']
738        self.browser.getControl(name="level_session").value = ['2004']
739        self.browser.getControl("Add study level").click()
740        self.assertMatches('...<span>100</span>...', self.browser.contents)
741        self.assertEqual(self.student['studycourse']['100'].level, 100)
742        self.assertEqual(self.student['studycourse']['100'].level_session, 2004)
743        self.browser.getControl(name="addlevel").value = ['100']
744        self.browser.getControl(name="level_session").value = ['2004']
745        self.browser.getControl("Add study level").click()
746        self.assertMatches('...This level exists...', self.browser.contents)
747        self.browser.getControl("Remove selected").click()
748        self.assertMatches(
749            '...No study level selected...', self.browser.contents)
750        self.browser.getControl(name="val_id").value = ['100']
751        self.browser.getControl(name="level_session").value = ['2004']
752        self.browser.getControl("Remove selected").click()
753        self.assertMatches('...Successfully removed...', self.browser.contents)
754        # Removing levels is properly logged
755        logfile = os.path.join(
756            self.app['datacenter'].storage, 'logs', 'students.log')
757        logcontent = open(logfile).read()
758        self.assertTrue('zope.mgr - students.browser.StudyCourseManageFormPage '
759                        '- K1000000 - removed: 100' in logcontent)
760        # Add level again
761        self.browser.getControl(name="addlevel").value = ['100']
762        self.browser.getControl(name="level_session").value = ['2004']
763        self.browser.getControl("Add study level").click()
764
765        # Managers can view and manage course lists
766        self.browser.getLink("100").click()
767        self.assertMatches(
768            '...: Study Level 100 (Year 1)...', self.browser.contents)
769        self.browser.getLink("Manage").click()
770        self.browser.getControl(name="form.level_session").value = ['2002']
771        self.browser.getControl("Save").click()
772        self.browser.getControl("Remove selected").click()
773        self.assertMatches('...No ticket selected...', self.browser.contents)
774        ctrl = self.browser.getControl(name='val_id')
775        ctrl.getControl(value='COURSE1').selected = True
776        self.browser.getControl("Remove selected", index=0).click()
777        self.assertTrue('Successfully removed' in self.browser.contents)
778        # Removing course tickets is properly logged
779        logfile = os.path.join(
780            self.app['datacenter'].storage, 'logs', 'students.log')
781        logcontent = open(logfile).read()
782        self.assertTrue('zope.mgr - students.browser.StudyLevelManageFormPage '
783        '- K1000000 - removed: COURSE1 at 100' in logcontent)
784        self.browser.getLink("here").click()
785        self.browser.getControl(name="form.course").value = ['COURSE1']
786        self.course.credits = 100
787        self.browser.getControl("Add course ticket").click()
788        self.assertMatches(
789            '...Total credits exceed 50...', self.browser.contents)
790        self.course.credits = 10
791        self.browser.getControl("Add course ticket").click()
792        self.assertTrue('Successfully added' in self.browser.contents)
793        # We can do the same by adding the course on the manage page directly
794        del self.student['studycourse']['100']['COURSE1']
795        self.browser.getControl(name="course").value = 'COURSE1'
796        self.browser.getControl("Add course ticket").click()
797        self.assertTrue('Successfully added' in self.browser.contents)
798        self.browser.getLink("here").click()
799        self.browser.getControl(name="form.course").value = ['COURSE1']
800        self.browser.getControl("Add course ticket").click()
801        self.assertTrue('The ticket exists' in self.browser.contents)
802        self.browser.getControl("Cancel").click()
803        self.browser.getLink("COURSE1").click()
804        self.browser.getLink("Manage").click()
805        self.browser.getControl("Save").click()
806        self.assertTrue('Form has been saved' in self.browser.contents)
807        # Grade and weight have been determined
808        self.browser.open(self.studycourse_path + '/100/COURSE1')
809        self.assertFalse('Grade' in self.browser.contents)
810        self.assertFalse('Weight' in self.browser.contents)
811        self.student['studycourse']['100']['COURSE1'].score = 55
812        self.browser.open(self.studycourse_path + '/100/COURSE1')
813        self.assertTrue('Grade' in self.browser.contents)
814        self.assertTrue('Weight' in self.browser.contents)
815        self.assertEqual(self.student['studycourse']['100']['COURSE1'].grade, 'C')
816        self.assertEqual(self.student['studycourse']['100']['COURSE1'].weight, 3)
817        # We add another ticket to check if GPA will be correctly calculated
818        # (and rounded)
819        courseticket = createObject('waeup.CourseTicket')
820        courseticket.code = 'ANYCODE'
821        courseticket.title = u'Any TITLE'
822        courseticket.credits = 13
823        courseticket.score = 66
824        courseticket.semester = 1
825        courseticket.dcode = u'ANYDCODE'
826        courseticket.fcode = u'ANYFCODE'
827        self.student['studycourse']['100']['COURSE2'] = courseticket
828        self.browser.open(self.student_path + '/studycourse/100')
829        # total credits
830        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[1], 23)
831        # weigheted credits = 3 * 10 + 4 * 13
832        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[2], 82.0)
833        # sgpa = 82 / 23
834        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[0], 3.565)
835        # Carry-over courses will be collected when next level is created
836        self.browser.open(self.student_path + '/studycourse/manage')
837        # Add next level
838        self.student['studycourse']['100']['COURSE1'].score = 10
839        self.browser.getControl(name="addlevel").value = ['200']
840        self.browser.getControl(name="level_session").value = ['2005']
841        self.browser.getControl("Add study level").click()
842        self.browser.getLink("200").click()
843        self.assertMatches(
844            '...: Study Level 200 (Year 2)...', self.browser.contents)
845        # Since COURSE1 has score 10 it becomes a carry-over course
846        # in level 200
847        self.assertEqual(
848            sorted(self.student['studycourse']['200'].keys()), [u'COURSE1'])
849        self.assertTrue(
850            self.student['studycourse']['200']['COURSE1'].carry_over)
851        # Passed and failed courses have been counted
852        self.assertEqual(
853            self.student['studycourse']['100'].passed_params,
854            (1, 1, 13, 10, ['COURSE1']))
855        self.assertEqual(
856            self.student['studycourse']['200'].passed_params,
857            (0, 0, 0, 0, []))
858        # And also cumulative params can be calculated. Meanwhile we have the
859        # following courses: COURSE1 and COURSE2 in level 100 and
860        # COURSE1 as carry-over course in level 200.
861        self.assertEqual(
862            self.student['studycourse']['100'].cumulative_params,
863            (2.261, 23, 52.0, 23, 13))
864        # COURSE1 in level 200 is not taken into consideration
865        # when calculating the gpa.
866        self.assertEqual(
867            self.student['studycourse']['200'].cumulative_params,
868            (2.261, 23, 52.0, 33, 13))
869        return
870
871    def test_gpa_calculation_with_carryover(self):
872        studylevel = createObject(u'waeup.StudentStudyLevel')
873        studylevel.level = 100
874        studylevel.level_session = 2005
875        self.student['studycourse'].entry_mode = 'ug_ft'
876        self.student['studycourse'].addStudentStudyLevel(
877            self.certificate, studylevel)
878        # First course has been added automatically.
879        # Set score above passmark.
880        studylevel['COURSE1'].score = studylevel['COURSE1'].passmark + 1
881        # GPA is 1.
882        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[0], 1.0)
883        # Set score below passmark.
884        studylevel['COURSE1'].score = studylevel['COURSE1'].passmark - 1
885        # GPA is still 0.
886        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[0], 0.0)
887        studylevel2 = createObject(u'waeup.StudentStudyLevel')
888        studylevel2.level = 200
889        studylevel2.level_session = 2006
890        self.student['studycourse'].addStudentStudyLevel(
891            self.certificate, studylevel2)
892        # Carry-over course has been autonatically added.
893        studylevel2['COURSE1'].score = 66
894        # The score of the carry-over course is now used for calculation of the
895        # GPA at level 100 ...
896        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[0], 4.0)
897        # ... but not at level 200
898        self.assertEqual(self.student['studycourse']['200'].gpa_params_rectified[0], 0.0)
899        return
900
901    def test_manage_payments(self):
902        # Managers can add online school fee payment tickets
903        # if certain requirements are met
904        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
905        self.browser.open(self.payments_path)
906        IWorkflowState(self.student).setState('cleared')
907        self.browser.getLink("Add current session payment ticket").click()
908        self.browser.getControl(name="form.p_category").value = ['schoolfee']
909        self.browser.getControl("Create ticket").click()
910        self.assertMatches('...ticket created...',
911                           self.browser.contents)
912        ctrl = self.browser.getControl(name='val_id')
913        value = ctrl.options[0]
914        self.browser.getLink(value).click()
915        self.assertMatches('...Amount Authorized...',
916                           self.browser.contents)
917        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
918        payment_url = self.browser.url
919        logfile = os.path.join(
920            self.app['datacenter'].storage, 'logs', 'students.log')
921        logcontent = open(logfile).read()
922        self.assertTrue(
923            ' zope.mgr - students.browser.OnlinePaymentAddFormPage - '
924            'K1000000 - added: %s' % value
925            in logcontent)
926        # The pdf payment slip can't yet be opened
927        #self.browser.open(payment_url + '/payment_slip.pdf')
928        #self.assertMatches('...Ticket not yet paid...',
929        #                   self.browser.contents)
930
931        # The same payment (with same p_item, p_session and p_category)
932        # can be initialized a second time if the former ticket is not yet paid.
933        self.browser.open(self.payments_path)
934        self.browser.getLink("Add current session payment ticket").click()
935        self.browser.getControl(name="form.p_category").value = ['schoolfee']
936        self.browser.getControl("Create ticket").click()
937        self.assertMatches('...Payment ticket created...',
938                           self.browser.contents)
939
940        # The ticket can be found in the payments_catalog
941        cat = queryUtility(ICatalog, name='payments_catalog')
942        results = list(cat.searchResults(p_state=('unpaid', 'unpaid')))
943        self.assertEqual(len(results), 2)
944        self.assertTrue(results[0] is self.student['payments'][value])
945        # Managers can approve the payment
946        # If, by some reason, the state has already changed,
947        # an access code is created after approval.
948        IWorkflowState(self.student).setState('school fee paid')
949        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
950        self.browser.open(payment_url)
951        self.browser.getLink("Approve payment").click()
952        self.assertMatches('...Payment approved...',
953                          self.browser.contents)
954        # Approval is logged in students.log ...
955        logcontent = open(logfile).read()
956        self.assertTrue(
957            'zope.mgr - students.browser.OnlinePaymentApproveView '
958            '- K1000000 - schoolfee payment approved'
959            in logcontent)
960        # ... and in payments.log
961        logfile = os.path.join(
962            self.app['datacenter'].storage, 'logs', 'payments.log')
963        logcontent = open(logfile).read()
964        self.assertTrue(
965            '"zope.mgr",K1000000,%s,schoolfee,40000.0,AP,,,,,,\n' % value
966            in logcontent)
967        # The authorized amount has been stored in the new access code
968        self.assertEqual(
969            self.app['accesscodes']['SFE-0'].values()[0].cost,40000.0)
970
971        # The catalog has been updated
972        results = list(cat.searchResults(p_state=('unpaid', 'unpaid')))
973        self.assertTrue(len(results), 0)
974        results = list(cat.searchResults(p_state=('paid', 'paid')))
975        self.assertTrue(len(results), 1)
976        self.assertTrue(results[0] is self.student['payments'][value])
977
978        # Payments can't be approved twice
979        self.browser.open(payment_url + '/approve')
980        self.assertMatches('...This ticket has already been paid...',
981                          self.browser.contents)
982
983        # Now the first ticket is paid and no more ticket of same type
984        # (with same p_item, p_session and p_category) can be added.
985        # First we have to reset the workflow state.
986        IWorkflowState(self.student).setState('cleared')
987        self.browser.open(self.payments_path)
988        self.browser.getLink("Add current session payment ticket").click()
989        self.browser.getControl(name="form.p_category").value = ['schoolfee']
990        self.browser.getControl("Create ticket").click()
991        self.assertMatches(
992            '...This type of payment has already been made...',
993            self.browser.contents)
994
995        # Managers can open the pdf payment slip
996        self.browser.open(payment_url)
997        self.browser.getLink("Download payment slip").click()
998        self.assertEqual(self.browser.headers['Status'], '200 Ok')
999        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1000
1001        # Managers can remove online school fee payment tickets
1002        self.browser.open(self.payments_path)
1003        self.browser.getControl("Remove selected").click()
1004        self.assertMatches('...No payment selected...', self.browser.contents)
1005        ctrl = self.browser.getControl(name='val_id')
1006        value = ctrl.options[0]
1007        ctrl.getControl(value=value).selected = True
1008        self.browser.getControl("Remove selected", index=0).click()
1009        self.assertTrue('Successfully removed' in self.browser.contents)
1010
1011        # Managers can add online clearance payment tickets
1012        self.browser.open(self.payments_path + '/addop')
1013        self.browser.getControl(name="form.p_category").value = ['clearance']
1014        self.browser.getControl("Create ticket").click()
1015        self.assertMatches('...ticket created...',
1016                           self.browser.contents)
1017
1018        # Managers can approve the payment
1019        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
1020        ctrl = self.browser.getControl(name='val_id')
1021        value = ctrl.options[1] # The clearance payment is the second in the table
1022        self.browser.getLink(value).click()
1023        self.browser.open(self.browser.url + '/approve')
1024        self.assertMatches('...Payment approved...',
1025                          self.browser.contents)
1026        expected = '''...
1027        <td>
1028          <span>Paid</span>
1029        </td>...'''
1030        self.assertMatches(expected,self.browser.contents)
1031        # The new CLR-0 pin has been created
1032        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
1033        pin = self.app['accesscodes']['CLR-0'].keys()[0]
1034        ac = self.app['accesscodes']['CLR-0'][pin]
1035        self.assertEqual(ac.owner, self.student_id)
1036        self.assertEqual(ac.cost, 3456.0)
1037
1038        # Managers can add online transcript payment tickets
1039        self.browser.open(self.payments_path + '/addop')
1040        self.browser.getControl(name="form.p_category").value = ['transcript']
1041        self.browser.getControl("Create ticket").click()
1042        self.assertMatches('...ticket created...',
1043                           self.browser.contents)
1044
1045        # Managers can approve the payment
1046        self.assertEqual(len(self.app['accesscodes']['TSC-0']),0)
1047        ctrl = self.browser.getControl(name='val_id')
1048        value = ctrl.options[2] # The clearance payment is the third in the table
1049        self.browser.getLink(value).click()
1050        self.browser.open(self.browser.url + '/approve')
1051        self.assertMatches('...Payment approved...',
1052                          self.browser.contents)
1053        expected = '''...
1054        <td>
1055          <span>Paid</span>
1056        </td>...'''
1057        self.assertMatches(expected,self.browser.contents)
1058        # The new CLR-0 pin has been created
1059        self.assertEqual(len(self.app['accesscodes']['TSC-0']),1)
1060        pin = self.app['accesscodes']['TSC-0'].keys()[0]
1061        ac = self.app['accesscodes']['TSC-0'][pin]
1062        self.assertEqual(ac.owner, self.student_id)
1063        self.assertEqual(ac.cost, 4567.0)
1064        return
1065
1066    def test_add_transfer_payment(self):
1067        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1068        self.browser.open(self.payments_path)
1069        self.browser.getLink("Add current session payment ticket").click()
1070        self.browser.getControl(name="form.p_category").value = ['transfer']
1071        self.browser.getControl(name="new_programme").value = 'my new study course'
1072        self.browser.getControl("Create ticket").click()
1073        self.assertMatches('...ticket created...',
1074                           self.browser.contents)
1075        ctrl = self.browser.getControl(name='val_id')
1076        value = ctrl.options[0]
1077        self.browser.getLink(value).click()
1078        self.assertMatches('...my new study course...',
1079                           self.browser.contents)
1080        self.assertEqual(self.student['payments'][value].p_item, u'my new study course')
1081
1082    def test_manage_payments_bypass_ac_creation(self):
1083        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1084        self.browser.open(self.payments_path)
1085        IWorkflowState(self.student).setState('cleared')
1086        self.browser.getLink("Add current session payment ticket").click()
1087        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1088        self.browser.getControl("Create ticket").click()
1089        ctrl = self.browser.getControl(name='val_id')
1090        value = ctrl.options[0]
1091        self.browser.getLink(value).click()
1092        payment_url = self.browser.url
1093        logfile = os.path.join(
1094            self.app['datacenter'].storage, 'logs', 'students.log')
1095        # The ticket can be found in the payments_catalog
1096        cat = queryUtility(ICatalog, name='payments_catalog')
1097        results = list(cat.searchResults(p_state=('unpaid', 'unpaid')))
1098        self.assertTrue(len(results), 1)
1099        self.assertTrue(results[0] is self.student['payments'][value])
1100        # Managers can approve the payment
1101        self.browser.open(payment_url)
1102        self.browser.getLink("Approve payment").click()
1103        self.assertMatches('...Payment approved...',
1104                          self.browser.contents)
1105        # Approval is logged in students.log ...
1106        logcontent = open(logfile).read()
1107        self.assertTrue(
1108            'zope.mgr - students.browser.OnlinePaymentApproveView '
1109            '- K1000000 - schoolfee payment approved'
1110            in logcontent)
1111        # ... and in payments.log
1112        logfile = os.path.join(
1113            self.app['datacenter'].storage, 'logs', 'payments.log')
1114        logcontent = open(logfile).read()
1115        self.assertTrue(
1116            '"zope.mgr",K1000000,%s,schoolfee,40000.0,AP,,,,,,\n' % value
1117            in logcontent)
1118        # Student is in state school fee paid, no activation
1119        # code was necessary.
1120        self.assertEqual(self.student.state, 'school fee paid')
1121        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
1122        return
1123
1124    def test_payment_disabled(self):
1125        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1126        self.browser.open(self.payments_path)
1127        IWorkflowState(self.student).setState('cleared')
1128        self.browser.getLink("Add current session payment ticket").click()
1129        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1130        self.browser.getControl("Create ticket").click()
1131        self.assertMatches('...ticket created...',
1132                           self.browser.contents)
1133        self.app['configuration']['2004'].payment_disabled = ['sf_all']
1134        self.browser.getLink("Add current session payment ticket").click()
1135        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1136        self.browser.getControl("Create ticket").click()
1137        self.assertMatches('...Payment temporarily disabled...',
1138                           self.browser.contents)
1139        return
1140
1141    def test_manage_balance_payments(self):
1142
1143        # Login
1144        #self.browser.open(self.login_path)
1145        #self.browser.getControl(name="form.login").value = self.student_id
1146        #self.browser.getControl(name="form.password").value = 'spwd'
1147        #self.browser.getControl("Login").click()
1148
1149        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1150        self.browser.open(self.payments_path)
1151
1152        # Managers can add previous school fee payment tickets in any state.
1153        IWorkflowState(self.student).setState('courses registered')
1154        self.browser.open(self.payments_path)
1155        self.browser.getLink("Add balance payment ticket").click()
1156
1157        # Previous session payment form is provided
1158        self.assertEqual(self.student.current_session, 2004)
1159        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1160        self.browser.getControl(name="form.balance_session").value = ['2004']
1161        self.browser.getControl(name="form.balance_level").value = ['300']
1162        self.browser.getControl(name="form.balance_amount").value = '-567.8'
1163        self.browser.getControl("Create ticket").click()
1164        self.assertMatches('...Amount must be greater than 0...',
1165                           self.browser.contents)
1166        self.browser.getControl(name="form.balance_amount").value = '0'
1167        self.browser.getControl("Create ticket").click()
1168        self.assertMatches('...Amount must be greater than 0...',
1169                           self.browser.contents)
1170        self.browser.getControl(name="form.balance_amount").value = '567.8'
1171        self.browser.getControl("Create ticket").click()
1172        self.assertMatches('...ticket created...',
1173                           self.browser.contents)
1174        ctrl = self.browser.getControl(name='val_id')
1175        value = ctrl.options[0]
1176        self.browser.getLink(value).click()
1177        self.assertMatches('...Amount Authorized...',
1178                           self.browser.contents)
1179        self.assertEqual(self.student['payments'][value].amount_auth, 567.8)
1180        # Payment attributes are properly set
1181        self.assertEqual(self.student['payments'][value].p_session, 2004)
1182        self.assertEqual(self.student['payments'][value].p_level, 300)
1183        self.assertEqual(self.student['payments'][value].p_item, u'Balance')
1184        self.assertEqual(self.student['payments'][value].p_category, 'schoolfee')
1185        # Adding payment tickets is logged.
1186        logfile = os.path.join(
1187            self.app['datacenter'].storage, 'logs', 'students.log')
1188        logcontent = open(logfile).read()
1189        self.assertTrue('zope.mgr - students.browser.BalancePaymentAddFormPage '
1190                        '- K1000000 - added: %s' % value in logcontent)
1191
1192    def test_manage_accommodation(self):
1193        logfile = os.path.join(
1194            self.app['datacenter'].storage, 'logs', 'students.log')
1195        # Managers can add online booking fee payment tickets and open the
1196        # callback view (see test_manage_payments)
1197        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1198        self.browser.open(self.payments_path)
1199        self.browser.getLink("Add current session payment ticket").click()
1200        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1201        # If student is not in accommodation session, payment cannot be processed
1202        self.app['hostels'].accommodation_session = 2011
1203        self.browser.getControl("Create ticket").click()
1204        self.assertMatches('...Your current session does not match...',
1205                           self.browser.contents)
1206        self.app['hostels'].accommodation_session = 2004
1207        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1208        self.browser.getControl("Create ticket").click()
1209        ctrl = self.browser.getControl(name='val_id')
1210        value = ctrl.options[0]
1211        self.browser.getLink(value).click()
1212        self.browser.open(self.browser.url + '/approve')
1213        # The new HOS-0 pin has been created
1214        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
1215        pin = self.app['accesscodes']['HOS-0'].keys()[0]
1216        ac = self.app['accesscodes']['HOS-0'][pin]
1217        self.assertEqual(ac.owner, self.student_id)
1218        parts = pin.split('-')[1:]
1219        sfeseries, sfenumber = parts
1220        # Managers can use HOS code and book a bed space with it
1221        self.browser.open(self.acco_path)
1222        self.browser.getControl("Book accommodation").click()
1223        self.assertMatches('...You are in the wrong...',
1224                           self.browser.contents)
1225        IWorkflowInfo(self.student).fireTransition('admit')
1226        # An existing HOS code can only be used if students
1227        # are in accommodation session
1228        self.student['studycourse'].current_session = 2003
1229        self.browser.getControl("Book accommodation").click()
1230        self.assertMatches('...Your current session does not match...',
1231                           self.browser.contents)
1232        self.student['studycourse'].current_session = 2004
1233        # All requirements are met and ticket can be created
1234        self.browser.getControl("Book accommodation").click()
1235        self.assertMatches('...Activation Code:...',
1236                           self.browser.contents)
1237        self.browser.getControl(name="ac_series").value = sfeseries
1238        self.browser.getControl(name="ac_number").value = sfenumber
1239        self.browser.getControl("Create bed ticket").click()
1240        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1241                           self.browser.contents)
1242        # Bed has been allocated
1243        bed1 = self.app['hostels']['hall-1']['hall-1_A_101_A']
1244        self.assertTrue(bed1.owner == self.student_id)
1245        # BedTicketAddPage is now blocked
1246        self.browser.getControl("Book accommodation").click()
1247        self.assertMatches('...You already booked a bed space...',
1248            self.browser.contents)
1249        # The bed ticket displays the data correctly
1250        self.browser.open(self.acco_path + '/2004')
1251        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1252                           self.browser.contents)
1253        self.assertMatches('...2004/2005...', self.browser.contents)
1254        self.assertMatches('...regular_male_fr...', self.browser.contents)
1255        self.assertMatches('...%s...' % pin, self.browser.contents)
1256        # Booking is properly logged
1257        logcontent = open(logfile).read()
1258        self.assertTrue('zope.mgr - students.browser.BedTicketAddPage '
1259            '- K1000000 - booked: hall-1_A_101_A' in logcontent)
1260        # Managers can relocate students if the student's bed_type has changed
1261        self.browser.getLink("Relocate student").click()
1262        self.assertMatches(
1263            "...Student can't be relocated...", self.browser.contents)
1264        self.student.sex = u'f'
1265        self.browser.getLink("Relocate student").click()
1266        self.assertMatches(
1267            "...Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
1268        self.assertTrue(bed1.owner == NOT_OCCUPIED)
1269        bed2 = self.app['hostels']['hall-1']['hall-1_A_101_B']
1270        self.assertTrue(bed2.owner == self.student_id)
1271        self.assertTrue(self.student['accommodation'][
1272            '2004'].bed_type == u'regular_female_fr')
1273        # Relocation is properly logged
1274        logcontent = open(logfile).read()
1275        self.assertTrue('zope.mgr - students.accommodation.BedTicket '
1276            '- K1000000 - relocated: hall-1_A_101_B' in logcontent)
1277        # The payment object still shows the original payment item
1278        payment_id = self.student['payments'].keys()[0]
1279        payment = self.student['payments'][payment_id]
1280        self.assertTrue(payment.p_item == u'regular_male_fr')
1281        # Managers can relocate students if the bed's bed_type has changed
1282        bed1.bed_type = u'regular_female_fr'
1283        bed2.bed_type = u'regular_male_fr'
1284        notify(grok.ObjectModifiedEvent(bed1))
1285        notify(grok.ObjectModifiedEvent(bed2))
1286        self.browser.getLink("Relocate student").click()
1287        self.assertMatches(
1288            "...Student relocated...", self.browser.contents)
1289        self.assertMatches(
1290            "... Hall 1, Block A, Room 101, Bed A...", self.browser.contents)
1291        self.assertMatches(bed1.owner, self.student_id)
1292        self.assertMatches(bed2.owner, NOT_OCCUPIED)
1293        # Managers can't relocate students if bed is reserved
1294        self.student.sex = u'm'
1295        bed1.bed_type = u'regular_female_reserved'
1296        notify(grok.ObjectModifiedEvent(bed1))
1297        self.browser.getLink("Relocate student").click()
1298        self.assertMatches(
1299            "...Students in reserved beds can't be relocated...",
1300            self.browser.contents)
1301        # Managers can relocate students if booking has been cancelled but
1302        # other bed space has been manually allocated after cancellation
1303        old_owner = bed1.releaseBed()
1304        self.assertMatches(old_owner, self.student_id)
1305        bed2.owner = self.student_id
1306        self.browser.open(self.acco_path + '/2004')
1307        self.assertMatches(
1308            "...booking cancelled...", self.browser.contents)
1309        self.browser.getLink("Relocate student").click()
1310        # We didn't informed the catalog therefore the new owner is not found
1311        self.assertMatches(
1312            "...There is no free bed in your category regular_male_fr...",
1313            self.browser.contents)
1314        # Now we fire the event properly
1315        notify(grok.ObjectModifiedEvent(bed2))
1316        self.browser.getLink("Relocate student").click()
1317        self.assertMatches(
1318            "...Student relocated...", self.browser.contents)
1319        self.assertMatches(
1320            "... Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
1321          # Managers can delete bed tickets
1322        self.browser.open(self.acco_path)
1323        ctrl = self.browser.getControl(name='val_id')
1324        value = ctrl.options[0]
1325        ctrl.getControl(value=value).selected = True
1326        self.browser.getControl("Remove selected", index=0).click()
1327        self.assertMatches('...Successfully removed...', self.browser.contents)
1328        # The bed has been properly released by the event handler
1329        self.assertMatches(bed1.owner, NOT_OCCUPIED)
1330        self.assertMatches(bed2.owner, NOT_OCCUPIED)
1331        return
1332
1333    def test_manage_workflow(self):
1334        # Managers can pass through the whole workflow
1335        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1336        student = self.app['students'][self.student_id]
1337        self.browser.open(self.trigtrans_path)
1338        self.assertTrue(student.clearance_locked)
1339        self.browser.getControl(name="transition").value = ['admit']
1340        self.browser.getControl("Save").click()
1341        self.assertTrue(student.clearance_locked)
1342        self.browser.getControl(name="transition").value = ['start_clearance']
1343        self.browser.getControl("Save").click()
1344        self.assertFalse(student.clearance_locked)
1345        self.browser.getControl(name="transition").value = ['request_clearance']
1346        self.browser.getControl("Save").click()
1347        self.assertTrue(student.clearance_locked)
1348        self.browser.getControl(name="transition").value = ['clear']
1349        self.browser.getControl("Save").click()
1350        # Managers approve payment, they do not pay
1351        self.assertFalse('pay_first_school_fee' in self.browser.contents)
1352        self.browser.getControl(
1353            name="transition").value = ['approve_first_school_fee']
1354        self.browser.getControl("Save").click()
1355        self.browser.getControl(name="transition").value = ['reset6']
1356        self.browser.getControl("Save").click()
1357        # In state returning the pay_school_fee transition triggers some
1358        # changes of attributes
1359        self.browser.getControl(name="transition").value = ['approve_school_fee']
1360        self.browser.getControl("Save").click()
1361        self.assertEqual(student['studycourse'].current_session, 2005) # +1
1362        self.assertEqual(student['studycourse'].current_level, 200) # +100
1363        self.assertEqual(student['studycourse'].current_verdict, '0') # 0 = Zero = not set
1364        self.assertEqual(student['studycourse'].previous_verdict, 'A')
1365        self.browser.getControl(name="transition").value = ['register_courses']
1366        self.browser.getControl("Save").click()
1367        self.browser.getControl(name="transition").value = ['validate_courses']
1368        self.browser.getControl("Save").click()
1369        self.browser.getControl(name="transition").value = ['return']
1370        self.browser.getControl("Save").click()
1371        return
1372
1373    def test_manage_pg_workflow(self):
1374        # Managers can pass through the whole workflow
1375        IWorkflowState(self.student).setState('school fee paid')
1376        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1377        student = self.app['students'][self.student_id]
1378        self.browser.open(self.trigtrans_path)
1379        self.assertTrue('<option value="reset6">' in self.browser.contents)
1380        self.assertTrue('<option value="register_courses">' in self.browser.contents)
1381        self.assertTrue('<option value="reset5">' in self.browser.contents)
1382        self.certificate.study_mode = 'pg_ft'
1383        self.browser.open(self.trigtrans_path)
1384        self.assertFalse('<option value="reset6">' in self.browser.contents)
1385        self.assertFalse('<option value="register_courses">' in self.browser.contents)
1386        self.assertTrue('<option value="reset5">' in self.browser.contents)
1387        return
1388
1389    def test_manage_import(self):
1390        # Managers can import student data files
1391        datacenter_path = 'http://localhost/app/datacenter'
1392        # Prepare a csv file for students
1393        open('students.csv', 'wb').write(
1394"""firstname,lastname,reg_number,date_of_birth,matric_number,email,phone,sex,password
1395Aaren,Pieri,1,1990-01-02,100000,aa@aa.ng,1234,m,mypwd1
1396Claus,Finau,2,1990-01-03,100001,aa@aa.ng,1234,m,mypwd1
1397Brit,Berson,3,1990-01-04,100001,aa@aa.ng,1234,m,mypwd1
1398""")
1399        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1400        self.browser.open(datacenter_path)
1401        self.browser.getLink('Upload data').click()
1402        filecontents = StringIO(open('students.csv', 'rb').read())
1403        filewidget = self.browser.getControl(name='uploadfile:file')
1404        filewidget.add_file(filecontents, 'text/plain', 'students.csv')
1405        self.browser.getControl(name='SUBMIT').click()
1406        self.browser.getLink('Process data').click()
1407        self.browser.getLink("Switch maintenance mode").click()
1408        button = lookup_submit_value(
1409            'select', 'students_zope.mgr.csv', self.browser)
1410        button.click()
1411        importerselect = self.browser.getControl(name='importer')
1412        modeselect = self.browser.getControl(name='mode')
1413        importerselect.getControl('Student Processor').selected = True
1414        modeselect.getControl(value='create').selected = True
1415        self.browser.getControl('Proceed to step 3').click()
1416        self.assertTrue('Header fields OK' in self.browser.contents)
1417        self.browser.getControl('Perform import').click()
1418        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
1419        self.assertTrue('Successfully processed 2 rows' in self.browser.contents)
1420        self.assertTrue('Batch processing finished' in self.browser.contents)
1421        open('studycourses.csv', 'wb').write(
1422"""reg_number,matric_number,certificate,current_session,current_level
14231,,CERT1,2008,100
1424,100001,CERT1,2008,100
1425,100002,CERT1,2008,100
1426""")
1427        self.browser.open(datacenter_path)
1428        self.browser.getLink('Upload data').click()
1429        filecontents = StringIO(open('studycourses.csv', 'rb').read())
1430        filewidget = self.browser.getControl(name='uploadfile:file')
1431        filewidget.add_file(filecontents, 'text/plain', 'studycourses.csv')
1432        self.browser.getControl(name='SUBMIT').click()
1433        self.browser.getLink('Process data').click()
1434        # Meanwhile maintenance mode is disabled again.
1435        self.browser.getLink("Switch maintenance mode").click()
1436        button = lookup_submit_value(
1437            'select', 'studycourses_zope.mgr.csv', self.browser)
1438        button.click()
1439        importerselect = self.browser.getControl(name='importer')
1440        modeselect = self.browser.getControl(name='mode')
1441        importerselect.getControl(
1442            'StudentStudyCourse Processor (update only)').selected = True
1443        modeselect.getControl(value='create').selected = True
1444        self.browser.getControl('Proceed to step 3').click()
1445        self.assertTrue('Update mode only' in self.browser.contents)
1446        self.browser.getControl('Proceed to step 3').click()
1447        self.assertTrue('Header fields OK' in self.browser.contents)
1448        self.browser.getControl('Perform import').click()
1449        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
1450        self.assertTrue('Successfully processed 2 rows'
1451                        in self.browser.contents)
1452        # The students are properly indexed and we can
1453        # thus find a student in  the department
1454        self.browser.open(self.manage_container_path)
1455        self.browser.getControl(name="searchtype").value = ['depcode']
1456        self.browser.getControl(name="searchterm").value = 'dep1'
1457        self.browser.getControl("Find student(s)").click()
1458        self.assertTrue('Aaren Pieri' in self.browser.contents)
1459        # We can search for a new student by name ...
1460        self.browser.getControl(name="searchtype").value = ['fullname']
1461        self.browser.getControl(name="searchterm").value = 'Claus'
1462        self.browser.getControl("Find student(s)").click()
1463        self.assertTrue('Claus Finau' in self.browser.contents)
1464        # ... and check if the imported password has been properly set
1465        ctrl = self.browser.getControl(name='entries')
1466        value = ctrl.options[0]
1467        claus = self.app['students'][value]
1468        self.assertTrue(IUserAccount(claus).checkPassword('mypwd1'))
1469        return
1470
1471    def init_clearance_officer(self):
1472        # Create clearance officer
1473        self.app['users'].addUser('mrclear', 'mrclearsecret')
1474        self.app['users']['mrclear'].email = 'mrclear@foo.ng'
1475        self.app['users']['mrclear'].title = 'Carlo Pitter'
1476        # Clearance officers need not necessarily to get
1477        # the StudentsOfficer site role
1478        #prmglobal = IPrincipalRoleManager(self.app)
1479        #prmglobal.assignRoleToPrincipal('waeup.StudentsOfficer', 'mrclear')
1480        # Assign local ClearanceOfficer role
1481        self.department = self.app['faculties']['fac1']['dep1']
1482        prmlocal = IPrincipalRoleManager(self.department)
1483        prmlocal.assignRoleToPrincipal('waeup.local.ClearanceOfficer', 'mrclear')
1484        IWorkflowState(self.student).setState('clearance started')
1485        # Add another student for testing
1486        other_student = Student()
1487        other_student.firstname = u'Dep2'
1488        other_student.lastname = u'Student'
1489        self.app['students'].addStudent(other_student)
1490        self.other_student_path = (
1491            'http://localhost/app/students/%s' % other_student.student_id)
1492        # Login as clearance officer
1493        self.browser.open(self.login_path)
1494        self.browser.getControl(name="form.login").value = 'mrclear'
1495        self.browser.getControl(name="form.password").value = 'mrclearsecret'
1496        self.browser.getControl("Login").click()
1497
1498    def test_handle_clearance_by_co(self):
1499        self.init_clearance_officer()
1500        self.assertMatches('...You logged in...', self.browser.contents)
1501        # CO is landing on index page.
1502        self.assertEqual(self.browser.url, 'http://localhost/app/index')
1503        # CO can see his roles
1504        self.browser.getLink("My Roles").click()
1505        self.assertMatches(
1506            '...<div>Academics Officer (view only)</div>...',
1507            self.browser.contents)
1508        # But not his local role ...
1509        self.assertFalse('Clearance Officer' in self.browser.contents)
1510        # ... because we forgot to notify the department that the local role
1511        # has changed.
1512        notify(LocalRoleSetEvent(
1513            self.department, 'waeup.local.ClearanceOfficer', 'mrclear',
1514            granted=True))
1515        self.browser.open('http://localhost/app/users/mrclear/my_roles')
1516        self.assertTrue('Clearance Officer' in self.browser.contents)
1517        self.assertMatches(
1518            '...<a href="http://localhost/app/faculties/fac1/dep1">...',
1519            self.browser.contents)
1520        # CO can view the student ...
1521        self.browser.open(self.clearance_path)
1522        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1523        self.assertEqual(self.browser.url, self.clearance_path)
1524        # ... but not other students.
1525        self.assertRaises(
1526            Unauthorized, self.browser.open, self.other_student_path)
1527        # Clearance is disabled for this session.
1528        self.browser.open(self.clearance_path)
1529        self.assertFalse('Clear student' in self.browser.contents)
1530        self.browser.open(self.student_path + '/clear')
1531        self.assertTrue('Clearance is disabled for this session'
1532            in self.browser.contents)
1533        self.app['configuration']['2004'].clearance_enabled = True
1534        # Only in state clearance requested the CO does see the 'Clear' button.
1535        self.browser.open(self.clearance_path)
1536        self.assertFalse('Clear student' in self.browser.contents)
1537        self.browser.open(self.student_path + '/clear')
1538        self.assertTrue('Student is in wrong state.'
1539            in self.browser.contents)
1540        IWorkflowInfo(self.student).fireTransition('request_clearance')
1541        self.browser.open(self.clearance_path)
1542        self.assertTrue('Clear student' in self.browser.contents)
1543        self.browser.getLink("Clear student").click()
1544        self.assertTrue('Student has been cleared' in self.browser.contents)
1545        self.assertTrue('cleared' in self.browser.contents)
1546        self.browser.open(self.history_path)
1547        self.assertTrue('Cleared by Carlo Pitter' in self.browser.contents)
1548        # Hide real name.
1549        self.app['users']['mrclear'].public_name = 'My Public Name'
1550        self.browser.open(self.clearance_path)
1551        self.browser.getLink("Reject clearance").click()
1552        self.assertEqual(
1553            self.browser.url, self.student_path + '/reject_clearance')
1554        # Type comment why.
1555        self.browser.getControl(name="form.officer_comment").value = (
1556            'Dear Student,\n'
1557            'You did not fill properly.')
1558        self.browser.getControl("Save comment").click()
1559        self.assertTrue('Clearance has been annulled' in self.browser.contents)
1560        url = ('http://localhost/app/students/K1000000/'
1561              'contactstudent?body=Dear+Student%2C%0AYou+did+not+fill+properly.'
1562              '&subject=Clearance+has+been+annulled.')
1563        # CO does now see the prefilled contact form and can send a message.
1564        self.assertEqual(self.browser.url, url)
1565        self.assertTrue('clearance started' in self.browser.contents)
1566        self.assertTrue('name="form.subject" size="20" type="text" '
1567            'value="Clearance has been annulled."'
1568            in self.browser.contents)
1569        self.assertTrue('name="form.body" rows="10" >Dear Student,'
1570            in self.browser.contents)
1571        self.browser.getControl("Send message now").click()
1572        self.assertTrue('Your message has been sent' in self.browser.contents)
1573        # The comment has been stored ...
1574        self.assertEqual(self.student.officer_comment,
1575            u'Dear Student,\nYou did not fill properly.')
1576        # ... and logged.
1577        logfile = os.path.join(
1578            self.app['datacenter'].storage, 'logs', 'students.log')
1579        logcontent = open(logfile).read()
1580        self.assertTrue(
1581            'INFO - mrclear - students.browser.StudentRejectClearancePage - '
1582            'K1000000 - comment: Dear Student,<br>You did not fill '
1583            'properly.\n' in logcontent)
1584        self.browser.open(self.history_path)
1585        self.assertTrue("Reset to 'clearance started' by My Public Name" in
1586            self.browser.contents)
1587        IWorkflowInfo(self.student).fireTransition('request_clearance')
1588        self.browser.open(self.clearance_path)
1589        self.browser.getLink("Reject clearance").click()
1590        self.browser.getControl("Save comment").click()
1591        self.assertTrue('Clearance request has been rejected'
1592            in self.browser.contents)
1593        self.assertTrue('clearance started' in self.browser.contents)
1594        # The CO can't clear students if not in state
1595        # clearance requested.
1596        self.browser.open(self.student_path + '/clear')
1597        self.assertTrue('Student is in wrong state'
1598            in self.browser.contents)
1599        # The CO can go to his department throug the my_roles page ...
1600        self.browser.open('http://localhost/app/users/mrclear/my_roles')
1601        self.browser.getLink("http://localhost/app/faculties/fac1/dep1").click()
1602        # ... and view the list of students.
1603        self.browser.getLink("Show students").click()
1604        self.browser.getControl(name="session").value = ['2004']
1605        self.browser.getControl(name="level").value = ['200']
1606        self.browser.getControl("Show").click()
1607        self.assertFalse(self.student_id in self.browser.contents)
1608        self.browser.getControl(name="session").value = ['2004']
1609        self.browser.getControl(name="level").value = ['100']
1610        self.browser.getControl("Show").click()
1611        self.assertTrue(self.student_id in self.browser.contents)
1612        # The comment is indicated by 'yes'.
1613        self.assertTrue('<td><span>yes</span></td>' in self.browser.contents)
1614        # Check if the enquiries form is not pre-filled with officer_comment
1615        # (regression test).
1616        self.browser.getLink("Logout").click()
1617        self.browser.open('http://localhost/app/enquiries')
1618        self.assertFalse(
1619            'You did not fill properly'
1620            in self.browser.contents)
1621        # When a student is cleared the comment is automatically deleted
1622        IWorkflowInfo(self.student).fireTransition('request_clearance')
1623        IWorkflowInfo(self.student).fireTransition('clear')
1624        self.assertEqual(self.student.officer_comment, None)
1625        return
1626
1627    def test_handle_mass_clearance_by_co(self):
1628        self.init_clearance_officer()
1629        # Additional setups according to test above
1630        notify(LocalRoleSetEvent(
1631            self.department, 'waeup.local.ClearanceOfficer', 'mrclear',
1632            granted=True))
1633        self.app['configuration']['2004'].clearance_enabled = True
1634        IWorkflowState(self.student).setState('clearance requested')
1635        # Update the catalog
1636        notify(grok.ObjectModifiedEvent(self.student))
1637        # The CO can go to the department and clear all students in department
1638        self.browser.open('http://localhost/app/faculties/fac1/dep1')
1639        self.browser.getLink("Clear all students").click()
1640        self.assertTrue('1 students have been cleared' in self.browser.contents)
1641        self.browser.open(self.history_path)
1642        self.assertTrue('Cleared by Carlo Pitter' in self.browser.contents)
1643        logfile = os.path.join(
1644            self.app['datacenter'].storage, 'logs', 'students.log')
1645        logcontent = open(logfile).read()
1646        self.assertTrue(
1647            'INFO - mrclear - K1000000 - Cleared' in logcontent)
1648        self.browser.open('http://localhost/app/faculties/fac1/dep1')
1649        self.browser.getLink("Clear all students").click()
1650        self.assertTrue('0 students have been cleared' in self.browser.contents)
1651        return
1652
1653    def test_handle_courses_by_ca(self):
1654        self.app['users'].addUser('mrsadvise', 'mrsadvisesecret')
1655        self.app['users']['mrsadvise'].email = 'mradvise@foo.ng'
1656        self.app['users']['mrsadvise'].title = u'Helen Procter'
1657        # Assign local CourseAdviser100 role for a certificate
1658        cert = self.app['faculties']['fac1']['dep1'].certificates['CERT1']
1659        prmlocal = IPrincipalRoleManager(cert)
1660        prmlocal.assignRoleToPrincipal('waeup.local.CourseAdviser100', 'mrsadvise')
1661        IWorkflowState(self.student).setState('school fee paid')
1662        # Login as course adviser.
1663        self.browser.open(self.login_path)
1664        self.browser.getControl(name="form.login").value = 'mrsadvise'
1665        self.browser.getControl(name="form.password").value = 'mrsadvisesecret'
1666        self.browser.getControl("Login").click()
1667        self.assertMatches('...You logged in...', self.browser.contents)
1668        # CO can see his roles.
1669        self.browser.getLink("My Roles").click()
1670        self.assertMatches(
1671            '...<div>Academics Officer (view only)</div>...',
1672            self.browser.contents)
1673        # But not his local role ...
1674        self.assertFalse('Course Adviser' in self.browser.contents)
1675        # ... because we forgot to notify the certificate that the local role
1676        # has changed.
1677        notify(LocalRoleSetEvent(
1678            cert, 'waeup.local.CourseAdviser100', 'mrsadvise', granted=True))
1679        self.browser.open('http://localhost/app/users/mrsadvise/my_roles')
1680        self.assertTrue('Course Adviser 100L' in self.browser.contents)
1681        self.assertMatches(
1682            '...<a href="http://localhost/app/faculties/fac1/dep1/certificates/CERT1">...',
1683            self.browser.contents)
1684        # CA can view the student ...
1685        self.browser.open(self.student_path)
1686        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1687        self.assertEqual(self.browser.url, self.student_path)
1688        # ... but not other students.
1689        other_student = Student()
1690        other_student.firstname = u'Dep2'
1691        other_student.lastname = u'Student'
1692        self.app['students'].addStudent(other_student)
1693        other_student_path = (
1694            'http://localhost/app/students/%s' % other_student.student_id)
1695        self.assertRaises(
1696            Unauthorized, self.browser.open, other_student_path)
1697        # We add study level 110 to the student's studycourse.
1698        studylevel = StudentStudyLevel()
1699        studylevel.level = 110
1700        self.student['studycourse'].addStudentStudyLevel(
1701            cert,studylevel)
1702        L110_student_path = self.studycourse_path + '/110'
1703        # The CA can neither see the Validate nor the Edit button.
1704        self.browser.open(L110_student_path)
1705        self.assertFalse('Validate courses' in self.browser.contents)
1706        self.assertFalse('Edit' in self.browser.contents)
1707        IWorkflowInfo(self.student).fireTransition('register_courses')
1708        self.browser.open(L110_student_path)
1709        self.assertFalse('Validate courses' in self.browser.contents)
1710        self.assertFalse('Edit' in self.browser.contents)
1711        # Only in state courses registered and only if the current level
1712        # corresponds with the name of the study level object
1713        # the 100L CA does see the 'Validate' button but not
1714        # the edit button.
1715        self.student['studycourse'].current_level = 110
1716        self.browser.open(L110_student_path)
1717        self.assertFalse('Edit' in self.browser.contents)
1718        self.assertTrue('Validate courses' in self.browser.contents)
1719        # But a 100L CA does not see the button at other levels.
1720        studylevel2 = StudentStudyLevel()
1721        studylevel2.level = 200
1722        self.student['studycourse'].addStudentStudyLevel(
1723            cert,studylevel2)
1724        L200_student_path = self.studycourse_path + '/200'
1725        self.browser.open(L200_student_path)
1726        self.assertFalse('Edit' in self.browser.contents)
1727        self.assertFalse('Validate courses' in self.browser.contents)
1728        self.browser.open(L110_student_path)
1729        self.browser.getLink("Validate courses").click()
1730        self.assertTrue('Course list has been validated' in self.browser.contents)
1731        self.assertTrue('courses validated' in self.browser.contents)
1732        self.assertEqual(self.student['studycourse']['110'].validated_by,
1733            'Helen Procter')
1734        self.assertMatches(
1735            '<YYYY-MM-DD hh:mm:ss>',
1736            self.student['studycourse']['110'].validation_date.strftime(
1737                "%Y-%m-%d %H:%M:%S"))
1738        self.browser.getLink("Reject courses").click()
1739        self.assertTrue('Course list request has been annulled.'
1740            in self.browser.contents)
1741        urlmessage = 'Course+list+request+has+been+annulled.'
1742        self.assertEqual(self.browser.url, self.student_path +
1743            '/contactstudent?subject=%s' % urlmessage)
1744        self.assertTrue('school fee paid' in self.browser.contents)
1745        self.assertTrue(self.student['studycourse']['110'].validated_by is None)
1746        self.assertTrue(self.student['studycourse']['110'].validation_date is None)
1747        IWorkflowInfo(self.student).fireTransition('register_courses')
1748        self.browser.open(L110_student_path)
1749        self.browser.getLink("Reject courses").click()
1750        self.assertTrue('Course list has been unregistered'
1751            in self.browser.contents)
1752        self.assertTrue('school fee paid' in self.browser.contents)
1753        # CA does now see the contact form and can send a message.
1754        self.browser.getControl(name="form.subject").value = 'Important subject'
1755        self.browser.getControl(name="form.body").value = 'Course list rejected'
1756        self.browser.getControl("Send message now").click()
1757        self.assertTrue('Your message has been sent' in self.browser.contents)
1758        # The CA does now see the Edit button and can edit
1759        # current study level.
1760        self.browser.open(L110_student_path)
1761        self.browser.getLink("Edit").click()
1762        self.assertTrue('Edit course list of 100 (Year 1) on 1st probation'
1763            in self.browser.contents)
1764        # The CA can't validate courses if not in state
1765        # courses registered.
1766        self.browser.open(L110_student_path + '/validate_courses')
1767        self.assertTrue('Student is in the wrong state'
1768            in self.browser.contents)
1769        # The CA can go to his certificate through the my_roles page ...
1770        self.browser.open('http://localhost/app/users/mrsadvise/my_roles')
1771        self.browser.getLink(
1772            "http://localhost/app/faculties/fac1/dep1/certificates/CERT1").click()
1773        # ... and view the list of students.
1774        self.browser.getLink("Show students").click()
1775        self.browser.getControl(name="session").value = ['2004']
1776        self.browser.getControl(name="level").value = ['100']
1777        self.browser.getControl("Show").click()
1778        self.assertTrue(self.student_id in self.browser.contents)
1779
1780    def test_handle_courses_by_lecturer(self):
1781        self.app['users'].addUser('mrslecturer', 'mrslecturersecret')
1782        self.app['users']['mrslecturer'].email = 'mrslecturer@foo.ng'
1783        self.app['users']['mrslecturer'].title = u'Mercedes Benz'
1784        # Assign local Lecturer role for a certificate.
1785        course = self.app['faculties']['fac1']['dep1'].courses['COURSE1']
1786        prmlocal = IPrincipalRoleManager(course)
1787        prmlocal.assignRoleToPrincipal('waeup.local.Lecturer', 'mrslecturer')
1788        # Login as lecturer.
1789        self.browser.open(self.login_path)
1790        self.browser.getControl(name="form.login").value = 'mrslecturer'
1791        self.browser.getControl(name="form.password").value = 'mrslecturersecret'
1792        self.browser.getControl("Login").click()
1793        self.assertMatches('...You logged in...', self.browser.contents)
1794        # CO can see her roles.
1795        self.browser.getLink("My Roles").click()
1796        self.assertMatches(
1797            '...<div>Academics Officer (view only)</div>...',
1798            self.browser.contents)
1799        # But not her local role ...
1800        self.assertFalse('Lecturer' in self.browser.contents)
1801        # ... because we forgot to notify the course that the local role
1802        # has changed.
1803        notify(LocalRoleSetEvent(
1804            course, 'waeup.local.Lecturer', 'mrslecturer', granted=True))
1805        self.browser.open('http://localhost/app/users/mrslecturer/my_roles')
1806        self.assertTrue('Lecturer' in self.browser.contents)
1807        self.assertMatches(
1808            '...<a href="http://localhost/app/faculties/fac1/dep1/courses/COURSE1">...',
1809            self.browser.contents)
1810        # The lecturer can go to her course ...
1811        self.browser.getLink(
1812            "http://localhost/app/faculties/fac1/dep1/courses/COURSE1").click()
1813        # ... and view the list of students
1814        self.browser.getLink("Show students").click()
1815        self.browser.getControl(name="session").value = ['2004']
1816        self.browser.getControl(name="level").value = ['100']
1817        self.browser.getControl("Show").click()
1818        self.assertTrue('No student found.' in self.browser.contents)
1819        # No student in course so far.
1820        self.assertFalse(self.student_id in self.browser.contents)
1821        studylevel = createObject(u'waeup.StudentStudyLevel')
1822        studylevel.level = 100
1823        studylevel.level_session = 2004
1824        self.student['studycourse'].addStudentStudyLevel(
1825            self.certificate, studylevel)
1826        # Now the student has registered the course and can
1827        # be seen by the lecturer.
1828        self.browser.open("http://localhost/app/faculties/fac1/dep1/courses/COURSE1/students")
1829        self.browser.getControl(name="session").value = ['2004']
1830        self.browser.getControl(name="level").value = ['100']
1831        self.browser.getControl("Show").click()
1832        self.assertTrue(self.student_id in self.browser.contents)
1833        # Lecturer can neither access the student ...
1834        self.assertRaises(
1835            Unauthorized, self.browser.open, self.student_path)
1836        # ... nor the respective course ticket since
1837        # editing course tickets by lecturers is not feasible.
1838        self.assertTrue('COURSE1' in self.student['studycourse']['100'].keys())
1839        course_ticket_path = self.student_path + '/studycourse/100/COURSE1'
1840        self.assertRaises(
1841            Unauthorized, self.browser.open, course_ticket_path)
1842        # Course results can be batch edited via the edit_courses view.
1843        self.app['faculties']['fac1']['dep1'].score_editing_disabled = True
1844        self.browser.open("http://localhost/app/faculties/fac1/dep1/courses/COURSE1")
1845        self.browser.getLink("Update scores").click()
1846        self.assertTrue('Score editing disabled' in self.browser.contents)
1847        self.app['faculties']['fac1']['dep1'].score_editing_disabled = False
1848        self.browser.getLink("Update scores").click()
1849        self.assertTrue('Current academic session not set' in self.browser.contents)
1850        self.app['configuration'].current_academic_session = 2004
1851        self.browser.getLink("Update scores").click()
1852        self.assertFalse(
1853            '<input type="text" name="scores" class="span1" />'
1854            in self.browser.contents)
1855        IWorkflowState(self.student).setState('courses validated')
1856        # Student must be in state 'courses validated'.
1857        self.browser.open(
1858            "http://localhost/app/faculties/fac1/dep1/courses/COURSE1/edit_scores")
1859        self.assertTrue(
1860            '<input type="text" name="scores" class="span1" />'
1861            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        # A level with one course ticket was created
2662        self.assertEqual(self.student['studycourse']['999'].number_of_tickets, 0)
2663        self.browser.getLink("999").click()
2664        self.browser.getLink("Edit course list").click()
2665        self.browser.getLink("here").click()
2666        self.browser.getControl(name="form.course").value = ['COURSE1']
2667        self.browser.getControl("Add course ticket").click()
2668        self.assertMatches('...Successfully added COURSE1...',
2669                           self.browser.contents)
2670        # Postgraduate students can't register course lists
2671        self.browser.getControl("Register course list").click()
2672        self.assertTrue("your course list can't bee registered"
2673            in self.browser.contents)
2674        self.assertEqual(self.student.state, 'school fee paid')
2675        return
2676
2677    def test_student_clearance_wo_clrcode(self):
2678        IWorkflowState(self.student).setState('clearance started')
2679        self.browser.open(self.login_path)
2680        self.browser.getControl(name="form.login").value = self.student_id
2681        self.browser.getControl(name="form.password").value = 'spwd'
2682        self.browser.getControl("Login").click()
2683        self.browser.open(self.edit_clearance_path)
2684        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
2685        self.browser.getControl("Save and request clearance").click()
2686        self.assertMatches('...Clearance has been requested...',
2687                           self.browser.contents)
2688
2689    def test_student_clearance_payment(self):
2690        # Login
2691        self.browser.open(self.login_path)
2692        self.browser.getControl(name="form.login").value = self.student_id
2693        self.browser.getControl(name="form.password").value = 'spwd'
2694        self.browser.getControl("Login").click()
2695
2696        # Students can add online clearance payment tickets
2697        self.browser.open(self.payments_path + '/addop')
2698        self.browser.getControl(name="form.p_category").value = ['clearance']
2699        self.browser.getControl("Create ticket").click()
2700        self.assertMatches('...ticket created...',
2701                           self.browser.contents)
2702
2703        # Students can't approve the payment
2704        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
2705        ctrl = self.browser.getControl(name='val_id')
2706        value = ctrl.options[0]
2707        self.browser.getLink(value).click()
2708        payment_url = self.browser.url
2709        self.assertRaises(
2710            Unauthorized, self.browser.open, payment_url + '/approve')
2711        # In the base package they can 'use' a fake approval view.
2712        # XXX: I tried to use
2713        # self.student['payments'][value].approveStudentPayment() instead.
2714        # But this function fails in
2715        # w.k.accesscodes.accesscode.create_accesscode.
2716        # grok.getSite returns None in tests.
2717        self.browser.open(payment_url + '/fake_approve')
2718        self.assertMatches('...Payment approved...',
2719                          self.browser.contents)
2720        expected = '''...
2721        <td>
2722          <span>Paid</span>
2723        </td>...'''
2724        expected = '''...
2725        <td>
2726          <span>Paid</span>
2727        </td>...'''
2728        self.assertMatches(expected,self.browser.contents)
2729        payment_id = self.student['payments'].keys()[0]
2730        payment = self.student['payments'][payment_id]
2731        self.assertEqual(payment.p_state, 'paid')
2732        self.assertEqual(payment.r_amount_approved, 3456.0)
2733        self.assertEqual(payment.r_code, 'AP')
2734        self.assertEqual(payment.r_desc, u'Payment approved by Anna Tester')
2735        # The new CLR-0 pin has been created
2736        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
2737        pin = self.app['accesscodes']['CLR-0'].keys()[0]
2738        ac = self.app['accesscodes']['CLR-0'][pin]
2739        self.assertEqual(ac.owner, self.student_id)
2740        self.assertEqual(ac.cost, 3456.0)
2741
2742        # Students can open the pdf payment slip
2743        self.browser.open(payment_url + '/payment_slip.pdf')
2744        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2745        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2746
2747        # The new CLR-0 pin can be used for starting clearance
2748        # but they have to upload a passport picture first
2749        # which is only possible in state admitted
2750        self.browser.open(self.student_path + '/change_portrait')
2751        self.assertMatches('...form is locked...',
2752                          self.browser.contents)
2753        IWorkflowInfo(self.student).fireTransition('admit')
2754        self.browser.open(self.student_path + '/change_portrait')
2755        image = open(SAMPLE_IMAGE, 'rb')
2756        ctrl = self.browser.getControl(name='passportuploadedit')
2757        file_ctrl = ctrl.mech_control
2758        file_ctrl.add_file(image, filename='my_photo.jpg')
2759        self.browser.getControl(
2760            name='upload_passportuploadedit').click()
2761        self.browser.open(self.student_path + '/start_clearance')
2762        parts = pin.split('-')[1:]
2763        clrseries, clrnumber = parts
2764        self.browser.getControl(name="ac_series").value = clrseries
2765        self.browser.getControl(name="ac_number").value = clrnumber
2766        self.browser.getControl("Start clearance now").click()
2767        self.assertMatches('...Clearance process has been started...',
2768                           self.browser.contents)
2769
2770    def test_student_schoolfee_payment(self):
2771        configuration = createObject('waeup.SessionConfiguration')
2772        configuration.academic_session = 2005
2773        self.app['configuration'].addSessionConfiguration(configuration)
2774        # Login
2775        self.browser.open(self.login_path)
2776        self.browser.getControl(name="form.login").value = self.student_id
2777        self.browser.getControl(name="form.password").value = 'spwd'
2778        self.browser.getControl("Login").click()
2779
2780        # Students can add online school fee payment tickets.
2781        IWorkflowState(self.student).setState('returning')
2782        self.browser.open(self.payments_path)
2783        self.assertRaises(
2784            LookupError, self.browser.getControl, name='val_id')
2785        self.browser.getLink("Add current session payment ticket").click()
2786        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2787        self.browser.getControl("Create ticket").click()
2788        self.assertMatches('...ticket created...',
2789                           self.browser.contents)
2790        ctrl = self.browser.getControl(name='val_id')
2791        value = ctrl.options[0]
2792        self.browser.getLink(value).click()
2793        self.assertMatches('...Amount Authorized...',
2794                           self.browser.contents)
2795        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2796        # Payment session and will be calculated as defined
2797        # in w.k.students.utils because we set changed the state
2798        # to returning
2799        self.assertEqual(self.student['payments'][value].p_session, 2005)
2800        self.assertEqual(self.student['payments'][value].p_level, 200)
2801
2802        # Student is the payer of the payment ticket.
2803        payer = IPayer(self.student['payments'][value])
2804        self.assertEqual(payer.display_fullname, 'Anna Tester')
2805        self.assertEqual(payer.id, self.student_id)
2806        self.assertEqual(payer.faculty, 'fac1')
2807        self.assertEqual(payer.department, 'dep1')
2808
2809        # We simulate the approval
2810        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
2811        self.browser.open(self.browser.url + '/fake_approve')
2812        self.assertMatches('...Payment approved...',
2813                          self.browser.contents)
2814
2815        ## The new SFE-0 pin can be used for starting new session
2816        #self.browser.open(self.studycourse_path)
2817        #self.browser.getLink('Start new session').click()
2818        #pin = self.app['accesscodes']['SFE-0'].keys()[0]
2819        #parts = pin.split('-')[1:]
2820        #sfeseries, sfenumber = parts
2821        #self.browser.getControl(name="ac_series").value = sfeseries
2822        #self.browser.getControl(name="ac_number").value = sfenumber
2823        #self.browser.getControl("Start now").click()
2824        #self.assertMatches('...Session started...',
2825        #                   self.browser.contents)
2826
2827        self.assertTrue(self.student.state == 'school fee paid')
2828        return
2829
2830    def test_student_bedallocation_payment(self):
2831        # Login
2832        self.browser.open(self.login_path)
2833        self.browser.getControl(name="form.login").value = self.student_id
2834        self.browser.getControl(name="form.password").value = 'spwd'
2835        self.browser.getControl("Login").click()
2836        self.browser.open(self.payments_path)
2837        self.browser.open(self.payments_path + '/addop')
2838        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
2839        self.browser.getControl("Create ticket").click()
2840        self.assertMatches('...ticket created...',
2841                           self.browser.contents)
2842        # Students can remove only online payment tickets which have
2843        # not received a valid callback
2844        self.browser.open(self.payments_path)
2845        ctrl = self.browser.getControl(name='val_id')
2846        value = ctrl.options[0]
2847        ctrl.getControl(value=value).selected = True
2848        self.browser.getControl("Remove selected", index=0).click()
2849        self.assertTrue('Successfully removed' in self.browser.contents)
2850
2851    def test_student_maintenance_payment(self):
2852        # Login
2853        self.browser.open(self.login_path)
2854        self.browser.getControl(name="form.login").value = self.student_id
2855        self.browser.getControl(name="form.password").value = 'spwd'
2856        self.browser.getControl("Login").click()
2857        self.browser.open(self.payments_path)
2858        self.browser.open(self.payments_path + '/addop')
2859        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
2860        self.browser.getControl("Create ticket").click()
2861        self.assertMatches('...You have not yet booked accommodation...',
2862                           self.browser.contents)
2863        # We continue this test in test_student_accommodation
2864
2865    def test_student_previous_payments(self):
2866        configuration = createObject('waeup.SessionConfiguration')
2867        configuration.academic_session = 2000
2868        configuration.clearance_fee = 3456.0
2869        configuration.booking_fee = 123.4
2870        self.app['configuration'].addSessionConfiguration(configuration)
2871        configuration2 = createObject('waeup.SessionConfiguration')
2872        configuration2.academic_session = 2003
2873        configuration2.clearance_fee = 3456.0
2874        configuration2.booking_fee = 123.4
2875        self.app['configuration'].addSessionConfiguration(configuration2)
2876        configuration3 = createObject('waeup.SessionConfiguration')
2877        configuration3.academic_session = 2005
2878        configuration3.clearance_fee = 3456.0
2879        configuration3.booking_fee = 123.4
2880        self.app['configuration'].addSessionConfiguration(configuration3)
2881        self.student['studycourse'].entry_session = 2002
2882
2883        # Login
2884        self.browser.open(self.login_path)
2885        self.browser.getControl(name="form.login").value = self.student_id
2886        self.browser.getControl(name="form.password").value = 'spwd'
2887        self.browser.getControl("Login").click()
2888
2889        # Students can add previous school fee payment tickets in any state.
2890        IWorkflowState(self.student).setState('courses registered')
2891        self.browser.open(self.payments_path)
2892        self.browser.getLink("Add previous session payment ticket").click()
2893
2894        # Previous session payment form is provided
2895        self.assertEqual(self.student.current_session, 2004)
2896        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2897        self.browser.getControl(name="form.p_session").value = ['2000']
2898        self.browser.getControl(name="form.p_level").value = ['300']
2899        self.browser.getControl("Create ticket").click()
2900        self.assertMatches('...The previous session must not fall below...',
2901                           self.browser.contents)
2902        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2903        self.browser.getControl(name="form.p_session").value = ['2005']
2904        self.browser.getControl(name="form.p_level").value = ['300']
2905        self.browser.getControl("Create ticket").click()
2906        self.assertMatches('...This is not a previous session...',
2907                           self.browser.contents)
2908        # Students can pay current session school fee.
2909        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2910        self.browser.getControl(name="form.p_session").value = ['2004']
2911        self.browser.getControl(name="form.p_level").value = ['300']
2912        self.browser.getControl("Create ticket").click()
2913        self.assertMatches('...ticket created...',
2914                           self.browser.contents)
2915        ctrl = self.browser.getControl(name='val_id')
2916        value = ctrl.options[0]
2917        self.browser.getLink(value).click()
2918        self.assertMatches('...Amount Authorized...',
2919                           self.browser.contents)
2920        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2921
2922        # Payment session is properly set
2923        self.assertEqual(self.student['payments'][value].p_session, 2004)
2924        self.assertEqual(self.student['payments'][value].p_level, 300)
2925
2926        # We simulate the approval
2927        self.browser.open(self.browser.url + '/fake_approve')
2928        self.assertMatches('...Payment approved...',
2929                          self.browser.contents)
2930
2931        # No AC has been created
2932        self.assertEqual(len(self.app['accesscodes']['SFE-0'].keys()), 0)
2933        self.assertTrue(self.student['payments'][value].ac is None)
2934
2935        # Current payment flag is set False
2936        self.assertFalse(self.student['payments'][value].p_current)
2937
2938        # Button and form are not available for students who are in
2939        # states up to cleared
2940        self.student['studycourse'].entry_session = 2004
2941        IWorkflowState(self.student).setState('cleared')
2942        self.browser.open(self.payments_path)
2943        self.assertFalse(
2944            "Add previous session payment ticket" in self.browser.contents)
2945        self.browser.open(self.payments_path + '/addpp')
2946        self.assertTrue(
2947            "No previous payment to be made" in self.browser.contents)
2948        return
2949
2950    def test_postgraduate_student_payments(self):
2951        configuration = createObject('waeup.SessionConfiguration')
2952        configuration.academic_session = 2005
2953        self.app['configuration'].addSessionConfiguration(configuration)
2954        self.certificate.study_mode = 'pg_ft'
2955        self.certificate.start_level = 999
2956        self.certificate.end_level = 999
2957        self.student['studycourse'].current_level = 999
2958        # Login
2959        self.browser.open(self.login_path)
2960        self.browser.getControl(name="form.login").value = self.student_id
2961        self.browser.getControl(name="form.password").value = 'spwd'
2962        self.browser.getControl("Login").click()
2963        # Students can add online school fee payment tickets.
2964        IWorkflowState(self.student).setState('cleared')
2965        self.browser.open(self.payments_path)
2966        self.browser.getLink("Add current session payment ticket").click()
2967        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2968        self.browser.getControl("Create ticket").click()
2969        self.assertMatches('...ticket created...',
2970                           self.browser.contents)
2971        ctrl = self.browser.getControl(name='val_id')
2972        value = ctrl.options[0]
2973        self.browser.getLink(value).click()
2974        self.assertMatches('...Amount Authorized...',
2975                           self.browser.contents)
2976        # Payment session and level are current ones.
2977        # Postgrads have to pay school_fee_1.
2978        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
2979        self.assertEqual(self.student['payments'][value].p_session, 2004)
2980        self.assertEqual(self.student['payments'][value].p_level, 999)
2981
2982        # We simulate the approval
2983        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
2984        self.browser.open(self.browser.url + '/fake_approve')
2985        self.assertMatches('...Payment approved...',
2986                          self.browser.contents)
2987
2988        ## The new SFE-0 pin can be used for starting session
2989        #self.browser.open(self.studycourse_path)
2990        #self.browser.getLink('Start new session').click()
2991        #pin = self.app['accesscodes']['SFE-0'].keys()[0]
2992        #parts = pin.split('-')[1:]
2993        #sfeseries, sfenumber = parts
2994        #self.browser.getControl(name="ac_series").value = sfeseries
2995        #self.browser.getControl(name="ac_number").value = sfenumber
2996        #self.browser.getControl("Start now").click()
2997        #self.assertMatches('...Session started...',
2998        #                   self.browser.contents)
2999
3000        self.assertTrue(self.student.state == 'school fee paid')
3001
3002        # Postgrad students do not need to register courses the
3003        # can just pay for the next session.
3004        self.browser.open(self.payments_path)
3005        # Remove first payment to be sure that we access the right ticket
3006        del self.student['payments'][value]
3007        self.browser.getLink("Add current session payment ticket").click()
3008        self.browser.getControl(name="form.p_category").value = ['schoolfee']
3009        self.browser.getControl("Create ticket").click()
3010        ctrl = self.browser.getControl(name='val_id')
3011        value = ctrl.options[0]
3012        self.browser.getLink(value).click()
3013        # Payment session has increased by one, payment level remains the same.
3014        # Returning Postgraduates have to pay school_fee_2.
3015        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
3016        self.assertEqual(self.student['payments'][value].p_session, 2005)
3017        self.assertEqual(self.student['payments'][value].p_level, 999)
3018
3019        # Student is still in old session
3020        self.assertEqual(self.student.current_session, 2004)
3021
3022        # We do not need to pay the ticket if any other
3023        # SFE pin is provided
3024        pin_container = self.app['accesscodes']
3025        pin_container.createBatch(
3026            datetime.utcnow(), 'some_userid', 'SFE', 9.99, 1)
3027        pin = pin_container['SFE-1'].values()[0].representation
3028        sfeseries, sfenumber = pin.split('-')[1:]
3029        # The new SFE-1 pin can be used for starting new session
3030        self.browser.open(self.studycourse_path)
3031        self.browser.getLink('Start new session').click()
3032        self.browser.getControl(name="ac_series").value = sfeseries
3033        self.browser.getControl(name="ac_number").value = sfenumber
3034        self.browser.getControl("Start now").click()
3035        self.assertMatches('...Session started...',
3036                           self.browser.contents)
3037        self.assertTrue(self.student.state == 'school fee paid')
3038        # Student is in new session
3039        self.assertEqual(self.student.current_session, 2005)
3040        self.assertEqual(self.student['studycourse'].current_level, 999)
3041        return
3042
3043    def test_student_accommodation(self):
3044        # Create a second hostel with one bed
3045        hostel = Hostel()
3046        hostel.hostel_id = u'hall-2'
3047        hostel.hostel_name = u'Hall 2'
3048        self.app['hostels'].addHostel(hostel)
3049        bed = Bed()
3050        bed.bed_id = u'hall-2_A_101_A'
3051        bed.bed_number = 1
3052        bed.owner = NOT_OCCUPIED
3053        bed.bed_type = u'regular_female_fr'
3054        self.app['hostels'][hostel.hostel_id].addBed(bed)
3055
3056        self.browser.open(self.login_path)
3057        self.browser.getControl(name="form.login").value = self.student_id
3058        self.browser.getControl(name="form.password").value = 'spwd'
3059        self.browser.getControl("Login").click()
3060        # Students can add online booking fee payment tickets and open the
3061        # callback view (see test_manage_payments).
3062        self.browser.getLink("Payments").click()
3063        self.browser.getLink("Add current session payment ticket").click()
3064        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
3065        self.browser.getControl("Create ticket").click()
3066        ctrl = self.browser.getControl(name='val_id')
3067        value = ctrl.options[0]
3068        self.browser.getLink(value).click()
3069        self.browser.open(self.browser.url + '/fake_approve')
3070        # The new HOS-0 pin has been created.
3071        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
3072        pin = self.app['accesscodes']['HOS-0'].keys()[0]
3073        ac = self.app['accesscodes']['HOS-0'][pin]
3074        parts = pin.split('-')[1:]
3075        sfeseries, sfenumber = parts
3076        # Students can use HOS code and book a bed space with it ...
3077        self.browser.open(self.acco_path)
3078        # ... but not if booking period has expired ...
3079        self.app['hostels'].enddate = datetime.now(pytz.utc)
3080        self.browser.getControl("Book accommodation").click()
3081        self.assertMatches('...Outside booking period: ...',
3082                           self.browser.contents)
3083        self.app['hostels'].enddate = datetime.now(pytz.utc) + timedelta(days=10)
3084        # ... or student data are incomplete ...
3085        self.student['studycourse'].current_level = None
3086        self.browser.getControl("Book accommodation").click()
3087        self.assertMatches('...Your data are incomplete...',
3088            self.browser.contents)
3089        self.student['studycourse'].current_level = 100
3090        # ... or student is not the an allowed state ...
3091        self.browser.getControl("Book accommodation").click()
3092        self.assertMatches('...You are in the wrong...',
3093                           self.browser.contents)
3094        # Students can still not see the disired hostel selector.
3095        self.assertFalse('desired hostel' in self.browser.contents)
3096        IWorkflowInfo(self.student).fireTransition('admit')
3097        # Students can now see the disired hostel selector.
3098        self.browser.reload()
3099        self.browser.open(self.acco_path)
3100        self.assertTrue('desired hostel' in self.browser.contents)
3101        self.browser.getControl(name="hostel").value = ['hall-2']
3102        self.browser.getControl("Save").click()
3103        self.assertTrue('selection has been saved' in self.browser.contents)
3104        self.assertTrue('<option selected="selected" value="hall-2">'
3105            in self.browser.contents)
3106        self.browser.getControl("Book accommodation").click()
3107        self.assertMatches('...Activation Code:...',
3108                           self.browser.contents)
3109        # Student can't use faked ACs ...
3110        self.browser.getControl(name="ac_series").value = u'nonsense'
3111        self.browser.getControl(name="ac_number").value = sfenumber
3112        self.browser.getControl("Create bed ticket").click()
3113        self.assertMatches('...Activation code is invalid...',
3114                           self.browser.contents)
3115        # ... or ACs owned by somebody else.
3116        ac.owner = u'Anybody'
3117        self.browser.getControl(name="ac_series").value = sfeseries
3118        self.browser.getControl(name="ac_number").value = sfenumber
3119        self.browser.getControl("Create bed ticket").click()
3120        # Hostel 2 has only a bed for women.
3121        self.assertTrue('There is no free bed in your desired hostel'
3122            in self.browser.contents)
3123        self.browser.getControl(name="hostel").value = ['hall-1']
3124        self.browser.getControl("Save").click()
3125        self.browser.getControl("Book accommodation").click()
3126        # Student can't use faked ACs ...
3127        self.browser.getControl(name="ac_series").value = sfeseries
3128        self.browser.getControl(name="ac_number").value = sfenumber
3129        self.browser.getControl("Create bed ticket").click()
3130        self.assertMatches('...You are not the owner of this access code...',
3131                           self.browser.contents)
3132        # The bed remains empty.
3133        bed = self.app['hostels']['hall-1']['hall-1_A_101_A']
3134        self.assertTrue(bed.owner == NOT_OCCUPIED)
3135        ac.owner = self.student_id
3136        self.browser.getControl(name="ac_series").value = sfeseries
3137        self.browser.getControl(name="ac_number").value = sfenumber
3138        self.browser.getControl("Create bed ticket").click()
3139        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
3140                           self.browser.contents)
3141        # Bed has been allocated.
3142        self.assertTrue(bed.owner == self.student_id)
3143        # BedTicketAddPage is now blocked.
3144        self.browser.getControl("Book accommodation").click()
3145        self.assertMatches('...You already booked a bed space...',
3146            self.browser.contents)
3147        # The bed ticket displays the data correctly.
3148        self.browser.open(self.acco_path + '/2004')
3149        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
3150                           self.browser.contents)
3151        self.assertMatches('...2004/2005...', self.browser.contents)
3152        self.assertMatches('...regular_male_fr...', self.browser.contents)
3153        self.assertMatches('...%s...' % pin, self.browser.contents)
3154        # Students can open the pdf slip.
3155        self.browser.open(self.browser.url + '/bed_allocation_slip.pdf')
3156        self.assertEqual(self.browser.headers['Status'], '200 Ok')
3157        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
3158        # Students can't relocate themselves.
3159        self.assertFalse('Relocate' in self.browser.contents)
3160        relocate_path = self.acco_path + '/2004/relocate'
3161        self.assertRaises(
3162            Unauthorized, self.browser.open, relocate_path)
3163        # Students can't see the Remove button and check boxes.
3164        self.browser.open(self.acco_path)
3165        self.assertFalse('Remove' in self.browser.contents)
3166        self.assertFalse('val_id' in self.browser.contents)
3167        # Students can pay maintenance fee now.
3168        self.browser.open(self.payments_path)
3169        self.browser.open(self.payments_path + '/addop')
3170        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
3171        self.browser.getControl("Create ticket").click()
3172        self.assertMatches('...Payment ticket created...',
3173                           self.browser.contents)
3174        ctrl = self.browser.getControl(name='val_id')
3175        value = ctrl.options[0]
3176        # Maintennace fee is taken from the hostel object.
3177        self.assertEqual(self.student['payments'][value].amount_auth, 876.0)
3178        # If the hostel's maintenance fee isn't set, the fee is
3179        # taken from the session configuration object.
3180        self.app['hostels']['hall-1'].maint_fee = 0.0
3181        self.browser.open(self.payments_path + '/addop')
3182        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
3183        self.browser.getControl("Create ticket").click()
3184        ctrl = self.browser.getControl(name='val_id')
3185        value = ctrl.options[1]
3186        self.assertEqual(self.student['payments'][value].amount_auth, 987.0)
3187        # The bedticket is aware of successfull maintenance fee payment
3188        bedticket = self.student['accommodation']['2004']
3189        self.assertFalse(bedticket.maint_payment_made)
3190        self.student['payments'][value].approve()
3191        self.assertTrue(bedticket.maint_payment_made)
3192        return
3193
3194    def test_change_password_request(self):
3195        self.browser.open('http://localhost/app/changepw')
3196        self.browser.getControl(name="form.identifier").value = '123'
3197        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
3198        self.browser.getControl("Send login credentials").click()
3199        self.assertTrue('An email with' in self.browser.contents)
3200
3201    def test_student_expired_personal_data(self):
3202        # Login
3203        IWorkflowState(self.student).setState('school fee paid')
3204        delta = timedelta(days=180)
3205        self.student.personal_updated = datetime.utcnow() - delta
3206        self.browser.open(self.login_path)
3207        self.browser.getControl(name="form.login").value = self.student_id
3208        self.browser.getControl(name="form.password").value = 'spwd'
3209        self.browser.getControl("Login").click()
3210        self.assertEqual(self.browser.url, self.student_path)
3211        self.assertTrue(
3212            'You logged in' in self.browser.contents)
3213        # Students don't see personal_updated field in edit form
3214        self.browser.open(self.edit_personal_path)
3215        self.assertFalse('Updated' in self.browser.contents)
3216        self.browser.open(self.personal_path)
3217        self.assertTrue('Updated' in self.browser.contents)
3218        self.browser.getLink("Logout").click()
3219        delta = timedelta(days=181)
3220        self.student.personal_updated = datetime.utcnow() - delta
3221        self.browser.open(self.login_path)
3222        self.browser.getControl(name="form.login").value = self.student_id
3223        self.browser.getControl(name="form.password").value = 'spwd'
3224        self.browser.getControl("Login").click()
3225        self.assertEqual(self.browser.url, self.edit_personal_path)
3226        self.assertTrue(
3227            'Your personal data record is outdated.' in self.browser.contents)
3228
3229    def test_request_transcript(self):
3230        IWorkflowState(self.student).setState('graduated')
3231        self.browser.open(self.login_path)
3232        self.browser.getControl(name="form.login").value = self.student_id
3233        self.browser.getControl(name="form.password").value = 'spwd'
3234        self.browser.getControl("Login").click()
3235        self.assertMatches(
3236            '...You logged in...', self.browser.contents)
3237        # Create payment ticket
3238        self.browser.open(self.payments_path)
3239        self.browser.open(self.payments_path + '/addop')
3240        self.browser.getControl(name="form.p_category").value = ['transcript']
3241        self.browser.getControl("Create ticket").click()
3242        ctrl = self.browser.getControl(name='val_id')
3243        value = ctrl.options[0]
3244        self.browser.getLink(value).click()
3245        self.assertMatches('...Amount Authorized...',
3246                           self.browser.contents)
3247        self.assertEqual(self.student['payments'][value].amount_auth, 4567.0)
3248        # Student is the payer of the payment ticket.
3249        payer = IPayer(self.student['payments'][value])
3250        self.assertEqual(payer.display_fullname, 'Anna Tester')
3251        self.assertEqual(payer.id, self.student_id)
3252        self.assertEqual(payer.faculty, 'fac1')
3253        self.assertEqual(payer.department, 'dep1')
3254        # We simulate the approval and fetch the pin
3255        self.assertEqual(len(self.app['accesscodes']['TSC-0']),0)
3256        self.browser.open(self.browser.url + '/fake_approve')
3257        self.assertMatches('...Payment approved...',
3258                          self.browser.contents)
3259        pin = self.app['accesscodes']['TSC-0'].keys()[0]
3260        parts = pin.split('-')[1:]
3261        tscseries, tscnumber = parts
3262        # Student can use the pin to send the transcript request
3263        self.browser.open(self.student_path)
3264        self.browser.getLink("Request transcript").click()
3265        self.browser.getControl(name="ac_series").value = tscseries
3266        self.browser.getControl(name="ac_number").value = tscnumber
3267        self.browser.getControl(name="comment").value = 'Comment line 1 \nComment line2'
3268        self.browser.getControl(name="address").value = 'Address line 1 \nAddress line2'
3269        self.browser.getControl("Submit").click()
3270        self.assertMatches('...Transcript processing has been started...',
3271                          self.browser.contents)
3272        self.assertEqual(self.student.state, 'transcript requested')
3273        self.assertMatches(
3274            '... UTC K1000000 wrote:\n\nComment line 1 \n'
3275            'Comment line2\n\nDispatch Address:\nAddress line 1 \n'
3276            'Address line2\n\n', self.student.transcript_comment)
3277        # The comment has been logged
3278        logfile = os.path.join(
3279            self.app['datacenter'].storage, 'logs', 'students.log')
3280        logcontent = open(logfile).read()
3281        self.assertTrue(
3282            'K1000000 - students.browser.StudentTranscriptRequestPage - '
3283            'K1000000 - comment: Comment line 1 <br>Comment line2\n'
3284            in logcontent)
3285
3286    def test_late_registration(self):
3287        # Login
3288        delta = timedelta(days=10)
3289        self.app['configuration'][
3290            '2004'].coursereg_deadline = datetime.now(pytz.utc) - delta
3291        IWorkflowState(self.student).setState('school fee paid')
3292        self.browser.open(self.login_path)
3293        self.browser.getControl(name="form.login").value = self.student_id
3294        self.browser.getControl(name="form.password").value = 'spwd'
3295        self.browser.getControl("Login").click()
3296        self.browser.open(self.payments_path)
3297        self.browser.open(self.payments_path + '/addop')
3298        self.browser.getControl(name="form.p_category").value = ['late_registration']
3299        self.browser.getControl("Create ticket").click()
3300        self.assertMatches('...ticket created...',
3301                           self.browser.contents)
3302        self.browser.open(self.payments_path)
3303        ctrl = self.browser.getControl(name='val_id')
3304        value = ctrl.options[0]
3305        self.browser.getLink("Study Course").click()
3306        self.browser.getLink("Add course list").click()
3307        self.assertMatches('...Add current level 100 (Year 1)...',
3308                           self.browser.contents)
3309        self.browser.getControl("Create course list now").click()
3310        self.browser.getLink("100").click()
3311        self.browser.getLink("Edit course list").click()
3312        self.browser.getControl("Register course list").click()
3313        self.assertTrue('Course registration has ended. Please pay' in self.browser.contents)
3314        self.student['payments'][value].approve()
3315        self.browser.getControl("Register course list").click()
3316        self.assertTrue('Course list has been registered' in self.browser.contents)
3317        self.assertEqual(self.student.state, 'courses registered')
3318
3319
3320class StudentRequestPWTests(StudentsFullSetup):
3321    # Tests for student registration
3322
3323    layer = FunctionalLayer
3324
3325    def test_request_pw(self):
3326        # Student with wrong number can't be found.
3327        self.browser.open('http://localhost/app/requestpw')
3328        self.browser.getControl(name="form.lastname").value = 'Tester'
3329        self.browser.getControl(name="form.number").value = 'anynumber'
3330        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
3331        self.browser.getControl("Send login credentials").click()
3332        self.assertTrue('No student record found.'
3333            in self.browser.contents)
3334        # Anonymous is not informed that lastname verification failed.
3335        # It seems that the record doesn't exist.
3336        self.browser.open('http://localhost/app/requestpw')
3337        self.browser.getControl(name="form.lastname").value = 'Johnny'
3338        self.browser.getControl(name="form.number").value = '123'
3339        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
3340        self.browser.getControl("Send login credentials").click()
3341        self.assertTrue('No student record found.'
3342            in self.browser.contents)
3343        # Even with the correct lastname we can't register if a
3344        # password has been set and used.
3345        self.browser.getControl(name="form.lastname").value = 'Tester'
3346        self.browser.getControl(name="form.number").value = '123'
3347        self.browser.getControl("Send login credentials").click()
3348        self.assertTrue('Your password has already been set and used.'
3349            in self.browser.contents)
3350        self.browser.open('http://localhost/app/requestpw')
3351        self.app['students'][self.student_id].password = None
3352        # The lastname field, used for verification, is not case-sensitive.
3353        self.browser.getControl(name="form.lastname").value = 'tESTer'
3354        self.browser.getControl(name="form.number").value = '123'
3355        self.browser.getControl(name="form.email").value = 'new@yy.zz'
3356        self.browser.getControl("Send login credentials").click()
3357        # Yeah, we succeded ...
3358        self.assertTrue('Your password request was successful.'
3359            in self.browser.contents)
3360        # We can also use the matric_number instead.
3361        self.browser.open('http://localhost/app/requestpw')
3362        self.browser.getControl(name="form.lastname").value = 'tESTer'
3363        self.browser.getControl(name="form.number").value = '234'
3364        self.browser.getControl(name="form.email").value = 'new@yy.zz'
3365        self.browser.getControl("Send login credentials").click()
3366        self.assertTrue('Your password request was successful.'
3367            in self.browser.contents)
3368        # ... and  student can be found in the catalog via the email address
3369        cat = queryUtility(ICatalog, name='students_catalog')
3370        results = list(
3371            cat.searchResults(
3372            email=('new@yy.zz', 'new@yy.zz')))
3373        self.assertEqual(self.student,results[0])
3374        logfile = os.path.join(
3375            self.app['datacenter'].storage, 'logs', 'main.log')
3376        logcontent = open(logfile).read()
3377        self.assertTrue('zope.anybody - students.browser.StudentRequestPasswordPage - '
3378                        '234 (K1000000) - new@yy.zz' in logcontent)
3379        return
3380
3381    def test_student_locked_level_forms(self):
3382
3383        # Add two study levels, one current and one previous
3384        studylevel = createObject(u'waeup.StudentStudyLevel')
3385        studylevel.level = 100
3386        self.student['studycourse'].addStudentStudyLevel(
3387            self.certificate, studylevel)
3388        studylevel = createObject(u'waeup.StudentStudyLevel')
3389        studylevel.level = 200
3390        self.student['studycourse'].addStudentStudyLevel(
3391            self.certificate, studylevel)
3392        IWorkflowState(self.student).setState('school fee paid')
3393        self.student['studycourse'].current_level = 200
3394
3395        self.browser.open(self.login_path)
3396        self.browser.getControl(name="form.login").value = self.student_id
3397        self.browser.getControl(name="form.password").value = 'spwd'
3398        self.browser.getControl("Login").click()
3399
3400        self.browser.open(self.student_path + '/studycourse/200/edit')
3401        self.assertFalse('The requested form is locked' in self.browser.contents)
3402        self.browser.open(self.student_path + '/studycourse/100/edit')
3403        self.assertTrue('The requested form is locked' in self.browser.contents)
3404
3405        self.browser.open(self.student_path + '/studycourse/200/ctadd')
3406        self.assertFalse('The requested form is locked' in self.browser.contents)
3407        self.browser.open(self.student_path + '/studycourse/100/ctadd')
3408        self.assertTrue('The requested form is locked' in self.browser.contents)
3409
3410        IWorkflowState(self.student).setState('courses registered')
3411        self.browser.open(self.student_path + '/studycourse/200/edit')
3412        self.assertTrue('The requested form is locked' in self.browser.contents)
3413        self.browser.open(self.student_path + '/studycourse/200/ctadd')
3414        self.assertTrue('The requested form is locked' in self.browser.contents)
3415
3416
3417class PublicPagesTests(StudentsFullSetup):
3418    # Tests for simple webservices
3419
3420    layer = FunctionalLayer
3421
3422    def test_paymentrequest(self):
3423        payment = createObject('waeup.StudentOnlinePayment')
3424        payment.p_category = u'schoolfee'
3425        payment.p_session = self.student.current_session
3426        payment.p_item = u'My Certificate'
3427        payment.p_id = u'anyid'
3428        self.student['payments']['anykey'] = payment
3429        # Request information about unpaid payment ticket
3430        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
3431        self.assertEqual(self.browser.contents, '-1')
3432        # Request information about paid payment ticket
3433        payment.p_state = u'paid'
3434        notify(grok.ObjectModifiedEvent(payment))
3435        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
3436        self.assertEqual(self.browser.contents,
3437            'FULL_NAME=Anna Tester&FACULTY=fac1&DEPARTMENT=dep1'
3438            '&PAYMENT_ITEM=My Certificate&PAYMENT_CATEGORY=School Fee'
3439            '&ACADEMIC_SESSION=2004/2005&MATRIC_NUMBER=234&REG_NUMBER=123'
3440            '&FEE_AMOUNT=0.0')
3441        self.browser.open('http://localhost/app/paymentrequest?NONSENSE=nonsense')
3442        self.assertEqual(self.browser.contents, '-1')
3443        self.browser.open('http://localhost/app/paymentrequest?P_ID=nonsense')
3444        self.assertEqual(self.browser.contents, '-1')
3445
3446class StudentDataExportTests(StudentsFullSetup, FunctionalAsyncTestCase):
3447    # Tests for StudentsContainer class views and pages
3448
3449    layer = FunctionalLayer
3450
3451    def wait_for_export_job_completed(self):
3452        # helper function waiting until the current export job is completed
3453        manager = getUtility(IJobManager)
3454        job_id = self.app['datacenter'].running_exports[0][0]
3455        job = manager.get(job_id)
3456        wait_for_result(job)
3457        return job_id
3458
3459    def test_datacenter_export(self):
3460        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3461        self.browser.open('http://localhost/app/datacenter/@@exportconfig')
3462        self.browser.getControl(name="exporter").value = ['bursary']
3463        self.browser.getControl(name="session").value = ['2004']
3464        self.browser.getControl(name="level").value = ['100']
3465        self.browser.getControl(name="mode").value = ['ug_ft']
3466        self.browser.getControl(name="payments_start").value = '13/12/2012'
3467        self.browser.getControl(name="payments_end").value = '14/12/2012'
3468        self.browser.getControl("Create CSV file").click()
3469
3470        # When the job is finished and we reload the page...
3471        job_id = self.wait_for_export_job_completed()
3472        # ... the csv file can be downloaded ...
3473        self.browser.open('http://localhost/app/datacenter/@@export')
3474        self.browser.getLink("Download").click()
3475        self.assertEqual(self.browser.headers['content-type'],
3476            'text/csv; charset=UTF-8')
3477        self.assertTrue(
3478            'filename="WAeUP.Kofa_bursary_%s.csv' % job_id in
3479            self.browser.headers['content-disposition'])
3480        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3481        job_id = self.app['datacenter'].running_exports[0][0]
3482        # ... and discarded
3483        self.browser.open('http://localhost/app/datacenter/@@export')
3484        self.browser.getControl("Discard").click()
3485        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3486        # Creation, downloading and discarding is logged
3487        logfile = os.path.join(
3488            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3489        logcontent = open(logfile).read()
3490        self.assertTrue(
3491            'zope.mgr - students.browser.DatacenterExportJobContainerJobConfig '
3492            '- exported: bursary (2004, 100, ug_ft, None, None, None, '
3493            '13/12/2012, 14/12/2012), job_id=%s'
3494            % job_id in logcontent
3495            )
3496        self.assertTrue(
3497            'zope.mgr - browser.pages.ExportCSVView '
3498            '- downloaded: WAeUP.Kofa_bursary_%s.csv, job_id=%s'
3499            % (job_id, job_id) in logcontent
3500            )
3501        self.assertTrue(
3502            'zope.mgr - browser.pages.ExportCSVPage '
3503            '- discarded: job_id=%s' % job_id in logcontent
3504            )
3505
3506    def test_datacenter_export_selected(self):
3507        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3508        self.browser.open('http://localhost/app/datacenter/@@exportselected')
3509        self.browser.getControl(name="exporter").value = ['students']
3510        self.browser.getControl(name="students").value = 'K1000000'
3511        self.browser.getControl("Create CSV file").click()
3512        # When the job is finished and we reload the page...
3513        job_id = self.wait_for_export_job_completed()
3514        # ... the csv file can be downloaded ...
3515        self.browser.open('http://localhost/app/datacenter/@@export')
3516        self.browser.getLink("Download").click()
3517        self.assertEqual(self.browser.headers['content-type'],
3518            'text/csv; charset=UTF-8')
3519        self.assertTrue(
3520            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3521            self.browser.headers['content-disposition'])
3522        self.assertTrue(
3523            'adm_code,clr_code,date_of_birth,email,employer,'
3524            'firstname,lastname,matric_number,middlename,nationality,'
3525            'officer_comment,perm_address,personal_updated,phone,reg_number,'
3526            'sex,student_id,suspended,suspended_comment,transcript_comment,'
3527            'password,state,history,certcode,is_postgrad,current_level,'
3528            'current_session\r\n'
3529            ',,1981-02-04#,aa@aa.ng,,Anna,Tester,234,,,,,,'
3530            '1234#,123,m,K1000000,0,,,{SSHA}' in self.browser.contents)
3531
3532    def test_payment_dates(self):
3533        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3534        self.browser.open('http://localhost/app/datacenter/@@exportconfig')
3535        self.browser.getControl(name="exporter").value = ['bursary']
3536        self.browser.getControl(name="session").value = ['2004']
3537        self.browser.getControl(name="level").value = ['100']
3538        self.browser.getControl(name="mode").value = ['ug_ft']
3539        self.browser.getControl(name="payments_start").value = '13/12/2012'
3540        # If one payment date is missing, an error message appears
3541        self.browser.getControl(name="payments_end").value = ''
3542        self.browser.getControl("Create CSV file").click()
3543        self.assertTrue('Payment dates do not match format d/m/Y'
3544            in self.browser.contents)
3545
3546    def test_faculties_export(self):
3547        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3548        facs_path = 'http://localhost/app/faculties'
3549        self.browser.open(facs_path)
3550        self.browser.getLink("Export student data").click()
3551        self.browser.getControl("Configure new export").click()
3552        self.browser.getControl(name="exporter").value = ['bursary']
3553        self.browser.getControl(name="session").value = ['2004']
3554        self.browser.getControl(name="level").value = ['100']
3555        self.browser.getControl(name="mode").value = ['ug_ft']
3556        self.browser.getControl(name="payments_start").value = '13/12/2012'
3557        self.browser.getControl(name="payments_end").value = '14/12/2012'
3558        self.browser.getControl("Create CSV file").click()
3559
3560        # When the job is finished and we reload the page...
3561        job_id = self.wait_for_export_job_completed()
3562        self.browser.open(facs_path + '/exports')
3563        # ... the csv file can be downloaded ...
3564        self.browser.getLink("Download").click()
3565        self.assertEqual(self.browser.headers['content-type'],
3566            'text/csv; charset=UTF-8')
3567        self.assertTrue(
3568            'filename="WAeUP.Kofa_bursary_%s.csv' % job_id in
3569            self.browser.headers['content-disposition'])
3570        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3571        job_id = self.app['datacenter'].running_exports[0][0]
3572        # ... and discarded
3573        self.browser.open(facs_path + '/exports')
3574        self.browser.getControl("Discard").click()
3575        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3576        # Creation, downloading and discarding is logged
3577        logfile = os.path.join(
3578            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3579        logcontent = open(logfile).read()
3580        self.assertTrue(
3581            'zope.mgr - students.browser.FacultiesExportJobContainerJobConfig '
3582            '- exported: bursary (2004, 100, ug_ft, None, None, None, '
3583            '13/12/2012, 14/12/2012), job_id=%s'
3584            % job_id in logcontent
3585            )
3586        self.assertTrue(
3587            'zope.mgr - students.browser.ExportJobContainerDownload '
3588            '- downloaded: WAeUP.Kofa_bursary_%s.csv, job_id=%s'
3589            % (job_id, job_id) in logcontent
3590            )
3591        self.assertTrue(
3592            'zope.mgr - students.browser.ExportJobContainerOverview '
3593            '- discarded: job_id=%s' % job_id in logcontent
3594            )
3595
3596    def test_faculty_export(self):
3597        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3598        fac1_path = 'http://localhost/app/faculties/fac1'
3599        self.browser.open(fac1_path)
3600        self.browser.getLink("Export student data").click()
3601        self.browser.getControl("Configure new export").click()
3602        self.browser.getControl(name="exporter").value = ['students']
3603        self.browser.getControl(name="session").value = ['2004']
3604        self.browser.getControl(name="level").value = ['100']
3605        self.browser.getControl(name="mode").value = ['ug_ft']
3606        # The testbrowser does not hide the payment period fields, but
3607        # values are ignored when using the students exporter.
3608        self.browser.getControl(name="payments_start").value = '13/12/2012'
3609        self.browser.getControl(name="payments_end").value = '14/12/2012'
3610        self.browser.getControl("Create CSV file").click()
3611
3612        # When the job is finished and we reload the page...
3613        job_id = self.wait_for_export_job_completed()
3614        self.browser.open(fac1_path + '/exports')
3615        # ... the csv file can be downloaded ...
3616        self.browser.getLink("Download").click()
3617        self.assertEqual(self.browser.headers['content-type'],
3618            'text/csv; charset=UTF-8')
3619        self.assertTrue(
3620            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3621            self.browser.headers['content-disposition'])
3622        self.assertTrue(
3623            'adm_code,clr_code,date_of_birth,email,employer,'
3624            'firstname,lastname,matric_number,middlename,nationality,'
3625            'officer_comment,perm_address,personal_updated,phone,reg_number,'
3626            'sex,student_id,suspended,suspended_comment,transcript_comment,'
3627            'password,state,history,certcode,is_postgrad,current_level,'
3628            'current_session\r\n'
3629            ',,1981-02-04#,aa@aa.ng,,Anna,Tester,234,,,,,,'
3630            '1234#,123,m,K1000000,0,,,{SSHA}' in self.browser.contents)
3631        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3632        job_id = self.app['datacenter'].running_exports[0][0]
3633        # ... and discarded
3634        self.browser.open(fac1_path + '/exports')
3635        self.browser.getControl("Discard").click()
3636        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3637        # Creation, downloading and discarding is logged
3638        logfile = os.path.join(
3639            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3640        logcontent = open(logfile).read()
3641        self.assertTrue(
3642            'zope.mgr - students.browser.FacultyExportJobContainerJobConfig '
3643            '- exported: students (2004, 100, ug_ft, fac1, None, None, '
3644            '13/12/2012, 14/12/2012), job_id=%s'
3645            % job_id in logcontent
3646            )
3647        self.assertTrue(
3648            'zope.mgr - students.browser.ExportJobContainerDownload '
3649            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3650            % (job_id, job_id) in logcontent
3651            )
3652        self.assertTrue(
3653            'zope.mgr - students.browser.ExportJobContainerOverview '
3654            '- discarded: job_id=%s' % job_id in logcontent
3655            )
3656
3657    def test_department_export(self):
3658        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3659        dep1_path = 'http://localhost/app/faculties/fac1/dep1'
3660        self.browser.open(dep1_path)
3661        self.browser.getLink("Export student data").click()
3662        self.browser.getControl("Configure new export").click()
3663        self.browser.getControl(name="exporter").value = ['students']
3664        self.browser.getControl(name="session").value = ['2004']
3665        self.browser.getControl(name="level").value = ['100']
3666        self.browser.getControl(name="mode").value = ['ug_ft']
3667        # The testbrowser does not hide the payment period fields, but
3668        # values are ignored when using the students exporter.
3669        self.browser.getControl(name="payments_start").value = '13/12/2012'
3670        self.browser.getControl(name="payments_end").value = '14/12/2012'
3671        self.browser.getControl("Create CSV file").click()
3672
3673        # When the job is finished and we reload the page...
3674        job_id = self.wait_for_export_job_completed()
3675        self.browser.open(dep1_path + '/exports')
3676        # ... the csv file can be downloaded ...
3677        self.browser.getLink("Download").click()
3678        self.assertEqual(self.browser.headers['content-type'],
3679            'text/csv; charset=UTF-8')
3680        self.assertTrue(
3681            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3682            self.browser.headers['content-disposition'])
3683        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3684        job_id = self.app['datacenter'].running_exports[0][0]
3685        # ... and discarded
3686        self.browser.open(dep1_path + '/exports')
3687        self.browser.getControl("Discard").click()
3688        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3689        # Creation, downloading and discarding is logged
3690        logfile = os.path.join(
3691            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3692        logcontent = open(logfile).read()
3693        self.assertTrue(
3694            'zope.mgr - students.browser.DepartmentExportJobContainerJobConfig '
3695            '- exported: students (2004, 100, ug_ft, None, dep1, None, '
3696            '13/12/2012, 14/12/2012), job_id=%s'
3697            % job_id in logcontent
3698            )
3699        self.assertTrue(
3700            'zope.mgr - students.browser.ExportJobContainerDownload '
3701            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3702            % (job_id, job_id) in logcontent
3703            )
3704        self.assertTrue(
3705            'zope.mgr - students.browser.ExportJobContainerOverview '
3706            '- discarded: job_id=%s' % job_id in logcontent
3707            )
3708
3709    def test_certificate_export(self):
3710        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3711        cert1_path = 'http://localhost/app/faculties/fac1/dep1/certificates/CERT1'
3712        self.browser.open(cert1_path)
3713        self.browser.getLink("Export student data").click()
3714        self.browser.getControl("Configure new export").click()
3715        self.browser.getControl(name="exporter").value = ['students']
3716        self.browser.getControl(name="session").value = ['2004']
3717        self.browser.getControl(name="level").value = ['100']
3718        self.browser.getControl("Create CSV file").click()
3719
3720        # When the job is finished and we reload the page...
3721        job_id = self.wait_for_export_job_completed()
3722        self.browser.open(cert1_path + '/exports')
3723        # ... the csv file can be downloaded ...
3724        self.browser.getLink("Download").click()
3725        self.assertEqual(self.browser.headers['content-type'],
3726            'text/csv; charset=UTF-8')
3727        self.assertTrue(
3728            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3729            self.browser.headers['content-disposition'])
3730        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3731        job_id = self.app['datacenter'].running_exports[0][0]
3732        # ... and discarded
3733        self.browser.open(cert1_path + '/exports')
3734        self.browser.getControl("Discard").click()
3735        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3736        # Creation, downloading and discarding is logged
3737        logfile = os.path.join(
3738            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3739        logcontent = open(logfile).read()
3740        self.assertTrue(
3741            'zope.mgr - students.browser.CertificateExportJobContainerJobConfig '
3742            '- exported: students (2004, 100, None, None, None, CERT1, None, None), '
3743            'job_id=%s'
3744            % job_id in logcontent
3745            )
3746        self.assertTrue(
3747            'zope.mgr - students.browser.ExportJobContainerDownload '
3748            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3749            % (job_id, job_id) in logcontent
3750            )
3751        self.assertTrue(
3752            'zope.mgr - students.browser.ExportJobContainerOverview '
3753            '- discarded: job_id=%s' % job_id in logcontent
3754            )
3755
3756    def test_course_export_students(self):
3757        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3758        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
3759        self.browser.open(course1_path)
3760        self.browser.getLink("Export student data").click()
3761        self.browser.getControl("Configure new export").click()
3762        self.browser.getControl(name="exporter").value = ['students']
3763        self.browser.getControl(name="session").value = ['2004']
3764        self.browser.getControl(name="level").value = ['100']
3765        self.browser.getControl("Create CSV file").click()
3766
3767        # When the job is finished and we reload the page...
3768        job_id = self.wait_for_export_job_completed()
3769        self.browser.open(course1_path + '/exports')
3770        # ... the csv file can be downloaded ...
3771        self.browser.getLink("Download").click()
3772        self.assertEqual(self.browser.headers['content-type'],
3773            'text/csv; charset=UTF-8')
3774        self.assertTrue(
3775            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3776            self.browser.headers['content-disposition'])
3777        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3778        job_id = self.app['datacenter'].running_exports[0][0]
3779        # ... and discarded
3780        self.browser.open(course1_path + '/exports')
3781        self.browser.getControl("Discard").click()
3782        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3783        # Creation, downloading and discarding is logged
3784        logfile = os.path.join(
3785            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3786        logcontent = open(logfile).read()
3787        self.assertTrue(
3788            'zope.mgr - students.browser.CourseExportJobContainerJobConfig '
3789            '- exported: students (2004, 100, COURSE1), job_id=%s'
3790            % job_id in logcontent
3791            )
3792        self.assertTrue(
3793            'zope.mgr - students.browser.ExportJobContainerDownload '
3794            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3795            % (job_id, job_id) in logcontent
3796            )
3797        self.assertTrue(
3798            'zope.mgr - students.browser.ExportJobContainerOverview '
3799            '- discarded: job_id=%s' % job_id in logcontent
3800            )
3801
3802    def test_course_export_coursetickets(self):
3803        # We add study level 100 to the student's studycourse
3804        studylevel = StudentStudyLevel()
3805        studylevel.level = 100
3806        studylevel.level_session = 2004
3807        self.student['studycourse'].addStudentStudyLevel(
3808            self.certificate,studylevel)
3809        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3810        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
3811        self.browser.open(course1_path)
3812        self.browser.getLink("Export student data").click()
3813        self.browser.getControl("Configure new export").click()
3814        self.browser.getControl(name="exporter").value = ['coursetickets']
3815        self.browser.getControl(name="session").value = ['2004']
3816        self.browser.getControl(name="level").value = ['100']
3817        self.browser.getControl("Create CSV file").click()
3818        # When the job is finished and we reload the page...
3819        job_id = self.wait_for_export_job_completed()
3820        self.browser.open(course1_path + '/exports')
3821        # ... the csv file can be downloaded ...
3822        self.browser.getLink("Download").click()
3823        self.assertEqual(self.browser.headers['content-type'],
3824            'text/csv; charset=UTF-8')
3825        self.assertTrue(
3826            'filename="WAeUP.Kofa_coursetickets_%s.csv' % job_id in
3827            self.browser.headers['content-disposition'])
3828        # ... and contains the course ticket COURSE1
3829        self.assertEqual(self.browser.contents,
3830            'automatic,carry_over,code,credits,dcode,fcode,level,'
3831            'level_session,mandatory,passmark,score,semester,title,'
3832            'student_id,certcode,display_fullname\r\n1,0,COURSE1,10,'
3833            'dep1,fac1,100,2004,1,40,,1,'
3834            'Unnamed Course,K1000000,CERT1,Anna Tester\r\n')
3835
3836        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3837        job_id = self.app['datacenter'].running_exports[0][0]
3838        # Thew job can be discarded
3839        self.browser.open(course1_path + '/exports')
3840        self.browser.getControl("Discard").click()
3841        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3842        # Creation, downloading and discarding is logged
3843        logfile = os.path.join(
3844            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3845        logcontent = open(logfile).read()
3846        self.assertTrue(
3847            'zope.mgr - students.browser.CourseExportJobContainerJobConfig '
3848            '- exported: coursetickets (2004, 100, COURSE1), job_id=%s'
3849            % job_id in logcontent
3850            )
3851        self.assertTrue(
3852            'zope.mgr - students.browser.ExportJobContainerDownload '
3853            '- downloaded: WAeUP.Kofa_coursetickets_%s.csv, job_id=%s'
3854            % (job_id, job_id) in logcontent
3855            )
3856        self.assertTrue(
3857            'zope.mgr - students.browser.ExportJobContainerOverview '
3858            '- discarded: job_id=%s' % job_id in logcontent
3859            )
3860
3861    def test_export_departmet_officers(self):
3862        # Create department officer
3863        self.app['users'].addUser('mrdepartment', 'mrdepartmentsecret')
3864        self.app['users']['mrdepartment'].email = 'mrdepartment@foo.ng'
3865        self.app['users']['mrdepartment'].title = 'Carlo Pitter'
3866        # Assign local role
3867        department = self.app['faculties']['fac1']['dep1']
3868        prmlocal = IPrincipalRoleManager(department)
3869        prmlocal.assignRoleToPrincipal('waeup.local.DepartmentOfficer', 'mrdepartment')
3870        # Login as department officer
3871        self.browser.open(self.login_path)
3872        self.browser.getControl(name="form.login").value = 'mrdepartment'
3873        self.browser.getControl(name="form.password").value = 'mrdepartmentsecret'
3874        self.browser.getControl("Login").click()
3875        self.assertMatches('...You logged in...', self.browser.contents)
3876        self.browser.open("http://localhost/app/faculties/fac1/dep1")
3877        self.browser.getLink("Export student data").click()
3878        self.browser.getControl("Configure new export").click()
3879        # Only the paymentsoverview exporter is available for department officers
3880        self.assertFalse('<option value="students">' in self.browser.contents)
3881        self.assertTrue(
3882            '<option value="paymentsoverview">' in self.browser.contents)
3883        self.browser.getControl(name="exporter").value = ['paymentsoverview']
3884        self.browser.getControl(name="session").value = ['2004']
3885        self.browser.getControl(name="level").value = ['100']
3886        self.browser.getControl("Create CSV file").click()
3887        self.assertTrue('Export started' in self.browser.contents)
3888        # Thew job can be discarded
3889        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3890        #job_id = self.app['datacenter'].running_exports[0][0]
3891        job_id = self.wait_for_export_job_completed()
3892        self.browser.open("http://localhost/app/faculties/fac1/dep1/exports")
3893        self.browser.getControl("Discard").click()
3894        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3895
3896    def test_export_bursary_officers(self):
3897        # Create bursary officer
3898        self.app['users'].addUser('mrbursary', 'mrbursarysecret')
3899        self.app['users']['mrbursary'].email = 'mrbursary@foo.ng'
3900        self.app['users']['mrbursary'].title = 'Carlo Pitter'
3901        prmglobal = IPrincipalRoleManager(self.app)
3902        prmglobal.assignRoleToPrincipal('waeup.BursaryOfficer', 'mrbursary')
3903        # Login as bursary officer
3904        self.browser.open(self.login_path)
3905        self.browser.getControl(name="form.login").value = 'mrbursary'
3906        self.browser.getControl(name="form.password").value = 'mrbursarysecret'
3907        self.browser.getControl("Login").click()
3908        self.assertMatches('...You logged in...', self.browser.contents)
3909        self.browser.getLink("Academics").click()
3910        self.browser.getLink("Export student data").click()
3911        self.browser.getControl("Configure new export").click()
3912        # Only the bursary exporter is available for bursary officers
3913        # not only at facultiescontainer level ...
3914        self.assertFalse('<option value="students">' in self.browser.contents)
3915        self.assertTrue('<option value="bursary">' in self.browser.contents)
3916        self.browser.getControl(name="exporter").value = ['bursary']
3917        self.browser.getControl(name="session").value = ['2004']
3918        self.browser.getControl(name="level").value = ['100']
3919        self.browser.getControl("Create CSV file").click()
3920        self.assertTrue('Export started' in self.browser.contents)
3921        # ... but also at other levels
3922        self.browser.open('http://localhost/app/faculties/fac1/dep1')
3923        self.browser.getLink("Export student data").click()
3924        self.browser.getControl("Configure new export").click()
3925        self.assertFalse('<option value="students">' in self.browser.contents)
3926        self.assertTrue('<option value="bursary">' in self.browser.contents)
3927        # Thew job can be discarded
3928        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3929        #job_id = self.app['datacenter'].running_exports[0][0]
3930        job_id = self.wait_for_export_job_completed()
3931        self.browser.open('http://localhost/app/faculties/exports')
3932        self.browser.getControl("Discard").click()
3933        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
Note: See TracBrowser for help on using the repository browser.