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

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

Notify shell of pdf file exports.

  • Property svn:keywords set to Id
File size: 161.5 KB
Line 
1## $Id: test_browser.py 10259 2013-05-30 21:49:12Z 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 GPA will 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        self.assertTrue('<div>3.57</div>' in self.browser.contents)
800        self.assertEqual(self.student['studycourse']['100'].gpa, 3.57)
801        # Carry-over courses will be collected when next level is created
802        self.browser.open(self.student_path + '/studycourse/manage')
803        # Add next level
804        self.student['studycourse']['100']['COURSE1'].score = 10
805        self.browser.getControl(name="addlevel").value = ['200']
806        self.browser.getControl(name="level_session").value = ['2005']
807        self.browser.getControl("Add study level").click()
808        self.browser.getLink("200").click()
809        self.assertMatches(
810            '...: Study Level 200 (Year 2)...', self.browser.contents)
811        # If COURSE1 has score 10 it becomes a carry-over course
812        # in level 200
813        self.assertEqual(
814            sorted(self.student['studycourse']['200'].keys()), [u'COURSE1'])
815        self.assertTrue(
816            self.student['studycourse']['200']['COURSE1'].carry_over)
817        return
818
819    def test_manage_payments(self):
820        # Managers can add online school fee payment tickets
821        # if certain requirements are met
822        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
823        self.browser.open(self.payments_path)
824        IWorkflowState(self.student).setState('cleared')
825        self.browser.getLink("Add current session payment ticket").click()
826        self.browser.getControl(name="form.p_category").value = ['schoolfee']
827        self.browser.getControl("Create ticket").click()
828        self.assertMatches('...ticket created...',
829                           self.browser.contents)
830        ctrl = self.browser.getControl(name='val_id')
831        value = ctrl.options[0]
832        self.browser.getLink(value).click()
833        self.assertMatches('...Amount Authorized...',
834                           self.browser.contents)
835        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
836        payment_url = self.browser.url
837
838        # The pdf payment slip can't yet be opened
839        #self.browser.open(payment_url + '/payment_slip.pdf')
840        #self.assertMatches('...Ticket not yet paid...',
841        #                   self.browser.contents)
842
843        # The same payment (with same p_item, p_session and p_category)
844        # can be initialized a second time if the former ticket is not yet paid.
845        self.browser.open(self.payments_path)
846        self.browser.getLink("Add current session payment ticket").click()
847        self.browser.getControl(name="form.p_category").value = ['schoolfee']
848        self.browser.getControl("Create ticket").click()
849        self.assertMatches('...Payment ticket created...',
850                           self.browser.contents)
851
852        # The ticket can be found in the payments_catalog
853        cat = queryUtility(ICatalog, name='payments_catalog')
854        results = list(cat.searchResults(p_state=('unpaid', 'unpaid')))
855        self.assertTrue(len(results), 1)
856        self.assertTrue(results[0] is self.student['payments'][value])
857
858        # Managers can approve the payment
859        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
860        self.browser.open(payment_url)
861        self.browser.getLink("Approve payment").click()
862        self.assertMatches('...Payment approved...',
863                          self.browser.contents)
864        # Approval is logged in students.log ...
865        logfile = os.path.join(
866            self.app['datacenter'].storage, 'logs', 'students.log')
867        logcontent = open(logfile).read()
868        self.assertTrue(
869            'zope.mgr - students.browser.OnlinePaymentApprovePage '
870            '- K1000000 - schoolfee payment approved'
871            in logcontent)
872        # ... and in payments.log
873        logfile = os.path.join(
874            self.app['datacenter'].storage, 'logs', 'payments.log')
875        logcontent = open(logfile).read()
876        self.assertTrue(
877            '"zope.mgr",K1000000,%s,schoolfee,40000.0,AP,,,,,,\n' % value
878            in logcontent)
879
880        # The authorized amount has been stored in the access code
881        self.assertEqual(
882            self.app['accesscodes']['SFE-0'].values()[0].cost,40000.0)
883
884        # The catalog has been updated
885        results = list(cat.searchResults(p_state=('unpaid', 'unpaid')))
886        self.assertTrue(len(results), 0)
887        results = list(cat.searchResults(p_state=('paid', 'paid')))
888        self.assertTrue(len(results), 1)
889        self.assertTrue(results[0] is self.student['payments'][value])
890
891        # Payments can't be approved twice
892        self.browser.open(payment_url + '/approve')
893        self.assertMatches('...This ticket has already been paid...',
894                          self.browser.contents)
895
896        # Now the first ticket is paid and no more ticket of same type
897        # (with same p_item, p_session and p_category) can be added
898        self.browser.open(self.payments_path)
899        self.browser.getLink("Add current session payment ticket").click()
900        self.browser.getControl(name="form.p_category").value = ['schoolfee']
901        self.browser.getControl("Create ticket").click()
902        self.assertMatches(
903            '...This type of payment has already been made...',
904            self.browser.contents)
905
906        # Managers can open the pdf payment slip
907        self.browser.open(payment_url)
908        self.browser.getLink("Download payment slip").click()
909        self.assertEqual(self.browser.headers['Status'], '200 Ok')
910        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
911
912        # Managers can remove online school fee payment tickets
913        self.browser.open(self.payments_path)
914        self.browser.getControl("Remove selected").click()
915        self.assertMatches('...No payment selected...', self.browser.contents)
916        ctrl = self.browser.getControl(name='val_id')
917        value = ctrl.options[0]
918        ctrl.getControl(value=value).selected = True
919        self.browser.getControl("Remove selected", index=0).click()
920        self.assertTrue('Successfully removed' in self.browser.contents)
921
922        # Managers can add online clearance payment tickets
923        self.browser.open(self.payments_path + '/addop')
924        self.browser.getControl(name="form.p_category").value = ['clearance']
925        self.browser.getControl("Create ticket").click()
926        self.assertMatches('...ticket created...',
927                           self.browser.contents)
928
929        # Managers can approve the payment
930        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
931        ctrl = self.browser.getControl(name='val_id')
932        value = ctrl.options[1] # The clearance payment is the second in the table
933        self.browser.getLink(value).click()
934        self.browser.open(self.browser.url + '/approve')
935        self.assertMatches('...Payment approved...',
936                          self.browser.contents)
937        expected = '''...
938        <td>
939          <span>Paid</span>
940        </td>...'''
941        self.assertMatches(expected,self.browser.contents)
942        # The new CLR-0 pin has been created
943        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
944        pin = self.app['accesscodes']['CLR-0'].keys()[0]
945        ac = self.app['accesscodes']['CLR-0'][pin]
946        self.assertEqual(ac.owner, self.student_id)
947        self.assertEqual(ac.cost, 3456.0)
948        return
949
950    def test_manage_balance_payments(self):
951
952        # Login
953        #self.browser.open(self.login_path)
954        #self.browser.getControl(name="form.login").value = self.student_id
955        #self.browser.getControl(name="form.password").value = 'spwd'
956        #self.browser.getControl("Login").click()
957
958        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
959        self.browser.open(self.payments_path)
960
961        # Managers can add previous school fee payment tickets in any state.
962        IWorkflowState(self.student).setState('courses registered')
963        self.browser.open(self.payments_path)
964        self.browser.getLink("Add balance payment ticket").click()
965
966        # Previous session payment form is provided
967        self.assertEqual(self.student.current_session, 2004)
968        self.browser.getControl(name="form.p_category").value = ['schoolfee']
969        self.browser.getControl(name="form.balance_session").value = ['2004']
970        self.browser.getControl(name="form.balance_level").value = ['300']
971        self.browser.getControl(name="form.balance_amount").value = '-567.8'
972        self.browser.getControl("Create ticket").click()
973        self.assertMatches('...Amount must be greater than 0...',
974                           self.browser.contents)
975        self.browser.getControl(name="form.balance_amount").value = '0'
976        self.browser.getControl("Create ticket").click()
977        self.assertMatches('...Amount must be greater than 0...',
978                           self.browser.contents)
979        self.browser.getControl(name="form.balance_amount").value = '567.8'
980        self.browser.getControl("Create ticket").click()
981        self.assertMatches('...ticket created...',
982                           self.browser.contents)
983        ctrl = self.browser.getControl(name='val_id')
984        value = ctrl.options[0]
985        self.browser.getLink(value).click()
986        self.assertMatches('...Amount Authorized...',
987                           self.browser.contents)
988        self.assertEqual(self.student['payments'][value].amount_auth, 567.8)
989        # Payment attributes are properly set
990        self.assertEqual(self.student['payments'][value].p_session, 2004)
991        self.assertEqual(self.student['payments'][value].p_level, 300)
992        self.assertEqual(self.student['payments'][value].p_item, u'Balance')
993        self.assertEqual(self.student['payments'][value].p_category, 'schoolfee')
994
995    def test_manage_accommodation(self):
996        logfile = os.path.join(
997            self.app['datacenter'].storage, 'logs', 'students.log')
998        # Managers can add online booking fee payment tickets and open the
999        # callback view (see test_manage_payments)
1000        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1001        self.browser.open(self.payments_path)
1002        self.browser.getLink("Add current session payment ticket").click()
1003        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1004        # If student is not in accommodation session, payment cannot be processed
1005        self.app['hostels'].accommodation_session = 2011
1006        self.browser.getControl("Create ticket").click()
1007        self.assertMatches('...Your current session does not match...',
1008                           self.browser.contents)
1009        self.app['hostels'].accommodation_session = 2004
1010        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1011        self.browser.getControl("Create ticket").click()
1012        ctrl = self.browser.getControl(name='val_id')
1013        value = ctrl.options[0]
1014        self.browser.getLink(value).click()
1015        self.browser.open(self.browser.url + '/approve')
1016        # The new HOS-0 pin has been created
1017        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
1018        pin = self.app['accesscodes']['HOS-0'].keys()[0]
1019        ac = self.app['accesscodes']['HOS-0'][pin]
1020        self.assertEqual(ac.owner, self.student_id)
1021        parts = pin.split('-')[1:]
1022        sfeseries, sfenumber = parts
1023        # Managers can use HOS code and book a bed space with it
1024        self.browser.open(self.acco_path)
1025        self.browser.getLink("Book accommodation").click()
1026        self.assertMatches('...You are in the wrong...',
1027                           self.browser.contents)
1028        IWorkflowInfo(self.student).fireTransition('admit')
1029        # An existing HOS code can only be used if students
1030        # are in accommodation session
1031        self.student['studycourse'].current_session = 2003
1032        self.browser.getLink("Book accommodation").click()
1033        self.assertMatches('...Your current session does not match...',
1034                           self.browser.contents)
1035        self.student['studycourse'].current_session = 2004
1036        # All requirements are met and ticket can be created
1037        self.browser.getLink("Book accommodation").click()
1038        self.assertMatches('...Activation Code:...',
1039                           self.browser.contents)
1040        self.browser.getControl(name="ac_series").value = sfeseries
1041        self.browser.getControl(name="ac_number").value = sfenumber
1042        self.browser.getControl("Create bed ticket").click()
1043        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1044                           self.browser.contents)
1045        # Bed has been allocated
1046        bed1 = self.app['hostels']['hall-1']['hall-1_A_101_A']
1047        self.assertTrue(bed1.owner == self.student_id)
1048        # BedTicketAddPage is now blocked
1049        self.browser.getLink("Book accommodation").click()
1050        self.assertMatches('...You already booked a bed space...',
1051            self.browser.contents)
1052        # The bed ticket displays the data correctly
1053        self.browser.open(self.acco_path + '/2004')
1054        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1055                           self.browser.contents)
1056        self.assertMatches('...2004/2005...', self.browser.contents)
1057        self.assertMatches('...regular_male_fr...', self.browser.contents)
1058        self.assertMatches('...%s...' % pin, self.browser.contents)
1059        # Booking is properly logged
1060        logcontent = open(logfile).read()
1061        self.assertTrue('zope.mgr - students.browser.BedTicketAddPage '
1062            '- K1000000 - booked: hall-1_A_101_A' in logcontent)
1063        # Managers can relocate students if the student's bed_type has changed
1064        self.browser.getLink("Relocate student").click()
1065        self.assertMatches(
1066            "...Student can't be relocated...", self.browser.contents)
1067        self.student.sex = u'f'
1068        self.browser.getLink("Relocate student").click()
1069        self.assertMatches(
1070            "...Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
1071        self.assertTrue(bed1.owner == NOT_OCCUPIED)
1072        bed2 = self.app['hostels']['hall-1']['hall-1_A_101_B']
1073        self.assertTrue(bed2.owner == self.student_id)
1074        self.assertTrue(self.student['accommodation'][
1075            '2004'].bed_type == u'regular_female_fr')
1076        # Relocation is properly logged
1077        logcontent = open(logfile).read()
1078        self.assertTrue('zope.mgr - students.browser.BedTicketRelocationPage '
1079            '- K1000000 - relocated: hall-1_A_101_B' in logcontent)
1080        # The payment object still shows the original payment item
1081        payment_id = self.student['payments'].keys()[0]
1082        payment = self.student['payments'][payment_id]
1083        self.assertTrue(payment.p_item == u'regular_male_fr')
1084        # Managers can relocate students if the bed's bed_type has changed
1085        bed1.bed_type = u'regular_female_fr'
1086        bed2.bed_type = u'regular_male_fr'
1087        notify(grok.ObjectModifiedEvent(bed1))
1088        notify(grok.ObjectModifiedEvent(bed2))
1089        self.browser.getLink("Relocate student").click()
1090        self.assertMatches(
1091            "...Student relocated...", self.browser.contents)
1092        self.assertMatches(
1093            "... Hall 1, Block A, Room 101, Bed A...", self.browser.contents)
1094        self.assertMatches(bed1.owner, self.student_id)
1095        self.assertMatches(bed2.owner, NOT_OCCUPIED)
1096        # Managers can't relocate students if bed is reserved
1097        self.student.sex = u'm'
1098        bed1.bed_type = u'regular_female_reserved'
1099        notify(grok.ObjectModifiedEvent(bed1))
1100        self.browser.getLink("Relocate student").click()
1101        self.assertMatches(
1102            "...Students in reserved beds can't be relocated...",
1103            self.browser.contents)
1104        # Managers can relocate students if booking has been cancelled but
1105        # other bed space has been manually allocated after cancellation
1106        old_owner = bed1.releaseBed()
1107        self.assertMatches(old_owner, self.student_id)
1108        bed2.owner = self.student_id
1109        self.browser.open(self.acco_path + '/2004')
1110        self.assertMatches(
1111            "...booking cancelled...", self.browser.contents)
1112        self.browser.getLink("Relocate student").click()
1113        # We didn't informed the catalog therefore the new owner is not found
1114        self.assertMatches(
1115            "...There is no free bed in your category regular_male_fr...",
1116            self.browser.contents)
1117        # Now we fire the event properly
1118        notify(grok.ObjectModifiedEvent(bed2))
1119        self.browser.getLink("Relocate student").click()
1120        self.assertMatches(
1121            "...Student relocated...", self.browser.contents)
1122        self.assertMatches(
1123            "... Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
1124          # Managers can delete bed tickets
1125        self.browser.open(self.acco_path)
1126        ctrl = self.browser.getControl(name='val_id')
1127        value = ctrl.options[0]
1128        ctrl.getControl(value=value).selected = True
1129        self.browser.getControl("Remove selected", index=0).click()
1130        self.assertMatches('...Successfully removed...', self.browser.contents)
1131        # The bed has been properly released by the event handler
1132        self.assertMatches(bed1.owner, NOT_OCCUPIED)
1133        self.assertMatches(bed2.owner, NOT_OCCUPIED)
1134        return
1135
1136    def test_manage_workflow(self):
1137        # Managers can pass through the whole workflow
1138        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1139        student = self.app['students'][self.student_id]
1140        self.browser.open(self.trigtrans_path)
1141        self.assertTrue(student.clearance_locked)
1142        self.browser.getControl(name="transition").value = ['admit']
1143        self.browser.getControl("Save").click()
1144        self.assertTrue(student.clearance_locked)
1145        self.browser.getControl(name="transition").value = ['start_clearance']
1146        self.browser.getControl("Save").click()
1147        self.assertFalse(student.clearance_locked)
1148        self.browser.getControl(name="transition").value = ['request_clearance']
1149        self.browser.getControl("Save").click()
1150        self.assertTrue(student.clearance_locked)
1151        self.browser.getControl(name="transition").value = ['clear']
1152        self.browser.getControl("Save").click()
1153        # Managers approve payment, they do not pay
1154        self.assertFalse('pay_first_school_fee' in self.browser.contents)
1155        self.browser.getControl(
1156            name="transition").value = ['approve_first_school_fee']
1157        self.browser.getControl("Save").click()
1158        self.browser.getControl(name="transition").value = ['reset6']
1159        self.browser.getControl("Save").click()
1160        # In state returning the pay_school_fee transition triggers some
1161        # changes of attributes
1162        self.browser.getControl(name="transition").value = ['approve_school_fee']
1163        self.browser.getControl("Save").click()
1164        self.assertEqual(student['studycourse'].current_session, 2005) # +1
1165        self.assertEqual(student['studycourse'].current_level, 200) # +100
1166        self.assertEqual(student['studycourse'].current_verdict, '0') # 0 = Zero = not set
1167        self.assertEqual(student['studycourse'].previous_verdict, 'A')
1168        self.browser.getControl(name="transition").value = ['register_courses']
1169        self.browser.getControl("Save").click()
1170        self.browser.getControl(name="transition").value = ['validate_courses']
1171        self.browser.getControl("Save").click()
1172        self.browser.getControl(name="transition").value = ['return']
1173        self.browser.getControl("Save").click()
1174        return
1175
1176    def test_manage_pg_workflow(self):
1177        # Managers can pass through the whole workflow
1178        IWorkflowState(self.student).setState('school fee paid')
1179        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1180        student = self.app['students'][self.student_id]
1181        self.browser.open(self.trigtrans_path)
1182        self.assertTrue('<option value="reset6">' in self.browser.contents)
1183        self.assertTrue('<option value="register_courses">' in self.browser.contents)
1184        self.assertTrue('<option value="reset5">' in self.browser.contents)
1185        self.certificate.study_mode = 'pg_ft'
1186        self.browser.open(self.trigtrans_path)
1187        self.assertFalse('<option value="reset6">' in self.browser.contents)
1188        self.assertFalse('<option value="register_courses">' in self.browser.contents)
1189        self.assertTrue('<option value="reset5">' in self.browser.contents)
1190        return
1191
1192    def test_manage_import(self):
1193        # Managers can import student data files
1194        datacenter_path = 'http://localhost/app/datacenter'
1195        # Prepare a csv file for students
1196        open('students.csv', 'wb').write(
1197"""firstname,lastname,reg_number,date_of_birth,matric_number,email,phone,sex,password
1198Aaren,Pieri,1,1990-01-02,100000,aa@aa.ng,1234,m,mypwd1
1199Claus,Finau,2,1990-01-03,100001,aa@aa.ng,1234,m,mypwd1
1200Brit,Berson,3,1990-01-04,100001,aa@aa.ng,1234,m,mypwd1
1201""")
1202        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1203        self.browser.open(datacenter_path)
1204        self.browser.getLink('Upload data').click()
1205        filecontents = StringIO(open('students.csv', 'rb').read())
1206        filewidget = self.browser.getControl(name='uploadfile:file')
1207        filewidget.add_file(filecontents, 'text/plain', 'students.csv')
1208        self.browser.getControl(name='SUBMIT').click()
1209        self.browser.getLink('Process data').click()
1210        button = lookup_submit_value(
1211            'select', 'students_zope.mgr.csv', self.browser)
1212        button.click()
1213        importerselect = self.browser.getControl(name='importer')
1214        modeselect = self.browser.getControl(name='mode')
1215        importerselect.getControl('Student Processor').selected = True
1216        modeselect.getControl(value='create').selected = True
1217        self.browser.getControl('Proceed to step 3').click()
1218        self.assertTrue('Header fields OK' in self.browser.contents)
1219        self.browser.getControl('Perform import').click()
1220        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
1221        self.assertTrue('Successfully processed 2 rows' in self.browser.contents)
1222        self.assertTrue('Batch processing finished' in self.browser.contents)
1223        open('studycourses.csv', 'wb').write(
1224"""reg_number,matric_number,certificate,current_session,current_level
12251,,CERT1,2008,100
1226,100001,CERT1,2008,100
1227,100002,CERT1,2008,100
1228""")
1229        self.browser.open(datacenter_path)
1230        self.browser.getLink('Upload data').click()
1231        filecontents = StringIO(open('studycourses.csv', 'rb').read())
1232        filewidget = self.browser.getControl(name='uploadfile:file')
1233        filewidget.add_file(filecontents, 'text/plain', 'studycourses.csv')
1234        self.browser.getControl(name='SUBMIT').click()
1235        self.browser.getLink('Process data').click()
1236        button = lookup_submit_value(
1237            'select', 'studycourses_zope.mgr.csv', self.browser)
1238        button.click()
1239        importerselect = self.browser.getControl(name='importer')
1240        modeselect = self.browser.getControl(name='mode')
1241        importerselect.getControl(
1242            'StudentStudyCourse Processor (update only)').selected = True
1243        modeselect.getControl(value='create').selected = True
1244        self.browser.getControl('Proceed to step 3').click()
1245        self.assertTrue('Update mode only' in self.browser.contents)
1246        self.browser.getControl('Proceed to step 3').click()
1247        self.assertTrue('Header fields OK' in self.browser.contents)
1248        self.browser.getControl('Perform import').click()
1249        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
1250        self.assertTrue('Successfully processed 2 rows'
1251                        in self.browser.contents)
1252        # The students are properly indexed and we can
1253        # thus find a student in  the department
1254        self.browser.open(self.manage_container_path)
1255        self.browser.getControl(name="searchtype").value = ['depcode']
1256        self.browser.getControl(name="searchterm").value = 'dep1'
1257        self.browser.getControl("Search").click()
1258        self.assertTrue('Aaren Pieri' in self.browser.contents)
1259        # We can search for a new student by name ...
1260        self.browser.getControl(name="searchtype").value = ['fullname']
1261        self.browser.getControl(name="searchterm").value = 'Claus'
1262        self.browser.getControl("Search").click()
1263        self.assertTrue('Claus Finau' in self.browser.contents)
1264        # ... and check if the imported password has been properly set
1265        ctrl = self.browser.getControl(name='entries')
1266        value = ctrl.options[0]
1267        claus = self.app['students'][value]
1268        self.assertTrue(IUserAccount(claus).checkPassword('mypwd1'))
1269        return
1270
1271    def test_handle_clearance_by_co(self):
1272        # Create clearance officer
1273        self.app['users'].addUser('mrclear', 'mrclearsecret')
1274        self.app['users']['mrclear'].email = 'mrclear@foo.ng'
1275        self.app['users']['mrclear'].title = 'Carlo Pitter'
1276        # Clearance officers need not necessarily to get
1277        # the StudentsOfficer site role
1278        #prmglobal = IPrincipalRoleManager(self.app)
1279        #prmglobal.assignRoleToPrincipal('waeup.StudentsOfficer', 'mrclear')
1280        # Assign local ClearanceOfficer role
1281        department = self.app['faculties']['fac1']['dep1']
1282        prmlocal = IPrincipalRoleManager(department)
1283        prmlocal.assignRoleToPrincipal('waeup.local.ClearanceOfficer', 'mrclear')
1284        IWorkflowState(self.student).setState('clearance started')
1285        # Login as clearance officer
1286        self.browser.open(self.login_path)
1287        self.browser.getControl(name="form.login").value = 'mrclear'
1288        self.browser.getControl(name="form.password").value = 'mrclearsecret'
1289        self.browser.getControl("Login").click()
1290        self.assertMatches('...You logged in...', self.browser.contents)
1291        # CO can see his roles
1292        self.browser.getLink("My Roles").click()
1293        self.assertMatches(
1294            '...<div>Academics Officer (view only)</div>...',
1295            self.browser.contents)
1296        #self.assertMatches(
1297        #    '...<div>Students Officer (view only)</div>...',
1298        #    self.browser.contents)
1299        # But not his local role ...
1300        self.assertFalse('Clearance Officer' in self.browser.contents)
1301        # ... because we forgot to notify the department that the local role
1302        # has changed
1303        notify(LocalRoleSetEvent(
1304            department, 'waeup.local.ClearanceOfficer', 'mrclear', granted=True))
1305        self.browser.open('http://localhost/app/users/mrclear/my_roles')
1306        self.assertTrue('Clearance Officer' in self.browser.contents)
1307        self.assertMatches(
1308            '...<a href="http://localhost/app/faculties/fac1/dep1">...',
1309            self.browser.contents)
1310        # CO can view the student ...
1311        self.browser.open(self.clearance_path)
1312        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1313        self.assertEqual(self.browser.url, self.clearance_path)
1314        # ... but not other students
1315        other_student = Student()
1316        other_student.firstname = u'Dep2'
1317        other_student.lastname = u'Student'
1318        self.app['students'].addStudent(other_student)
1319        other_student_path = (
1320            'http://localhost/app/students/%s' % other_student.student_id)
1321        self.assertRaises(
1322            Unauthorized, self.browser.open, other_student_path)
1323        # Clearance is disabled for this session
1324        self.browser.open(self.clearance_path)
1325        self.assertFalse('Clear student' in self.browser.contents)
1326        self.browser.open(self.student_path + '/clear')
1327        self.assertTrue('Clearance is disabled for this session'
1328            in self.browser.contents)
1329        self.app['configuration']['2004'].clearance_enabled = True
1330        # Only in state clearance requested the CO does see the 'Clear' button
1331        self.browser.open(self.clearance_path)
1332        self.assertFalse('Clear student' in self.browser.contents)
1333        IWorkflowInfo(self.student).fireTransition('request_clearance')
1334        self.browser.open(self.clearance_path)
1335        self.assertTrue('Clear student' in self.browser.contents)
1336        self.browser.getLink("Clear student").click()
1337        self.assertTrue('Student has been cleared' in self.browser.contents)
1338        self.assertTrue('cleared' in self.browser.contents)
1339        self.browser.open(self.history_path)
1340        self.assertTrue('Cleared by Carlo Pitter' in self.browser.contents)
1341        # Hide real name.
1342        self.app['users']['mrclear'].public_name = 'My Public Name'
1343        self.browser.open(self.clearance_path)
1344        self.browser.getLink("Reject clearance").click()
1345        self.assertEqual(
1346            self.browser.url, self.student_path + '/reject_clearance')
1347        # Type comment why
1348        self.browser.getControl(name="form.officer_comment").value = """Dear Student,
1349You did not fill properly.
1350"""
1351        self.browser.getControl("Save comment").click()
1352        self.assertTrue('Clearance has been annulled' in self.browser.contents)
1353        url = ('http://localhost/app/students/K1000000/'
1354              'contactstudent?body=Dear+Student%2C%0AYou+did+not+fill+properly.'
1355              '%0A&subject=Clearance+has+been+annulled.')
1356        # CO does now see the prefilled contact form and can send a message
1357        self.assertEqual(self.browser.url, url)
1358        self.assertTrue('clearance started' in self.browser.contents)
1359        self.assertTrue('name="form.subject" size="20" type="text" '
1360            'value="Clearance has been annulled."'
1361            in self.browser.contents)
1362        self.assertTrue('name="form.body" rows="10" >Dear Student,'
1363            in self.browser.contents)
1364        self.browser.getControl("Send message now").click()
1365        self.assertTrue('Your message has been sent' in self.browser.contents)
1366        # The comment has been stored ...
1367        self.assertEqual(self.student.officer_comment,
1368            u'Dear Student,\nYou did not fill properly.\n')
1369        # ... and logged
1370        logfile = os.path.join(
1371            self.app['datacenter'].storage, 'logs', 'students.log')
1372        logcontent = open(logfile).read()
1373        self.assertTrue(
1374            'INFO - mrclear - students.browser.StudentRejectClearancePage - '
1375            'K1000000 - comment: Dear Student,<br>You did not fill '
1376            'properly.<br>\n' in logcontent)
1377        self.browser.open(self.history_path)
1378        self.assertTrue("Reset to 'clearance started' by My Public Name" in
1379            self.browser.contents)
1380        IWorkflowInfo(self.student).fireTransition('request_clearance')
1381        self.browser.open(self.clearance_path)
1382        self.browser.getLink("Reject clearance").click()
1383        self.browser.getControl("Save comment").click()
1384        self.assertTrue('Clearance request has been rejected'
1385            in self.browser.contents)
1386        self.assertTrue('clearance started' in self.browser.contents)
1387        # The CO can't clear students if not in state
1388        # clearance requested
1389        self.browser.open(self.student_path + '/clear')
1390        self.assertTrue('Student is in wrong state'
1391            in self.browser.contents)
1392        # The CO can go to his department throug the my_roles page ...
1393        self.browser.open('http://localhost/app/users/mrclear/my_roles')
1394        self.browser.getLink("http://localhost/app/faculties/fac1/dep1").click()
1395        # ... and view the list of students
1396        self.browser.getLink("Show students").click()
1397        self.browser.getControl(name="session").value = ['2004']
1398        self.browser.getControl(name="level").value = ['200']
1399        self.browser.getControl("Show").click()
1400        self.assertFalse(self.student_id in self.browser.contents)
1401        self.browser.getControl(name="session").value = ['2004']
1402        self.browser.getControl(name="level").value = ['100']
1403        self.browser.getControl("Show").click()
1404        self.assertTrue(self.student_id in self.browser.contents)
1405        # The comment is indicated by 'yes'
1406        self.assertTrue('<td><span>yes</span></td>' in self.browser.contents)
1407        # Check if the enquiries form is not pre-filled with officer_comment
1408        # (regression test)
1409        self.browser.getLink("Logout").click()
1410        self.browser.open('http://localhost/app/enquiries')
1411        self.assertFalse(
1412            'You did not fill properly'
1413            in self.browser.contents)
1414        # When a student is cleared the comment is automatically deleted
1415        IWorkflowInfo(self.student).fireTransition('request_clearance')
1416        IWorkflowInfo(self.student).fireTransition('clear')
1417        self.assertEqual(self.student.officer_comment, None)
1418
1419    def test_handle_courses_by_ca(self):
1420        # Create course adviser
1421        self.app['users'].addUser('mrsadvise', 'mrsadvisesecret')
1422        self.app['users']['mrsadvise'].email = 'mradvise@foo.ng'
1423        self.app['users']['mrsadvise'].title = u'Helen Procter'
1424        # Assign local CourseAdviser100 role for a certificate
1425        cert = self.app['faculties']['fac1']['dep1'].certificates['CERT1']
1426        prmlocal = IPrincipalRoleManager(cert)
1427        prmlocal.assignRoleToPrincipal('waeup.local.CourseAdviser100', 'mrsadvise')
1428        IWorkflowState(self.student).setState('school fee paid')
1429        # Login as course adviser
1430        self.browser.open(self.login_path)
1431        self.browser.getControl(name="form.login").value = 'mrsadvise'
1432        self.browser.getControl(name="form.password").value = 'mrsadvisesecret'
1433        self.browser.getControl("Login").click()
1434        self.assertMatches('...You logged in...', self.browser.contents)
1435        # CO can see his roles
1436        self.browser.getLink("My Roles").click()
1437        self.assertMatches(
1438            '...<div>Academics Officer (view only)</div>...',
1439            self.browser.contents)
1440        # But not his local role ...
1441        self.assertFalse('Course Adviser' in self.browser.contents)
1442        # ... because we forgot to notify the certificate that the local role
1443        # has changed
1444        notify(LocalRoleSetEvent(
1445            cert, 'waeup.local.CourseAdviser100', 'mrsadvise', granted=True))
1446        self.browser.open('http://localhost/app/users/mrsadvise/my_roles')
1447        self.assertTrue('Course Adviser 100L' in self.browser.contents)
1448        self.assertMatches(
1449            '...<a href="http://localhost/app/faculties/fac1/dep1/certificates/CERT1">...',
1450            self.browser.contents)
1451        # CA can view the student ...
1452        self.browser.open(self.student_path)
1453        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1454        self.assertEqual(self.browser.url, self.student_path)
1455        # ... but not other students
1456        other_student = Student()
1457        other_student.firstname = u'Dep2'
1458        other_student.lastname = u'Student'
1459        self.app['students'].addStudent(other_student)
1460        other_student_path = (
1461            'http://localhost/app/students/%s' % other_student.student_id)
1462        self.assertRaises(
1463            Unauthorized, self.browser.open, other_student_path)
1464        # We add study level 110 to the student's studycourse
1465        studylevel = StudentStudyLevel()
1466        studylevel.level = 110
1467        self.student['studycourse'].addStudentStudyLevel(
1468            cert,studylevel)
1469        L110_student_path = self.studycourse_path + '/110'
1470        # The CA can neither see the Validate nor the Edit button
1471        self.browser.open(L110_student_path)
1472        self.assertFalse('Validate courses' in self.browser.contents)
1473        self.assertFalse('Edit' in self.browser.contents)
1474        IWorkflowInfo(self.student).fireTransition('register_courses')
1475        self.browser.open(L110_student_path)
1476        self.assertFalse('Validate courses' in self.browser.contents)
1477        self.assertFalse('Edit' in self.browser.contents)
1478        # Only in state courses registered and only if the current level
1479        # corresponds with the name of the study level object
1480        # the 100L CA does see the 'Validate' button but not
1481        # the edit button
1482        self.student['studycourse'].current_level = 110
1483        self.browser.open(L110_student_path)
1484        self.assertFalse('Edit' in self.browser.contents)
1485        self.assertTrue('Validate courses' in self.browser.contents)
1486        # But a 100L CA does not see the button at other levels
1487        studylevel2 = StudentStudyLevel()
1488        studylevel2.level = 200
1489        self.student['studycourse'].addStudentStudyLevel(
1490            cert,studylevel2)
1491        L200_student_path = self.studycourse_path + '/200'
1492        self.browser.open(L200_student_path)
1493        self.assertFalse('Edit' in self.browser.contents)
1494        self.assertFalse('Validate courses' in self.browser.contents)
1495        self.browser.open(L110_student_path)
1496        self.browser.getLink("Validate courses").click()
1497        self.assertTrue('Course list has been validated' in self.browser.contents)
1498        self.assertTrue('courses validated' in self.browser.contents)
1499        self.assertEqual(self.student['studycourse']['110'].validated_by,
1500            'Helen Procter')
1501        self.assertMatches(
1502            '<YYYY-MM-DD hh:mm:ss>',
1503            self.student['studycourse']['110'].validation_date.strftime(
1504                "%Y-%m-%d %H:%M:%S"))
1505        self.browser.getLink("Reject courses").click()
1506        self.assertTrue('Course list request has been annulled.'
1507            in self.browser.contents)
1508        urlmessage = 'Course+list+request+has+been+annulled.'
1509        self.assertEqual(self.browser.url, self.student_path +
1510            '/contactstudent?subject=%s' % urlmessage)
1511        self.assertTrue('school fee paid' in self.browser.contents)
1512        self.assertTrue(self.student['studycourse']['110'].validated_by is None)
1513        self.assertTrue(self.student['studycourse']['110'].validation_date is None)
1514        IWorkflowInfo(self.student).fireTransition('register_courses')
1515        self.browser.open(L110_student_path)
1516        self.browser.getLink("Reject courses").click()
1517        self.assertTrue('Course list request has been rejected'
1518            in self.browser.contents)
1519        self.assertTrue('school fee paid' in self.browser.contents)
1520        # CA does now see the contact form and can send a message
1521        self.browser.getControl(name="form.subject").value = 'Important subject'
1522        self.browser.getControl(name="form.body").value = 'Course list rejected'
1523        self.browser.getControl("Send message now").click()
1524        self.assertTrue('Your message has been sent' in self.browser.contents)
1525        # The CA does now see the Edit button and can edit
1526        # current study level
1527        self.browser.open(L110_student_path)
1528        self.browser.getLink("Edit").click()
1529        self.assertTrue('Edit course list of 100 (Year 1) on 1st probation'
1530            in self.browser.contents)
1531        # The CA can't validate courses if not in state
1532        # courses registered
1533        self.browser.open(L110_student_path + '/validate_courses')
1534        self.assertTrue('Student is in the wrong state'
1535            in self.browser.contents)
1536        # The CA can go to his certificate through the my_roles page
1537        self.browser.open('http://localhost/app/users/mrsadvise/my_roles')
1538        self.browser.getLink(
1539            "http://localhost/app/faculties/fac1/dep1/certificates/CERT1").click()
1540        # and view the list of students
1541        self.browser.getLink("Show students").click()
1542        self.browser.getControl(name="session").value = ['2004']
1543        self.browser.getControl(name="level").value = ['100']
1544        self.browser.getControl("Show").click()
1545        self.assertTrue(self.student_id in self.browser.contents)
1546
1547    def test_handle_courses_by_lecturer(self):
1548        # Create course lecturer
1549        self.app['users'].addUser('mrslecturer', 'mrslecturersecret')
1550        self.app['users']['mrslecturer'].email = 'mrslecturer@foo.ng'
1551        self.app['users']['mrslecturer'].title = u'Mercedes Benz'
1552        # Assign local Courselecturer100 role for a certificate
1553        course = self.app['faculties']['fac1']['dep1'].courses['COURSE1']
1554        prmlocal = IPrincipalRoleManager(course)
1555        prmlocal.assignRoleToPrincipal('waeup.local.Lecturer', 'mrslecturer')
1556        # Login as lecturer
1557        self.browser.open(self.login_path)
1558        self.browser.getControl(name="form.login").value = 'mrslecturer'
1559        self.browser.getControl(name="form.password").value = 'mrslecturersecret'
1560        self.browser.getControl("Login").click()
1561        self.assertMatches('...You logged in...', self.browser.contents)
1562        # CO can see her roles
1563        self.browser.getLink("My Roles").click()
1564        self.assertMatches(
1565            '...<div>Academics Officer (view only)</div>...',
1566            self.browser.contents)
1567        # But not her local role ...
1568        self.assertFalse('Lecturer' in self.browser.contents)
1569        # ... because we forgot to notify the course that the local role
1570        # has changed
1571        notify(LocalRoleSetEvent(
1572            course, 'waeup.local.Lecturer', 'mrslecturer', granted=True))
1573        self.browser.open('http://localhost/app/users/mrslecturer/my_roles')
1574        self.assertTrue('Lecturer' in self.browser.contents)
1575        self.assertMatches(
1576            '...<a href="http://localhost/app/faculties/fac1/dep1/courses/COURSE1">...',
1577            self.browser.contents)
1578        # The lecturer can go to her course
1579        self.browser.getLink(
1580            "http://localhost/app/faculties/fac1/dep1/courses/COURSE1").click()
1581        # and view the list of students
1582        self.browser.getLink("Show students").click()
1583        self.browser.getControl(name="session").value = ['2004']
1584        self.browser.getControl(name="level").value = ['100']
1585        self.browser.getControl("Show").click()
1586        self.assertTrue('No student found.' in self.browser.contents)
1587        # No student in course so far
1588        self.assertFalse(self.student_id in self.browser.contents)
1589        studylevel = createObject(u'waeup.StudentStudyLevel')
1590        studylevel.level = 100
1591        studylevel.level_session = 2004
1592        self.student['studycourse'].addStudentStudyLevel(
1593            self.certificate, studylevel)
1594        # Now the student has registered the course and can
1595        # be seen by the lecturer.
1596        self.browser.open("http://localhost/app/faculties/fac1/dep1/courses/COURSE1/students")
1597        self.browser.getControl(name="session").value = ['2004']
1598        self.browser.getControl(name="level").value = ['100']
1599        self.browser.getControl("Show").click()
1600        self.assertTrue(self.student_id in self.browser.contents)
1601        # XXX: So far the lecturer can neither access ths student ...
1602        self.assertRaises(
1603            Unauthorized, self.browser.open, self.student_path)
1604        # ... nor the respective course ticket since a
1605        # CourseTicketPrincipalRoleManager does not yet exist.
1606        self.assertTrue('COURSE1' in self.student['studycourse']['100'].keys())
1607        course_ticket_path = self.student_path + '/studycourse/100/COURSE1'
1608        self.assertRaises(
1609            Unauthorized, self.browser.open, course_ticket_path)
1610
1611    def test_change_current_mode(self):
1612        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1613        self.browser.open(self.clearance_path)
1614        self.assertFalse('Employer' in self.browser.contents)
1615        self.browser.open(self.manage_clearance_path)
1616        self.assertFalse('Employer' in self.browser.contents)
1617        self.student.clearance_locked = False
1618        self.browser.open(self.edit_clearance_path)
1619        self.assertFalse('Employer' in self.browser.contents)
1620        # Now we change the study mode of the certificate and a different
1621        # interface is used by clearance views.
1622        self.certificate.study_mode = 'pg_ft'
1623        # Invariants are not being checked here?!
1624        self.certificate.end_level = 100
1625        self.browser.open(self.clearance_path)
1626        self.assertTrue('Employer' in self.browser.contents)
1627        self.browser.open(self.manage_clearance_path)
1628        self.assertTrue('Employer' in self.browser.contents)
1629        self.browser.open(self.edit_clearance_path)
1630        self.assertTrue('Employer' in self.browser.contents)
1631
1632    def test_activate_deactivate_buttons(self):
1633        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1634        self.browser.open(self.student_path)
1635        self.browser.getLink("Deactivate").click()
1636        self.assertTrue(
1637            'Student account has been deactivated.' in self.browser.contents)
1638        self.assertTrue(
1639            'Base Data (account deactivated)' in self.browser.contents)
1640        self.assertTrue(self.student.suspended)
1641        self.browser.getLink("Activate").click()
1642        self.assertTrue(
1643            'Student account has been activated.' in self.browser.contents)
1644        self.assertFalse(
1645            'Base Data (account deactivated)' in self.browser.contents)
1646        self.assertFalse(self.student.suspended)
1647        # History messages have been added ...
1648        self.browser.getLink("History").click()
1649        self.assertTrue(
1650            'Student account deactivated by Manager<br />' in self.browser.contents)
1651        self.assertTrue(
1652            'Student account activated by Manager<br />' in self.browser.contents)
1653        # ... and actions have been logged.
1654        logfile = os.path.join(
1655            self.app['datacenter'].storage, 'logs', 'students.log')
1656        logcontent = open(logfile).read()
1657        self.assertTrue('zope.mgr - students.browser.StudentDeactivatePage - '
1658                        'K1000000 - account deactivated' in logcontent)
1659        self.assertTrue('zope.mgr - students.browser.StudentActivatePage - '
1660                        'K1000000 - account activated' in logcontent)
1661
1662    def test_manage_student_transfer(self):
1663        # Add second certificate
1664        self.certificate2 = createObject('waeup.Certificate')
1665        self.certificate2.code = u'CERT2'
1666        self.certificate2.study_mode = 'ug_ft'
1667        self.certificate2.start_level = 999
1668        self.certificate2.end_level = 999
1669        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
1670            self.certificate2)
1671
1672        # Add study level to old study course
1673        studylevel = createObject(u'waeup.StudentStudyLevel')
1674        studylevel.level = 200
1675        self.student['studycourse'].addStudentStudyLevel(
1676            self.certificate, studylevel)
1677        studylevel = createObject(u'waeup.StudentStudyLevel')
1678        studylevel.level = 999
1679        self.student['studycourse'].addStudentStudyLevel(
1680            self.certificate, studylevel)
1681
1682        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1683        self.browser.open(self.student_path)
1684        self.browser.getLink("Transfer").click()
1685        self.browser.getControl(name="form.certificate").value = ['CERT2']
1686        self.browser.getControl(name="form.current_session").value = ['2011']
1687        self.browser.getControl(name="form.current_level").value = ['200']
1688        self.browser.getControl("Transfer").click()
1689        self.assertTrue(
1690            'Current level does not match certificate levels'
1691            in self.browser.contents)
1692        self.browser.getControl(name="form.current_level").value = ['999']
1693        self.browser.getControl("Transfer").click()
1694        self.assertTrue('Successfully transferred' in self.browser.contents)
1695        # The catalog has been updated
1696        cat = queryUtility(ICatalog, name='students_catalog')
1697        results = list(
1698            cat.searchResults(
1699            certcode=('CERT2', 'CERT2')))
1700        self.assertTrue(results[0] is self.student)
1701        results = list(
1702            cat.searchResults(
1703            current_session=(2011, 2011)))
1704        self.assertTrue(results[0] is self.student)
1705        # Add study level to new study course
1706        studylevel = createObject(u'waeup.StudentStudyLevel')
1707        studylevel.level = 999
1708        self.student['studycourse'].addStudentStudyLevel(
1709            self.certificate, studylevel)
1710
1711        # Edit and add pages are locked for old study courses
1712        self.browser.open(self.student_path + '/studycourse/manage')
1713        self.assertFalse('The requested form is locked' in self.browser.contents)
1714        self.browser.open(self.student_path + '/studycourse_1/manage')
1715        self.assertTrue('The requested form is locked' in self.browser.contents)
1716
1717        self.browser.open(self.student_path + '/studycourse/start_session')
1718        self.assertFalse('The requested form is locked' in self.browser.contents)
1719        self.browser.open(self.student_path + '/studycourse_1/start_session')
1720        self.assertTrue('The requested form is locked' in self.browser.contents)
1721
1722        IWorkflowState(self.student).setState('school fee paid')
1723        self.browser.open(self.student_path + '/studycourse/add')
1724        self.assertFalse('The requested form is locked' in self.browser.contents)
1725        self.browser.open(self.student_path + '/studycourse_1/add')
1726        self.assertTrue('The requested form is locked' in self.browser.contents)
1727
1728        self.browser.open(self.student_path + '/studycourse/999/manage')
1729        self.assertFalse('The requested form is locked' in self.browser.contents)
1730        self.browser.open(self.student_path + '/studycourse_1/999/manage')
1731        self.assertTrue('The requested form is locked' in self.browser.contents)
1732
1733        self.browser.open(self.student_path + '/studycourse/999/validate_courses')
1734        self.assertFalse('The requested form is locked' in self.browser.contents)
1735        self.browser.open(self.student_path + '/studycourse_1/999/validate_courses')
1736        self.assertTrue('The requested form is locked' in self.browser.contents)
1737
1738        self.browser.open(self.student_path + '/studycourse/999/reject_courses')
1739        self.assertFalse('The requested form is locked' in self.browser.contents)
1740        self.browser.open(self.student_path + '/studycourse_1/999/reject_courses')
1741        self.assertTrue('The requested form is locked' in self.browser.contents)
1742
1743        self.browser.open(self.student_path + '/studycourse/999/add')
1744        self.assertFalse('The requested form is locked' in self.browser.contents)
1745        self.browser.open(self.student_path + '/studycourse_1/999/add')
1746        self.assertTrue('The requested form is locked' in self.browser.contents)
1747
1748        self.browser.open(self.student_path + '/studycourse/999/edit')
1749        self.assertFalse('The requested form is locked' in self.browser.contents)
1750        self.browser.open(self.student_path + '/studycourse_1/999/edit')
1751        self.assertTrue('The requested form is locked' in self.browser.contents)
1752
1753        # Revert transfer
1754        self.browser.open(self.student_path + '/studycourse_1')
1755        self.browser.getLink("Reactivate").click()
1756        self.browser.getControl("Revert now").click()
1757        self.assertTrue('Previous transfer reverted' in self.browser.contents)
1758        results = list(
1759            cat.searchResults(
1760            certcode=('CERT1', 'CERT1')))
1761        self.assertTrue(results[0] is self.student)
1762        self.assertEqual([i for i in self.student.keys()],
1763            [u'accommodation', u'payments', u'studycourse'])
1764
1765    def test_login_as_student(self):
1766        # StudentImpersonators can login as student
1767        # Create clearance officer
1768        self.app['users'].addUser('mrofficer', 'mrofficersecret')
1769        self.app['users']['mrofficer'].email = 'mrofficer@foo.ng'
1770        self.app['users']['mrofficer'].title = 'Harry Actor'
1771        prmglobal = IPrincipalRoleManager(self.app)
1772        prmglobal.assignRoleToPrincipal('waeup.StudentImpersonator', 'mrofficer')
1773        prmglobal.assignRoleToPrincipal('waeup.StudentsManager', 'mrofficer')
1774        # Login as student impersonator
1775        self.browser.open(self.login_path)
1776        self.browser.getControl(name="form.login").value = 'mrofficer'
1777        self.browser.getControl(name="form.password").value = 'mrofficersecret'
1778        self.browser.getControl("Login").click()
1779        self.assertMatches('...You logged in...', self.browser.contents)
1780        self.browser.open(self.student_path)
1781        self.browser.getLink("Login as").click()
1782        self.browser.getControl("Set password now").click()
1783        temp_password = self.browser.getControl(name='form.password').value
1784        self.browser.getControl("Login now").click()
1785        self.assertMatches(
1786            '...You successfully logged in as...', self.browser.contents)
1787        # We are logged in as student and can see the 'My Data' tab
1788        self.assertMatches(
1789            '...<a href="#" class="dropdown-toggle">My Data</a>...',
1790            self.browser.contents)
1791        self.browser.getLink("Logout").click()
1792        # The student can't login with the original password ...
1793        self.browser.open(self.login_path)
1794        self.browser.getControl(name="form.login").value = self.student_id
1795        self.browser.getControl(name="form.password").value = 'spwd'
1796        self.browser.getControl("Login").click()
1797        self.assertMatches(
1798            '...Your account has been temporarily deactivated...',
1799            self.browser.contents)
1800        # ... but with the temporary password
1801        self.browser.open(self.login_path)
1802        self.browser.getControl(name="form.login").value = self.student_id
1803        self.browser.getControl(name="form.password").value = temp_password
1804        self.browser.getControl("Login").click()
1805        self.assertMatches('...You logged in...', self.browser.contents)
1806        # Creation of temp_password is properly logged
1807        logfile = os.path.join(
1808            self.app['datacenter'].storage, 'logs', 'students.log')
1809        logcontent = open(logfile).read()
1810        self.assertTrue(
1811            'mrofficer - students.browser.LoginAsStudentStep1 - K1000000 - '
1812            'temp_password generated: %s' % temp_password in logcontent)
1813
1814    def test_transcrtipts(self):
1815        self.student.date_of_birth = date(1981, 2, 4)
1816        studylevel = createObject(u'waeup.StudentStudyLevel')
1817        studylevel.level = 100
1818        studylevel.level_session = 2005
1819        self.student['studycourse'].entry_mode = 'ug_ft'
1820        self.student['studycourse'].addStudentStudyLevel(
1821            self.certificate, studylevel)
1822        # Add second course
1823        courseticket = createObject('waeup.CourseTicket')
1824        courseticket.code = 'ANYCODE'
1825        courseticket.title = u'Any TITLE'
1826        courseticket.credits = 13
1827        courseticket.score = 66
1828        courseticket.semester = 1
1829        courseticket.dcode = u'ANYDCODE'
1830        courseticket.fcode = u'ANYFCODE'
1831        self.student['studycourse']['100']['COURSE2'] = courseticket
1832        self.student['studycourse']['100']['COURSE1'].score = 55
1833        self.assertEqual(self.student['studycourse']['100'].gpa, 3.57)
1834        # Get transcript data
1835        td = self.student['studycourse'].getTranscriptData()
1836        self.assertEqual(td[0][0]['level_key'], '100')
1837        self.assertEqual(td[0][0]['level'].level, 100)
1838        self.assertEqual(td[0][0]['level'].level_session, 2005)
1839        self.assertEqual(td[0][0]['tickets_1'][0].code, 'ANYCODE')
1840        self.assertEqual(td[0][0]['tickets_1'][1].code, 'COURSE1')
1841        self.assertEqual(td[1], 3.57)
1842        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1843        self.browser.open(self.student_path + '/studycourse/transcript')
1844        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1845        self.assertTrue('Transcript' in self.browser.contents)
1846        # Officers can open the pdf transcript
1847        self.browser.open(self.student_path + '/studycourse/transcript.pdf')
1848        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1849        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1850        path = os.path.join(samples_dir(), 'transcript.pdf')
1851        open(path, 'wb').write(self.browser.contents)
1852        print "Sample PDF transcript.pdf written to %s" % path
1853
1854class StudentUITests(StudentsFullSetup):
1855    # Tests for Student class views and pages
1856
1857    def test_student_change_password(self):
1858        # Students can change the password
1859        self.student.personal_updated = datetime.utcnow()
1860        self.browser.open(self.login_path)
1861        self.browser.getControl(name="form.login").value = self.student_id
1862        self.browser.getControl(name="form.password").value = 'spwd'
1863        self.browser.getControl("Login").click()
1864        self.assertEqual(self.browser.url, self.student_path)
1865        self.assertTrue('You logged in' in self.browser.contents)
1866        # Change password
1867        self.browser.getLink("Change password").click()
1868        self.browser.getControl(name="change_password").value = 'pw'
1869        self.browser.getControl(
1870            name="change_password_repeat").value = 'pw'
1871        self.browser.getControl("Save").click()
1872        self.assertTrue('Password must have at least' in self.browser.contents)
1873        self.browser.getControl(name="change_password").value = 'new_password'
1874        self.browser.getControl(
1875            name="change_password_repeat").value = 'new_passssword'
1876        self.browser.getControl("Save").click()
1877        self.assertTrue('Passwords do not match' in self.browser.contents)
1878        self.browser.getControl(name="change_password").value = 'new_password'
1879        self.browser.getControl(
1880            name="change_password_repeat").value = 'new_password'
1881        self.browser.getControl("Save").click()
1882        self.assertTrue('Password changed' in self.browser.contents)
1883        # We are still logged in. Changing the password hasn't thrown us out.
1884        self.browser.getLink("Base Data").click()
1885        self.assertEqual(self.browser.url, self.student_path)
1886        # We can logout
1887        self.browser.getLink("Logout").click()
1888        self.assertTrue('You have been logged out' in self.browser.contents)
1889        self.assertEqual(self.browser.url, 'http://localhost/app')
1890        # We can login again with the new password
1891        self.browser.getLink("Login").click()
1892        self.browser.open(self.login_path)
1893        self.browser.getControl(name="form.login").value = self.student_id
1894        self.browser.getControl(name="form.password").value = 'new_password'
1895        self.browser.getControl("Login").click()
1896        self.assertEqual(self.browser.url, self.student_path)
1897        self.assertTrue('You logged in' in self.browser.contents)
1898        return
1899
1900    def test_setpassword(self):
1901        # Set password for first-time access
1902        student = Student()
1903        student.reg_number = u'123456'
1904        student.firstname = u'Klaus'
1905        student.lastname = u'Tester'
1906        self.app['students'].addStudent(student)
1907        setpassword_path = 'http://localhost/app/setpassword'
1908        student_path = 'http://localhost/app/students/%s' % student.student_id
1909        self.browser.open(setpassword_path)
1910        self.browser.getControl(name="ac_series").value = self.existing_pwdseries
1911        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
1912        self.browser.getControl(name="reg_number").value = '223456'
1913        self.browser.getControl("Set").click()
1914        self.assertMatches('...No student found...',
1915                           self.browser.contents)
1916        self.browser.getControl(name="reg_number").value = '123456'
1917        self.browser.getControl(name="ac_number").value = '999999'
1918        self.browser.getControl("Set").click()
1919        self.assertMatches('...Access code is invalid...',
1920                           self.browser.contents)
1921        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
1922        self.browser.getControl("Set").click()
1923        self.assertMatches('...Password has been set. Your Student Id is...',
1924                           self.browser.contents)
1925        self.browser.getControl("Set").click()
1926        self.assertMatches(
1927            '...Password has already been set. Your Student Id is...',
1928            self.browser.contents)
1929        existing_pwdpin = self.pwdpins[1]
1930        parts = existing_pwdpin.split('-')[1:]
1931        existing_pwdseries, existing_pwdnumber = parts
1932        self.browser.getControl(name="ac_series").value = existing_pwdseries
1933        self.browser.getControl(name="ac_number").value = existing_pwdnumber
1934        self.browser.getControl(name="reg_number").value = '123456'
1935        self.browser.getControl("Set").click()
1936        self.assertMatches(
1937            '...You are using the wrong Access Code...',
1938            self.browser.contents)
1939        # The student can login with the new credentials
1940        self.browser.open(self.login_path)
1941        self.browser.getControl(name="form.login").value = student.student_id
1942        self.browser.getControl(
1943            name="form.password").value = self.existing_pwdnumber
1944        self.browser.getControl("Login").click()
1945        self.assertEqual(self.browser.url, student_path)
1946        self.assertTrue('You logged in' in self.browser.contents)
1947        return
1948
1949    def test_student_login(self):
1950        # Student cant login if their password is not set
1951        self.student.password = None
1952        self.browser.open(self.login_path)
1953        self.browser.getControl(name="form.login").value = self.student_id
1954        self.browser.getControl(name="form.password").value = 'spwd'
1955        self.browser.getControl("Login").click()
1956        self.assertTrue(
1957            'You entered invalid credentials.' in self.browser.contents)
1958        # We set the password again
1959        IUserAccount(
1960            self.app['students'][self.student_id]).setPassword('spwd')
1961        # Students can't login if their account is suspended/deactivated
1962        self.student.suspended = True
1963        self.browser.open(self.login_path)
1964        self.browser.getControl(name="form.login").value = self.student_id
1965        self.browser.getControl(name="form.password").value = 'spwd'
1966        self.browser.getControl("Login").click()
1967        self.assertMatches(
1968            '...<div class="alert-message warning">'
1969            'Your account has been deactivated.</div>...', self.browser.contents)
1970        # If suspended_comment is set this message will be flashed instead
1971        self.student.suspended_comment = u'Aetsch baetsch!'
1972        self.browser.getControl(name="form.login").value = self.student_id
1973        self.browser.getControl(name="form.password").value = 'spwd'
1974        self.browser.getControl("Login").click()
1975        self.assertMatches(
1976            '...<div class="alert-message warning">Aetsch baetsch!</div>...',
1977            self.browser.contents)
1978        self.student.suspended = False
1979        # Students can't login if a temporary password has been set and
1980        # is not expired
1981        self.app['students'][self.student_id].setTempPassword(
1982            'anybody', 'temp_spwd')
1983        self.browser.open(self.login_path)
1984        self.browser.getControl(name="form.login").value = self.student_id
1985        self.browser.getControl(name="form.password").value = 'spwd'
1986        self.browser.getControl("Login").click()
1987        self.assertMatches(
1988            '...Your account has been temporarily deactivated...',
1989            self.browser.contents)
1990        # The student can login with the temporary password
1991        self.browser.open(self.login_path)
1992        self.browser.getControl(name="form.login").value = self.student_id
1993        self.browser.getControl(name="form.password").value = 'temp_spwd'
1994        self.browser.getControl("Login").click()
1995        self.assertMatches(
1996            '...You logged in...', self.browser.contents)
1997        # Student can view the base data
1998        self.browser.open(self.student_path)
1999        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2000        self.assertEqual(self.browser.url, self.student_path)
2001        # When the password expires ...
2002        delta = timedelta(minutes=11)
2003        self.app['students'][self.student_id].temp_password[
2004            'timestamp'] = datetime.utcnow() - delta
2005        self.app['students'][self.student_id]._p_changed = True
2006        # ... the student will be automatically logged out
2007        self.assertRaises(
2008            Unauthorized, self.browser.open, self.student_path)
2009        # Then the student can login with the original password
2010        self.browser.open(self.login_path)
2011        self.browser.getControl(name="form.login").value = self.student_id
2012        self.browser.getControl(name="form.password").value = 'spwd'
2013        self.browser.getControl("Login").click()
2014        self.assertMatches(
2015            '...You logged in...', self.browser.contents)
2016
2017    def test_student_clearance(self):
2018        # Student cant login if their password is not set
2019        IWorkflowInfo(self.student).fireTransition('admit')
2020        self.browser.open(self.login_path)
2021        self.browser.getControl(name="form.login").value = self.student_id
2022        self.browser.getControl(name="form.password").value = 'spwd'
2023        self.browser.getControl("Login").click()
2024        self.assertMatches(
2025            '...You logged in...', self.browser.contents)
2026        # Admitted student can upload a passport picture
2027        self.browser.open(self.student_path + '/change_portrait')
2028        ctrl = self.browser.getControl(name='passportuploadedit')
2029        file_obj = open(SAMPLE_IMAGE, 'rb')
2030        file_ctrl = ctrl.mech_control
2031        file_ctrl.add_file(file_obj, filename='my_photo.jpg')
2032        self.browser.getControl(
2033            name='upload_passportuploadedit').click()
2034        self.assertTrue(
2035            '<img align="middle" height="125px" src="passport.jpg" />'
2036            in self.browser.contents)
2037        # Students can open admission letter
2038        self.browser.getLink("Base Data").click()
2039        self.browser.getLink("Download admission letter").click()
2040        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2041        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2042        # Student can view the clearance data
2043        self.browser.open(self.student_path)
2044        self.browser.getLink("Clearance Data").click()
2045        # Student can't open clearance edit form before starting clearance
2046        self.browser.open(self.student_path + '/cedit')
2047        self.assertMatches('...The requested form is locked...',
2048                           self.browser.contents)
2049        self.browser.getLink("Clearance Data").click()
2050        self.browser.getLink("Start clearance").click()
2051        self.student.email = None
2052        # Uups, we forgot to fill the email fields
2053        self.browser.getControl("Start clearance").click()
2054        self.assertMatches('...Not all required fields filled...',
2055                           self.browser.contents)
2056        self.browser.open(self.student_path + '/edit_base')
2057        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
2058        self.browser.getControl("Save").click()
2059        self.browser.open(self.student_path + '/start_clearance')
2060        self.browser.getControl(name="ac_series").value = '3'
2061        self.browser.getControl(name="ac_number").value = '4444444'
2062        self.browser.getControl("Start clearance now").click()
2063        self.assertMatches('...Activation code is invalid...',
2064                           self.browser.contents)
2065        self.browser.getControl(name="ac_series").value = self.existing_clrseries
2066        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
2067        # Owner is Hans Wurst, AC can't be invalidated
2068        self.browser.getControl("Start clearance now").click()
2069        self.assertMatches('...You are not the owner of this access code...',
2070                           self.browser.contents)
2071        # Set the correct owner
2072        self.existing_clrac.owner = self.student_id
2073        # clr_code might be set (and thus returns None) due importing
2074        # an empty clr_code column.
2075        self.student.clr_code = None
2076        self.browser.getControl("Start clearance now").click()
2077        self.assertMatches('...Clearance process has been started...',
2078                           self.browser.contents)
2079        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
2080        self.browser.getControl("Save", index=0).click()
2081        # Student can view the clearance data
2082        self.browser.getLink("Clearance Data").click()
2083        # and go back to the edit form
2084        self.browser.getLink("Edit").click()
2085        # Students can upload documents
2086        ctrl = self.browser.getControl(name='birthcertificateupload')
2087        file_obj = open(SAMPLE_IMAGE, 'rb')
2088        file_ctrl = ctrl.mech_control
2089        file_ctrl.add_file(file_obj, filename='my_birth_certificate.jpg')
2090        self.browser.getControl(
2091            name='upload_birthcertificateupload').click()
2092        self.assertTrue(
2093            '<a target="image" href="birth_certificate">Birth Certificate Scan</a>'
2094            in self.browser.contents)
2095        # Students can open clearance slip
2096        self.browser.getLink("View").click()
2097        self.browser.getLink("Download clearance slip").click()
2098        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2099        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2100        # Students can request clearance
2101        self.browser.open(self.edit_clearance_path)
2102        self.browser.getControl("Save and request clearance").click()
2103        self.browser.getControl(name="ac_series").value = self.existing_clrseries
2104        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
2105        self.browser.getControl("Request clearance now").click()
2106        self.assertMatches('...Clearance has been requested...',
2107                           self.browser.contents)
2108        # Student can't reopen clearance form after requesting clearance
2109        self.browser.open(self.student_path + '/cedit')
2110        self.assertMatches('...The requested form is locked...',
2111                           self.browser.contents)
2112
2113    def test_student_course_registration(self):
2114        # Student cant login if their password is not set
2115        IWorkflowInfo(self.student).fireTransition('admit')
2116        self.browser.open(self.login_path)
2117        self.browser.getControl(name="form.login").value = self.student_id
2118        self.browser.getControl(name="form.password").value = 'spwd'
2119        self.browser.getControl("Login").click()
2120        # Student can't add study level if not in state 'school fee paid'
2121        self.browser.open(self.student_path + '/studycourse/add')
2122        self.assertMatches('...The requested form is locked...',
2123                           self.browser.contents)
2124        # ... and must be transferred first
2125        IWorkflowState(self.student).setState('school fee paid')
2126        # Now students can add the current study level
2127        self.browser.getLink("Study Course").click()
2128        self.browser.getLink("Add course list").click()
2129        self.assertMatches('...Add current level 100 (Year 1)...',
2130                           self.browser.contents)
2131        self.browser.getControl("Create course list now").click()
2132        # A level with one course ticket was created
2133        self.assertEqual(self.student['studycourse']['100'].number_of_tickets, 1)
2134        self.browser.getLink("100").click()
2135        self.browser.getLink("Edit course list").click()
2136        self.browser.getLink("here").click()
2137        self.browser.getControl(name="form.course").value = ['COURSE1']
2138        self.browser.getControl("Add course ticket").click()
2139        self.assertMatches('...The ticket exists...',
2140                           self.browser.contents)
2141        self.student['studycourse'].current_level = 200
2142        self.browser.getLink("Study Course").click()
2143        self.browser.getLink("Add course list").click()
2144        self.assertMatches('...Add current level 200 (Year 2)...',
2145                           self.browser.contents)
2146        self.browser.getControl("Create course list now").click()
2147        self.browser.getLink("200").click()
2148        self.browser.getLink("Edit course list").click()
2149        self.browser.getLink("here").click()
2150        self.browser.getControl(name="form.course").value = ['COURSE1']
2151        self.course.credits = 100
2152        self.browser.getControl("Add course ticket").click()
2153        self.assertMatches(
2154            '...Total credits exceed 50...', self.browser.contents)
2155        self.course.credits = 10
2156        self.browser.getControl("Add course ticket").click()
2157        self.assertMatches('...The ticket exists...',
2158                           self.browser.contents)
2159        # Indeed the ticket exists as carry-over course from level 100
2160        # since its score was 0
2161        self.assertTrue(
2162            self.student['studycourse']['200']['COURSE1'].carry_over is True)
2163        # Students can open the pdf course registration slip
2164        self.browser.open(self.student_path + '/studycourse/200')
2165        self.browser.getLink("Download course registration slip").click()
2166        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2167        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2168        # Students can remove course tickets
2169        self.browser.open(self.student_path + '/studycourse/200/edit')
2170        self.browser.getControl("Remove selected", index=0).click()
2171        self.assertTrue('No ticket selected' in self.browser.contents)
2172        # No ticket can be selected since the carry-over course is a core course
2173        self.assertRaises(
2174            LookupError, self.browser.getControl, name='val_id')
2175        self.student['studycourse']['200']['COURSE1'].mandatory = False
2176        self.browser.open(self.student_path + '/studycourse/200/edit')
2177        # Course list can't be registered if total_credits exceeds max_credits
2178        self.student['studycourse']['200']['COURSE1'].credits = 60
2179        self.browser.getControl("Register course list").click()
2180        self.assertTrue('Maximum credits of 50 exceeded' in self.browser.contents)
2181        # Student can now remove the ticket
2182        ctrl = self.browser.getControl(name='val_id')
2183        ctrl.getControl(value='COURSE1').selected = True
2184        self.browser.getControl("Remove selected", index=0).click()
2185        self.assertTrue('Successfully removed' in self.browser.contents)
2186        # Removing course tickets is properly logged
2187        logfile = os.path.join(
2188            self.app['datacenter'].storage, 'logs', 'students.log')
2189        logcontent = open(logfile).read()
2190        self.assertTrue('K1000000 - students.browser.StudyLevelEditFormPage '
2191        '- K1000000 - removed: COURSE1 at 200' in logcontent)
2192        # They can add the same ticket using the edit page directly.
2193        # We can do the same by adding the course on the manage page directly
2194        self.browser.getControl(name="course").value = 'COURSE1'
2195        self.browser.getControl("Add course ticket").click()
2196        # Adding course tickets is logged
2197        logfile = os.path.join(
2198            self.app['datacenter'].storage, 'logs', 'students.log')
2199        logcontent = open(logfile).read()
2200        self.assertTrue('K1000000 - students.browser.StudyLevelEditFormPage - '
2201            'K1000000 - added: COURSE1|200|2004' in logcontent)
2202        # Course list can be registered
2203        self.browser.getControl("Register course list").click()
2204        self.assertTrue('Course list has been registered' in self.browser.contents)
2205        self.assertEqual(self.student.state, 'courses registered')
2206        # Students can open the pdf transcript
2207        self.browser.open(self.student_path + '/studycourse/transcript.pdf')
2208        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2209        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2210        return
2211
2212    def test_postgraduate_student_access(self):
2213        self.certificate.study_mode = 'pg_ft'
2214        self.certificate.start_level = 999
2215        self.certificate.end_level = 999
2216        self.student['studycourse'].current_level = 999
2217        IWorkflowState(self.student).setState('school fee paid')
2218        self.browser.open(self.login_path)
2219        self.browser.getControl(name="form.login").value = self.student_id
2220        self.browser.getControl(name="form.password").value = 'spwd'
2221        self.browser.getControl("Login").click()
2222        self.assertTrue(
2223            'You logged in.' in self.browser.contents)
2224        # Now students can add the current study level
2225        self.browser.getLink("Study Course").click()
2226        self.browser.getLink("Add course list").click()
2227        self.assertMatches('...Add current level Postgraduate Level...',
2228                           self.browser.contents)
2229        self.browser.getControl("Create course list now").click()
2230        # A level with one course ticket was created
2231        self.assertEqual(self.student['studycourse']['999'].number_of_tickets, 0)
2232        self.browser.getLink("999").click()
2233        self.browser.getLink("Edit course list").click()
2234        self.browser.getLink("here").click()
2235        self.browser.getControl(name="form.course").value = ['COURSE1']
2236        self.browser.getControl("Add course ticket").click()
2237        self.assertMatches('...Successfully added COURSE1...',
2238                           self.browser.contents)
2239        # Postgraduate students can't register course lists
2240        self.browser.getControl("Register course list").click()
2241        self.assertTrue("your course list can't bee registered"
2242            in self.browser.contents)
2243        self.assertEqual(self.student.state, 'school fee paid')
2244        return
2245
2246    def test_student_clearance_wo_clrcode(self):
2247        IWorkflowState(self.student).setState('clearance started')
2248        self.browser.open(self.login_path)
2249        self.browser.getControl(name="form.login").value = self.student_id
2250        self.browser.getControl(name="form.password").value = 'spwd'
2251        self.browser.getControl("Login").click()
2252        self.student.clearance_locked = False
2253        self.browser.open(self.edit_clearance_path)
2254        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
2255        self.browser.getControl("Save and request clearance").click()
2256        self.assertMatches('...Clearance has been requested...',
2257                           self.browser.contents)
2258
2259    def test_student_clearance_payment(self):
2260        # Login
2261        self.browser.open(self.login_path)
2262        self.browser.getControl(name="form.login").value = self.student_id
2263        self.browser.getControl(name="form.password").value = 'spwd'
2264        self.browser.getControl("Login").click()
2265
2266        # Students can add online clearance payment tickets
2267        self.browser.open(self.payments_path + '/addop')
2268        self.browser.getControl(name="form.p_category").value = ['clearance']
2269        self.browser.getControl("Create ticket").click()
2270        self.assertMatches('...ticket created...',
2271                           self.browser.contents)
2272
2273        # Students can't approve the payment
2274        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
2275        ctrl = self.browser.getControl(name='val_id')
2276        value = ctrl.options[0]
2277        self.browser.getLink(value).click()
2278        payment_url = self.browser.url
2279        self.assertRaises(
2280            Unauthorized, self.browser.open, payment_url + '/approve')
2281        # In the base package they can 'use' a fake approval view.
2282        # XXX: I tried to use
2283        # self.student['payments'][value].approveStudentPayment() instead.
2284        # But this function fails in
2285        # w.k.accesscodes.accesscode.create_accesscode.
2286        # grok.getSite returns None in tests.
2287        self.browser.open(payment_url + '/fake_approve')
2288        self.assertMatches('...Payment approved...',
2289                          self.browser.contents)
2290        expected = '''...
2291        <td>
2292          <span>Paid</span>
2293        </td>...'''
2294        expected = '''...
2295        <td>
2296          <span>Paid</span>
2297        </td>...'''
2298        self.assertMatches(expected,self.browser.contents)
2299        payment_id = self.student['payments'].keys()[0]
2300        payment = self.student['payments'][payment_id]
2301        self.assertEqual(payment.p_state, 'paid')
2302        self.assertEqual(payment.r_amount_approved, 3456.0)
2303        self.assertEqual(payment.r_code, 'AP')
2304        self.assertEqual(payment.r_desc, u'Payment approved by Anna Tester')
2305        # The new CLR-0 pin has been created
2306        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
2307        pin = self.app['accesscodes']['CLR-0'].keys()[0]
2308        ac = self.app['accesscodes']['CLR-0'][pin]
2309        self.assertEqual(ac.owner, self.student_id)
2310        self.assertEqual(ac.cost, 3456.0)
2311
2312        # Students can open the pdf payment slip
2313        self.browser.open(payment_url + '/payment_slip.pdf')
2314        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2315        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2316
2317        # The new CLR-0 pin can be used for starting clearance
2318        # but they have to upload a passport picture first
2319        # which is only possible in state admitted
2320        self.browser.open(self.student_path + '/change_portrait')
2321        self.assertMatches('...form is locked...',
2322                          self.browser.contents)
2323        IWorkflowInfo(self.student).fireTransition('admit')
2324        self.browser.open(self.student_path + '/change_portrait')
2325        image = open(SAMPLE_IMAGE, 'rb')
2326        ctrl = self.browser.getControl(name='passportuploadedit')
2327        file_ctrl = ctrl.mech_control
2328        file_ctrl.add_file(image, filename='my_photo.jpg')
2329        self.browser.getControl(
2330            name='upload_passportuploadedit').click()
2331        self.browser.open(self.student_path + '/start_clearance')
2332        parts = pin.split('-')[1:]
2333        clrseries, clrnumber = parts
2334        self.browser.getControl(name="ac_series").value = clrseries
2335        self.browser.getControl(name="ac_number").value = clrnumber
2336        self.browser.getControl("Start clearance now").click()
2337        self.assertMatches('...Clearance process has been started...',
2338                           self.browser.contents)
2339
2340    def test_student_schoolfee_payment(self):
2341        configuration = createObject('waeup.SessionConfiguration')
2342        configuration.academic_session = 2005
2343        self.app['configuration'].addSessionConfiguration(configuration)
2344        # Login
2345        self.browser.open(self.login_path)
2346        self.browser.getControl(name="form.login").value = self.student_id
2347        self.browser.getControl(name="form.password").value = 'spwd'
2348        self.browser.getControl("Login").click()
2349
2350        # Students can add online school fee payment tickets.
2351        IWorkflowState(self.student).setState('returning')
2352        self.browser.open(self.payments_path)
2353        self.assertRaises(
2354            LookupError, self.browser.getControl, name='val_id')
2355        self.browser.getLink("Add current session payment ticket").click()
2356        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2357        self.browser.getControl("Create ticket").click()
2358        self.assertMatches('...ticket created...',
2359                           self.browser.contents)
2360        ctrl = self.browser.getControl(name='val_id')
2361        value = ctrl.options[0]
2362        self.browser.getLink(value).click()
2363        self.assertMatches('...Amount Authorized...',
2364                           self.browser.contents)
2365        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2366        # Payment session and will be calculated as defined
2367        # in w.k.students.utils because we set changed the state
2368        # to returning
2369        self.assertEqual(self.student['payments'][value].p_session, 2005)
2370        self.assertEqual(self.student['payments'][value].p_level, 200)
2371
2372        # Student is the payer of the payment ticket.
2373        payer = IPayer(self.student['payments'][value])
2374        self.assertEqual(payer.display_fullname, 'Anna Tester')
2375        self.assertEqual(payer.id, self.student_id)
2376        self.assertEqual(payer.faculty, 'fac1')
2377        self.assertEqual(payer.department, 'dep1')
2378
2379        # We simulate the approval
2380        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
2381        self.browser.open(self.browser.url + '/fake_approve')
2382        self.assertMatches('...Payment approved...',
2383                          self.browser.contents)
2384
2385        # The new SFE-0 pin can be used for starting new session
2386        self.browser.open(self.studycourse_path)
2387        self.browser.getLink('Start new session').click()
2388        pin = self.app['accesscodes']['SFE-0'].keys()[0]
2389        parts = pin.split('-')[1:]
2390        sfeseries, sfenumber = parts
2391        self.browser.getControl(name="ac_series").value = sfeseries
2392        self.browser.getControl(name="ac_number").value = sfenumber
2393        self.browser.getControl("Start now").click()
2394        self.assertMatches('...Session started...',
2395                           self.browser.contents)
2396        self.assertTrue(self.student.state == 'school fee paid')
2397        return
2398
2399    def test_student_bedallocation_payment(self):
2400        # Login
2401        self.browser.open(self.login_path)
2402        self.browser.getControl(name="form.login").value = self.student_id
2403        self.browser.getControl(name="form.password").value = 'spwd'
2404        self.browser.getControl("Login").click()
2405        self.browser.open(self.payments_path)
2406        self.browser.open(self.payments_path + '/addop')
2407        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
2408        self.browser.getControl("Create ticket").click()
2409        self.assertMatches('...ticket created...',
2410                           self.browser.contents)
2411        # Students can remove only online payment tickets which have
2412        # not received a valid callback
2413        self.browser.open(self.payments_path)
2414        ctrl = self.browser.getControl(name='val_id')
2415        value = ctrl.options[0]
2416        ctrl.getControl(value=value).selected = True
2417        self.browser.getControl("Remove selected", index=0).click()
2418        self.assertTrue('Successfully removed' in self.browser.contents)
2419
2420    def test_student_maintenance_payment(self):
2421        # Login
2422        self.browser.open(self.login_path)
2423        self.browser.getControl(name="form.login").value = self.student_id
2424        self.browser.getControl(name="form.password").value = 'spwd'
2425        self.browser.getControl("Login").click()
2426        self.browser.open(self.payments_path)
2427        self.browser.open(self.payments_path + '/addop')
2428        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
2429        self.browser.getControl("Create ticket").click()
2430        self.assertMatches('...You have not yet booked accommodation...',
2431                           self.browser.contents)
2432        # We continue this test in test_student_accommodation
2433
2434    def test_student_previous_payments(self):
2435        configuration = createObject('waeup.SessionConfiguration')
2436        configuration.academic_session = 2000
2437        configuration.clearance_fee = 3456.0
2438        configuration.booking_fee = 123.4
2439        self.app['configuration'].addSessionConfiguration(configuration)
2440        configuration2 = createObject('waeup.SessionConfiguration')
2441        configuration2.academic_session = 2003
2442        configuration2.clearance_fee = 3456.0
2443        configuration2.booking_fee = 123.4
2444        self.app['configuration'].addSessionConfiguration(configuration2)
2445        configuration3 = createObject('waeup.SessionConfiguration')
2446        configuration3.academic_session = 2005
2447        configuration3.clearance_fee = 3456.0
2448        configuration3.booking_fee = 123.4
2449        self.app['configuration'].addSessionConfiguration(configuration3)
2450        self.student['studycourse'].entry_session = 2002
2451
2452        # Login
2453        self.browser.open(self.login_path)
2454        self.browser.getControl(name="form.login").value = self.student_id
2455        self.browser.getControl(name="form.password").value = 'spwd'
2456        self.browser.getControl("Login").click()
2457
2458        # Students can add previous school fee payment tickets in any state.
2459        IWorkflowState(self.student).setState('courses registered')
2460        self.browser.open(self.payments_path)
2461        self.browser.getLink("Add previous session payment ticket").click()
2462
2463        # Previous session payment form is provided
2464        self.assertEqual(self.student.current_session, 2004)
2465        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2466        self.browser.getControl(name="form.p_session").value = ['2000']
2467        self.browser.getControl(name="form.p_level").value = ['300']
2468        self.browser.getControl("Create ticket").click()
2469        self.assertMatches('...The previous session must not fall below...',
2470                           self.browser.contents)
2471        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2472        self.browser.getControl(name="form.p_session").value = ['2005']
2473        self.browser.getControl(name="form.p_level").value = ['300']
2474        self.browser.getControl("Create ticket").click()
2475        self.assertMatches('...This is not a previous session...',
2476                           self.browser.contents)
2477        # Students can pay current session school fee.
2478        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2479        self.browser.getControl(name="form.p_session").value = ['2004']
2480        self.browser.getControl(name="form.p_level").value = ['300']
2481        self.browser.getControl("Create ticket").click()
2482        self.assertMatches('...ticket created...',
2483                           self.browser.contents)
2484        ctrl = self.browser.getControl(name='val_id')
2485        value = ctrl.options[0]
2486        self.browser.getLink(value).click()
2487        self.assertMatches('...Amount Authorized...',
2488                           self.browser.contents)
2489        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2490
2491        # Payment session is properly set
2492        self.assertEqual(self.student['payments'][value].p_session, 2004)
2493        self.assertEqual(self.student['payments'][value].p_level, 300)
2494
2495        # We simulate the approval
2496        self.browser.open(self.browser.url + '/fake_approve')
2497        self.assertMatches('...Payment approved...',
2498                          self.browser.contents)
2499
2500        # No AC has been created
2501        self.assertEqual(len(self.app['accesscodes']['SFE-0'].keys()), 0)
2502        self.assertTrue(self.student['payments'][value].ac is None)
2503
2504        # Current payment flag is set False
2505        self.assertFalse(self.student['payments'][value].p_current)
2506
2507        # Button and form are not available for students who are in
2508        # states up to cleared
2509        self.student['studycourse'].entry_session = 2004
2510        IWorkflowState(self.student).setState('cleared')
2511        self.browser.open(self.payments_path)
2512        self.assertFalse(
2513            "Add previous session payment ticket" in self.browser.contents)
2514        self.browser.open(self.payments_path + '/addpp')
2515        self.assertTrue(
2516            "No previous payment to be made" in self.browser.contents)
2517        return
2518
2519    def test_postgraduate_student_payments(self):
2520        configuration = createObject('waeup.SessionConfiguration')
2521        configuration.academic_session = 2005
2522        self.app['configuration'].addSessionConfiguration(configuration)
2523        self.certificate.study_mode = 'pg_ft'
2524        self.certificate.start_level = 999
2525        self.certificate.end_level = 999
2526        self.student['studycourse'].current_level = 999
2527        # Login
2528        self.browser.open(self.login_path)
2529        self.browser.getControl(name="form.login").value = self.student_id
2530        self.browser.getControl(name="form.password").value = 'spwd'
2531        self.browser.getControl("Login").click()
2532        # Students can add online school fee payment tickets.
2533        IWorkflowState(self.student).setState('cleared')
2534        self.browser.open(self.payments_path)
2535        self.browser.getLink("Add current session payment ticket").click()
2536        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2537        self.browser.getControl("Create ticket").click()
2538        self.assertMatches('...ticket created...',
2539                           self.browser.contents)
2540        ctrl = self.browser.getControl(name='val_id')
2541        value = ctrl.options[0]
2542        self.browser.getLink(value).click()
2543        self.assertMatches('...Amount Authorized...',
2544                           self.browser.contents)
2545        # Payment session and level are current ones.
2546        # Postgrads have to pay school_fee_1.
2547        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
2548        self.assertEqual(self.student['payments'][value].p_session, 2004)
2549        self.assertEqual(self.student['payments'][value].p_level, 999)
2550
2551        # We simulate the approval
2552        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
2553        self.browser.open(self.browser.url + '/fake_approve')
2554        self.assertMatches('...Payment approved...',
2555                          self.browser.contents)
2556
2557        # The new SFE-0 pin can be used for starting session
2558        self.browser.open(self.studycourse_path)
2559        self.browser.getLink('Start new session').click()
2560        pin = self.app['accesscodes']['SFE-0'].keys()[0]
2561        parts = pin.split('-')[1:]
2562        sfeseries, sfenumber = parts
2563        self.browser.getControl(name="ac_series").value = sfeseries
2564        self.browser.getControl(name="ac_number").value = sfenumber
2565        self.browser.getControl("Start now").click()
2566        self.assertMatches('...Session started...',
2567                           self.browser.contents)
2568        self.assertTrue(self.student.state == 'school fee paid')
2569
2570        # Postgrad students do not need to register courses the
2571        # can just pay for the next session.
2572        self.browser.open(self.payments_path)
2573        # Remove first payment to be sure that we access the right ticket
2574        del self.student['payments'][value]
2575        self.browser.getLink("Add current session payment ticket").click()
2576        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2577        self.browser.getControl("Create ticket").click()
2578        ctrl = self.browser.getControl(name='val_id')
2579        value = ctrl.options[0]
2580        self.browser.getLink(value).click()
2581        # Payment session has increased by one, payment level remains the same.
2582        # Returning Postgraduates have to pay school_fee_2.
2583        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2584        self.assertEqual(self.student['payments'][value].p_session, 2005)
2585        self.assertEqual(self.student['payments'][value].p_level, 999)
2586
2587        # Student is still in old session
2588        self.assertEqual(self.student.current_session, 2004)
2589
2590        # We do not need to pay the ticket if any other
2591        # SFE pin is provided
2592        pin_container = self.app['accesscodes']
2593        pin_container.createBatch(
2594            datetime.utcnow(), 'some_userid', 'SFE', 9.99, 1)
2595        pin = pin_container['SFE-1'].values()[0].representation
2596        sfeseries, sfenumber = pin.split('-')[1:]
2597        # The new SFE-1 pin can be used for starting new session
2598        self.browser.open(self.studycourse_path)
2599        self.browser.getLink('Start new session').click()
2600        self.browser.getControl(name="ac_series").value = sfeseries
2601        self.browser.getControl(name="ac_number").value = sfenumber
2602        self.browser.getControl("Start now").click()
2603        self.assertMatches('...Session started...',
2604                           self.browser.contents)
2605        self.assertTrue(self.student.state == 'school fee paid')
2606        # Student is in new session
2607        self.assertEqual(self.student.current_session, 2005)
2608        self.assertEqual(self.student['studycourse'].current_level, 999)
2609        return
2610
2611    def test_student_accommodation(self):
2612        # Login
2613        self.browser.open(self.login_path)
2614        self.browser.getControl(name="form.login").value = self.student_id
2615        self.browser.getControl(name="form.password").value = 'spwd'
2616        self.browser.getControl("Login").click()
2617
2618        # Students can add online booking fee payment tickets and open the
2619        # callback view (see test_manage_payments)
2620        self.browser.getLink("Payments").click()
2621        self.browser.getLink("Add current session payment ticket").click()
2622        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
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        self.browser.open(self.browser.url + '/fake_approve')
2628        # The new HOS-0 pin has been created
2629        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
2630        pin = self.app['accesscodes']['HOS-0'].keys()[0]
2631        ac = self.app['accesscodes']['HOS-0'][pin]
2632        parts = pin.split('-')[1:]
2633        sfeseries, sfenumber = parts
2634
2635        # Students can use HOS code and book a bed space with it ...
2636        self.browser.open(self.acco_path)
2637        # ... but not if booking period has expired ...
2638        self.app['hostels'].enddate = datetime.now(pytz.utc)
2639        self.browser.getLink("Book accommodation").click()
2640        self.assertMatches('...Outside booking period: ...',
2641                           self.browser.contents)
2642        self.app['hostels'].enddate = datetime.now(pytz.utc) + timedelta(days=10)
2643        # ... or student is not the an allowed state ...
2644        self.browser.getLink("Book accommodation").click()
2645        self.assertMatches('...You are in the wrong...',
2646                           self.browser.contents)
2647        IWorkflowInfo(self.student).fireTransition('admit')
2648        self.browser.getLink("Book accommodation").click()
2649        self.assertMatches('...Activation Code:...',
2650                           self.browser.contents)
2651        # Student can't used faked ACs ...
2652        self.browser.getControl(name="ac_series").value = u'nonsense'
2653        self.browser.getControl(name="ac_number").value = sfenumber
2654        self.browser.getControl("Create bed ticket").click()
2655        self.assertMatches('...Activation code is invalid...',
2656                           self.browser.contents)
2657        # ... or ACs owned by somebody else.
2658        ac.owner = u'Anybody'
2659        self.browser.getControl(name="ac_series").value = sfeseries
2660        self.browser.getControl(name="ac_number").value = sfenumber
2661        self.browser.getControl("Create bed ticket").click()
2662        self.assertMatches('...You are not the owner of this access code...',
2663                           self.browser.contents)
2664        ac.owner = self.student_id
2665        self.browser.getControl(name="ac_series").value = sfeseries
2666        self.browser.getControl(name="ac_number").value = sfenumber
2667        self.browser.getControl("Create bed ticket").click()
2668        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
2669                           self.browser.contents)
2670
2671        # Bed has been allocated
2672        bed = self.app['hostels']['hall-1']['hall-1_A_101_A']
2673        self.assertTrue(bed.owner == self.student_id)
2674
2675        # BedTicketAddPage is now blocked
2676        self.browser.getLink("Book accommodation").click()
2677        self.assertMatches('...You already booked a bed space...',
2678            self.browser.contents)
2679
2680        # The bed ticket displays the data correctly
2681        self.browser.open(self.acco_path + '/2004')
2682        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
2683                           self.browser.contents)
2684        self.assertMatches('...2004/2005...', self.browser.contents)
2685        self.assertMatches('...regular_male_fr...', self.browser.contents)
2686        self.assertMatches('...%s...' % pin, self.browser.contents)
2687
2688        # Students can open the pdf slip
2689        self.browser.open(self.browser.url + '/bed_allocation_slip.pdf')
2690        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2691        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2692
2693        # Students can't relocate themselves
2694        self.assertFalse('Relocate' in self.browser.contents)
2695        relocate_path = self.acco_path + '/2004/relocate'
2696        self.assertRaises(
2697            Unauthorized, self.browser.open, relocate_path)
2698
2699        # Students can't the Remove button and check boxes
2700        self.browser.open(self.acco_path)
2701        self.assertFalse('Remove' in self.browser.contents)
2702        self.assertFalse('val_id' in self.browser.contents)
2703
2704        # Students can pay maintenance fee now
2705        self.browser.open(self.payments_path)
2706        self.browser.open(self.payments_path + '/addop')
2707        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
2708        self.browser.getControl("Create ticket").click()
2709        self.assertMatches('...Payment ticket created...',
2710                           self.browser.contents)
2711        return
2712
2713    def test_change_password_request(self):
2714        self.browser.open('http://localhost/app/changepw')
2715        self.browser.getControl(name="form.identifier").value = '123'
2716        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
2717        self.browser.getControl("Send login credentials").click()
2718        self.assertTrue('An email with' in self.browser.contents)
2719
2720    def test_student_expired_personal_data(self):
2721        # Login
2722        IWorkflowState(self.student).setState('school fee paid')
2723        delta = timedelta(days=180)
2724        self.student.personal_updated = datetime.utcnow() - delta
2725        self.browser.open(self.login_path)
2726        self.browser.getControl(name="form.login").value = self.student_id
2727        self.browser.getControl(name="form.password").value = 'spwd'
2728        self.browser.getControl("Login").click()
2729        self.assertEqual(self.browser.url, self.student_path)
2730        self.assertTrue(
2731            'You logged in' in self.browser.contents)
2732        # Students don't see personal_updated field in edit form
2733        self.browser.open(self.edit_personal_path)
2734        self.assertFalse('Updated' in self.browser.contents)
2735        self.browser.open(self.personal_path)
2736        self.assertTrue('Updated' in self.browser.contents)
2737        self.browser.getLink("Logout").click()
2738        delta = timedelta(days=181)
2739        self.student.personal_updated = datetime.utcnow() - delta
2740        self.browser.open(self.login_path)
2741        self.browser.getControl(name="form.login").value = self.student_id
2742        self.browser.getControl(name="form.password").value = 'spwd'
2743        self.browser.getControl("Login").click()
2744        self.assertEqual(self.browser.url, self.edit_personal_path)
2745        self.assertTrue(
2746            'Your personal data record is outdated.' in self.browser.contents)
2747
2748    def test_setReturningData(self):
2749        utils = getUtility(IStudentsUtils)
2750        self.student['studycourse'].current_level = 600
2751        utils.setReturningData(self.student)
2752        # The new level exceeds the certificates end_level.
2753        # In this case current_level remains unchanged and no error is raised.
2754        self.assertEqual(self.student['studycourse'].current_level, 600)
2755
2756class StudentRequestPWTests(StudentsFullSetup):
2757    # Tests for student registration
2758
2759    layer = FunctionalLayer
2760
2761    def test_request_pw(self):
2762        # Student with wrong number can't be found.
2763        self.browser.open('http://localhost/app/requestpw')
2764        self.browser.getControl(name="form.firstname").value = 'Anna'
2765        self.browser.getControl(name="form.number").value = 'anynumber'
2766        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
2767        self.browser.getControl("Send login credentials").click()
2768        self.assertTrue('No student record found.'
2769            in self.browser.contents)
2770        # Anonymous is not informed that firstname verification failed.
2771        # It seems that the record doesn't exist.
2772        self.browser.open('http://localhost/app/requestpw')
2773        self.browser.getControl(name="form.firstname").value = 'Johnny'
2774        self.browser.getControl(name="form.number").value = '123'
2775        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
2776        self.browser.getControl("Send login credentials").click()
2777        self.assertTrue('No student record found.'
2778            in self.browser.contents)
2779        # Even with the correct firstname we can't register if a
2780        # password has been set and used.
2781        self.browser.getControl(name="form.firstname").value = 'Anna'
2782        self.browser.getControl(name="form.number").value = '123'
2783        self.browser.getControl("Send login credentials").click()
2784        self.assertTrue('Your password has already been set and used.'
2785            in self.browser.contents)
2786        self.browser.open('http://localhost/app/requestpw')
2787        self.app['students'][self.student_id].password = None
2788        # The firstname field, used for verification, is not case-sensitive.
2789        self.browser.getControl(name="form.firstname").value = 'aNNa'
2790        self.browser.getControl(name="form.number").value = '123'
2791        self.browser.getControl(name="form.email").value = 'new@yy.zz'
2792        self.browser.getControl("Send login credentials").click()
2793        # Yeah, we succeded ...
2794        self.assertTrue('Your password request was successful.'
2795            in self.browser.contents)
2796        # We can also use the matric_number instead.
2797        self.browser.open('http://localhost/app/requestpw')
2798        self.browser.getControl(name="form.firstname").value = 'aNNa'
2799        self.browser.getControl(name="form.number").value = '234'
2800        self.browser.getControl(name="form.email").value = 'new@yy.zz'
2801        self.browser.getControl("Send login credentials").click()
2802        self.assertTrue('Your password request was successful.'
2803            in self.browser.contents)
2804        # ... and  student can be found in the catalog via the email address
2805        cat = queryUtility(ICatalog, name='students_catalog')
2806        results = list(
2807            cat.searchResults(
2808            email=('new@yy.zz', 'new@yy.zz')))
2809        self.assertEqual(self.student,results[0])
2810        logfile = os.path.join(
2811            self.app['datacenter'].storage, 'logs', 'main.log')
2812        logcontent = open(logfile).read()
2813        self.assertTrue('zope.anybody - students.browser.StudentRequestPasswordPage - '
2814                        '234 (K1000000) - new@yy.zz' in logcontent)
2815        return
2816
2817    def test_student_locked_level_forms(self):
2818
2819        # Add two study levels, one current and one previous
2820        studylevel = createObject(u'waeup.StudentStudyLevel')
2821        studylevel.level = 100
2822        self.student['studycourse'].addStudentStudyLevel(
2823            self.certificate, studylevel)
2824        studylevel = createObject(u'waeup.StudentStudyLevel')
2825        studylevel.level = 200
2826        self.student['studycourse'].addStudentStudyLevel(
2827            self.certificate, studylevel)
2828        IWorkflowState(self.student).setState('school fee paid')
2829        self.student['studycourse'].current_level = 200
2830
2831        self.browser.open(self.login_path)
2832        self.browser.getControl(name="form.login").value = self.student_id
2833        self.browser.getControl(name="form.password").value = 'spwd'
2834        self.browser.getControl("Login").click()
2835
2836        self.browser.open(self.student_path + '/studycourse/200/edit')
2837        self.assertFalse('The requested form is locked' in self.browser.contents)
2838        self.browser.open(self.student_path + '/studycourse/100/edit')
2839        self.assertTrue('The requested form is locked' in self.browser.contents)
2840
2841        self.browser.open(self.student_path + '/studycourse/200/ctadd')
2842        self.assertFalse('The requested form is locked' in self.browser.contents)
2843        self.browser.open(self.student_path + '/studycourse/100/ctadd')
2844        self.assertTrue('The requested form is locked' in self.browser.contents)
2845
2846        IWorkflowState(self.student).setState('courses registered')
2847        self.browser.open(self.student_path + '/studycourse/200/edit')
2848        self.assertTrue('The requested form is locked' in self.browser.contents)
2849        self.browser.open(self.student_path + '/studycourse/200/ctadd')
2850        self.assertTrue('The requested form is locked' in self.browser.contents)
2851
2852
2853class PublicPagesTests(StudentsFullSetup):
2854    # Tests for simple webservices
2855
2856    layer = FunctionalLayer
2857
2858    def test_paymentrequest(self):
2859        payment = createObject('waeup.StudentOnlinePayment')
2860        payment.p_category = u'schoolfee'
2861        payment.p_session = self.student.current_session
2862        payment.p_item = u'My Certificate'
2863        payment.p_id = u'anyid'
2864        self.student['payments']['anykey'] = payment
2865        # Request information about unpaid payment ticket
2866        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
2867        self.assertEqual(self.browser.contents, '-1')
2868        # Request information about paid payment ticket
2869        payment.p_state = u'paid'
2870        notify(grok.ObjectModifiedEvent(payment))
2871        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
2872        self.assertEqual(self.browser.contents,
2873            'FULL_NAME=Anna Tester&FACULTY=fac1&DEPARTMENT=dep1'
2874            '&PAYMENT_ITEM=My Certificate&PAYMENT_CATEGORY=School Fee'
2875            '&ACADEMIC_SESSION=2004/2005&MATRIC_NUMBER=234&REG_NUMBER=123'
2876            '&FEE_AMOUNT=0.0')
2877        self.browser.open('http://localhost/app/paymentrequest?NONSENSE=nonsense')
2878        self.assertEqual(self.browser.contents, '-1')
2879        self.browser.open('http://localhost/app/paymentrequest?P_ID=nonsense')
2880        self.assertEqual(self.browser.contents, '-1')
2881
2882class StudentDataExportTests(StudentsFullSetup, FunctionalAsyncTestCase):
2883    # Tests for StudentsContainer class views and pages
2884
2885    layer = FunctionalLayer
2886
2887    def wait_for_export_job_completed(self):
2888        # helper function waiting until the current export job is completed
2889        manager = getUtility(IJobManager)
2890        job_id = self.app['datacenter'].running_exports[0][0]
2891        job = manager.get(job_id)
2892        wait_for_result(job)
2893        return job_id
2894
2895    def test_faculties_export(self):
2896        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
2897        facs_path = 'http://localhost/app/faculties'
2898        self.browser.open(facs_path)
2899        self.browser.getLink("Export student data").click()
2900        self.browser.getControl("Configure new export").click()
2901        self.browser.getControl(name="exporter").value = ['bursary']
2902        self.browser.getControl(name="session").value = ['2004']
2903        self.browser.getControl(name="level").value = ['100']
2904        self.browser.getControl(name="mode").value = ['ug_ft']
2905        self.browser.getControl("Create CSV file").click()
2906
2907        # When the job is finished and we reload the page...
2908        job_id = self.wait_for_export_job_completed()
2909        self.browser.open(facs_path + '/exports')
2910        # ... the csv file can be downloaded ...
2911        self.browser.getLink("Download").click()
2912        self.assertEqual(self.browser.headers['content-type'],
2913            'text/csv; charset=UTF-8')
2914        self.assertTrue(
2915            'filename="WAeUP.Kofa_bursary_%s.csv' % job_id in
2916            self.browser.headers['content-disposition'])
2917        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
2918        job_id = self.app['datacenter'].running_exports[0][0]
2919        # ... and discarded
2920        self.browser.open(facs_path + '/exports')
2921        self.browser.getControl("Discard").click()
2922        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
2923        # Creation, downloading and discarding is logged
2924        logfile = os.path.join(
2925            self.app['datacenter'].storage, 'logs', 'datacenter.log')
2926        logcontent = open(logfile).read()
2927        self.assertTrue(
2928            'zope.mgr - students.browser.FacultiesExportJobContainerJobConfig '
2929            '- exported: bursary (2004, 100, ug_ft, None, None), job_id=%s'
2930            % job_id in logcontent
2931            )
2932        self.assertTrue(
2933            'zope.mgr - students.browser.ExportJobContainerDownload '
2934            '- downloaded: WAeUP.Kofa_bursary_%s.csv, job_id=%s'
2935            % (job_id, job_id) in logcontent
2936            )
2937        self.assertTrue(
2938            'zope.mgr - students.browser.ExportJobContainerOverview '
2939            '- discarded: job_id=%s' % job_id in logcontent
2940            )
2941
2942    def test_department_export(self):
2943        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
2944        dep1_path = 'http://localhost/app/faculties/fac1/dep1'
2945        self.browser.open(dep1_path)
2946        self.browser.getLink("Export student data").click()
2947        self.browser.getControl("Configure new export").click()
2948        self.browser.getControl(name="exporter").value = ['students']
2949        self.browser.getControl(name="session").value = ['2004']
2950        self.browser.getControl(name="level").value = ['100']
2951        self.browser.getControl(name="mode").value = ['ug_ft']
2952        self.browser.getControl("Create CSV file").click()
2953
2954        # When the job is finished and we reload the page...
2955        job_id = self.wait_for_export_job_completed()
2956        self.browser.open(dep1_path + '/exports')
2957        # ... the csv file can be downloaded ...
2958        self.browser.getLink("Download").click()
2959        self.assertEqual(self.browser.headers['content-type'],
2960            'text/csv; charset=UTF-8')
2961        self.assertTrue(
2962            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
2963            self.browser.headers['content-disposition'])
2964        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
2965        job_id = self.app['datacenter'].running_exports[0][0]
2966        # ... and discarded
2967        self.browser.open(dep1_path + '/exports')
2968        self.browser.getControl("Discard").click()
2969        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
2970        # Creation, downloading and discarding is logged
2971        logfile = os.path.join(
2972            self.app['datacenter'].storage, 'logs', 'datacenter.log')
2973        logcontent = open(logfile).read()
2974        self.assertTrue(
2975            'zope.mgr - students.browser.DepartmentExportJobContainerJobConfig '
2976            '- exported: students (2004, 100, ug_ft, dep1, None), job_id=%s'
2977            % job_id in logcontent
2978            )
2979        self.assertTrue(
2980            'zope.mgr - students.browser.ExportJobContainerDownload '
2981            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
2982            % (job_id, job_id) in logcontent
2983            )
2984        self.assertTrue(
2985            'zope.mgr - students.browser.ExportJobContainerOverview '
2986            '- discarded: job_id=%s' % job_id in logcontent
2987            )
2988
2989    def test_certificate_export(self):
2990        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
2991        cert1_path = 'http://localhost/app/faculties/fac1/dep1/certificates/CERT1'
2992        self.browser.open(cert1_path)
2993        self.browser.getLink("Export student data").click()
2994        self.browser.getControl("Configure new export").click()
2995        self.browser.getControl(name="exporter").value = ['students']
2996        self.browser.getControl(name="session").value = ['2004']
2997        self.browser.getControl(name="level").value = ['100']
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(cert1_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(cert1_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.CertificateExportJobContainerJobConfig '
3022            '- exported: students (2004, 100, None, None, CERT1), 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_course_export_students(self):
3036        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3037        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
3038        self.browser.open(course1_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(course1_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(course1_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.CourseExportJobContainerJobConfig '
3068            '- exported: students (2004, 100, COURSE1), 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_coursetickets(self):
3082        # We add study level 100 to the student's studycourse
3083        studylevel = StudentStudyLevel()
3084        studylevel.level = 100
3085        studylevel.level_session = 2004
3086        self.student['studycourse'].addStudentStudyLevel(
3087            self.certificate,studylevel)
3088        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3089        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
3090        self.browser.open(course1_path)
3091        self.browser.getLink("Export student data").click()
3092        self.browser.getControl("Configure new export").click()
3093        self.browser.getControl(name="exporter").value = ['coursetickets']
3094        self.browser.getControl(name="session").value = ['2004']
3095        self.browser.getControl(name="level").value = ['100']
3096        self.browser.getControl("Create CSV file").click()
3097        # When the job is finished and we reload the page...
3098        job_id = self.wait_for_export_job_completed()
3099        self.browser.open(course1_path + '/exports')
3100        # ... the csv file can be downloaded ...
3101        self.browser.getLink("Download").click()
3102        self.assertEqual(self.browser.headers['content-type'],
3103            'text/csv; charset=UTF-8')
3104        self.assertTrue(
3105            'filename="WAeUP.Kofa_coursetickets_%s.csv' % job_id in
3106            self.browser.headers['content-disposition'])
3107        # ... and contains the course ticket COURSE1
3108        self.assertEqual(self.browser.contents,
3109            'automatic,carry_over,code,credits,dcode,fcode,level,'
3110            'level_session,mandatory,passmark,score,semester,title,'
3111            'student_id,certcode\r\n1,0,COURSE1,10,dep1,fac1,100,2004,1,40,,1,'
3112            'Unnamed Course,K1000000,CERT1\r\n')
3113
3114        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3115        job_id = self.app['datacenter'].running_exports[0][0]
3116        # Thew job can be discarded
3117        self.browser.open(course1_path + '/exports')
3118        self.browser.getControl("Discard").click()
3119        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3120        # Creation, downloading and discarding is logged
3121        logfile = os.path.join(
3122            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3123        logcontent = open(logfile).read()
3124        self.assertTrue(
3125            'zope.mgr - students.browser.CourseExportJobContainerJobConfig '
3126            '- exported: coursetickets (2004, 100, COURSE1), job_id=%s'
3127            % job_id in logcontent
3128            )
3129        self.assertTrue(
3130            'zope.mgr - students.browser.ExportJobContainerDownload '
3131            '- downloaded: WAeUP.Kofa_coursetickets_%s.csv, job_id=%s'
3132            % (job_id, job_id) in logcontent
3133            )
3134        self.assertTrue(
3135            'zope.mgr - students.browser.ExportJobContainerOverview '
3136            '- discarded: job_id=%s' % job_id in logcontent
3137            )
3138
3139    def test_export_bursary_officers(self):
3140        # Create bursary officer
3141        self.app['users'].addUser('mrbursary', 'mrbursarysecret')
3142        self.app['users']['mrbursary'].email = 'mrbursary@foo.ng'
3143        self.app['users']['mrbursary'].title = 'Carlo Pitter'
3144        prmglobal = IPrincipalRoleManager(self.app)
3145        prmglobal.assignRoleToPrincipal('waeup.BursaryOfficer', 'mrbursary')
3146        # Login as bursary officer
3147        self.browser.open(self.login_path)
3148        self.browser.getControl(name="form.login").value = 'mrbursary'
3149        self.browser.getControl(name="form.password").value = 'mrbursarysecret'
3150        self.browser.getControl("Login").click()
3151        self.assertMatches('...You logged in...', self.browser.contents)
3152        self.browser.getLink("Academics").click()
3153        self.browser.getLink("Export student data").click()
3154        self.browser.getControl("Configure new export").click()
3155        # Only the bursary exporter is available for bursary officers
3156        # not only at facultiescontainer level ...
3157        self.assertFalse('<option value="students">' in self.browser.contents)
3158        self.assertTrue('<option value="bursary">' in self.browser.contents)
3159        self.browser.getControl(name="exporter").value = ['bursary']
3160        self.browser.getControl(name="session").value = ['2004']
3161        self.browser.getControl(name="level").value = ['100']
3162        self.browser.getControl("Create CSV file").click()
3163        self.assertTrue('Export started' in self.browser.contents)
3164        # ... but also at other levels
3165        self.browser.open('http://localhost/app/faculties/fac1/dep1')
3166        self.browser.getLink("Export student data").click()
3167        self.browser.getControl("Configure new export").click()
3168        self.assertFalse('<option value="students">' in self.browser.contents)
3169        self.assertTrue('<option value="bursary">' in self.browser.contents)
3170        # Thew job can be discarded
3171        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3172        #job_id = self.app['datacenter'].running_exports[0][0]
3173        job_id = self.wait_for_export_job_completed()
3174        self.browser.open('http://localhost/app/faculties/exports')
3175        self.browser.getControl("Discard").click()
3176        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
Note: See TracBrowser for help on using the repository browser.