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

Last change on this file since 10277 was 10277, checked in by Henrik Bettermann, 11 years ago

Use carry over tickets in next level for GPA calculation. Ignore carry over tickets in current level.

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