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

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

Calculate sessional gpa.

  • Property svn:keywords set to Id
File size: 161.5 KB
Line 
1## $Id: test_browser.py 10260 2013-05-31 05:21:11Z 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]['sgpa'], 3.57)
1838        self.assertEqual(td[0][0]['level'].level, 100)
1839        self.assertEqual(td[0][0]['level'].level_session, 2005)
1840        self.assertEqual(td[0][0]['tickets_1'][0].code, 'ANYCODE')
1841        self.assertEqual(td[0][0]['tickets_1'][1].code, 'COURSE1')
1842        self.assertEqual(td[1], 3.57)
1843        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1844        self.browser.open(self.student_path + '/studycourse/transcript')
1845        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1846        self.assertTrue('Transcript' in self.browser.contents)
1847        # Officers can open the pdf transcript
1848        self.browser.open(self.student_path + '/studycourse/transcript.pdf')
1849        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1850        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1851        path = os.path.join(samples_dir(), 'transcript.pdf')
1852        open(path, 'wb').write(self.browser.contents)
1853        print "Sample PDF transcript.pdf written to %s" % path
1854
1855class StudentUITests(StudentsFullSetup):
1856    # Tests for Student class views and pages
1857
1858    def test_student_change_password(self):
1859        # Students can change the password
1860        self.student.personal_updated = datetime.utcnow()
1861        self.browser.open(self.login_path)
1862        self.browser.getControl(name="form.login").value = self.student_id
1863        self.browser.getControl(name="form.password").value = 'spwd'
1864        self.browser.getControl("Login").click()
1865        self.assertEqual(self.browser.url, self.student_path)
1866        self.assertTrue('You logged in' in self.browser.contents)
1867        # Change password
1868        self.browser.getLink("Change password").click()
1869        self.browser.getControl(name="change_password").value = 'pw'
1870        self.browser.getControl(
1871            name="change_password_repeat").value = 'pw'
1872        self.browser.getControl("Save").click()
1873        self.assertTrue('Password must have at least' in self.browser.contents)
1874        self.browser.getControl(name="change_password").value = 'new_password'
1875        self.browser.getControl(
1876            name="change_password_repeat").value = 'new_passssword'
1877        self.browser.getControl("Save").click()
1878        self.assertTrue('Passwords do not match' in self.browser.contents)
1879        self.browser.getControl(name="change_password").value = 'new_password'
1880        self.browser.getControl(
1881            name="change_password_repeat").value = 'new_password'
1882        self.browser.getControl("Save").click()
1883        self.assertTrue('Password changed' in self.browser.contents)
1884        # We are still logged in. Changing the password hasn't thrown us out.
1885        self.browser.getLink("Base Data").click()
1886        self.assertEqual(self.browser.url, self.student_path)
1887        # We can logout
1888        self.browser.getLink("Logout").click()
1889        self.assertTrue('You have been logged out' in self.browser.contents)
1890        self.assertEqual(self.browser.url, 'http://localhost/app')
1891        # We can login again with the new password
1892        self.browser.getLink("Login").click()
1893        self.browser.open(self.login_path)
1894        self.browser.getControl(name="form.login").value = self.student_id
1895        self.browser.getControl(name="form.password").value = 'new_password'
1896        self.browser.getControl("Login").click()
1897        self.assertEqual(self.browser.url, self.student_path)
1898        self.assertTrue('You logged in' in self.browser.contents)
1899        return
1900
1901    def test_setpassword(self):
1902        # Set password for first-time access
1903        student = Student()
1904        student.reg_number = u'123456'
1905        student.firstname = u'Klaus'
1906        student.lastname = u'Tester'
1907        self.app['students'].addStudent(student)
1908        setpassword_path = 'http://localhost/app/setpassword'
1909        student_path = 'http://localhost/app/students/%s' % student.student_id
1910        self.browser.open(setpassword_path)
1911        self.browser.getControl(name="ac_series").value = self.existing_pwdseries
1912        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
1913        self.browser.getControl(name="reg_number").value = '223456'
1914        self.browser.getControl("Set").click()
1915        self.assertMatches('...No student found...',
1916                           self.browser.contents)
1917        self.browser.getControl(name="reg_number").value = '123456'
1918        self.browser.getControl(name="ac_number").value = '999999'
1919        self.browser.getControl("Set").click()
1920        self.assertMatches('...Access code is invalid...',
1921                           self.browser.contents)
1922        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
1923        self.browser.getControl("Set").click()
1924        self.assertMatches('...Password has been set. Your Student Id is...',
1925                           self.browser.contents)
1926        self.browser.getControl("Set").click()
1927        self.assertMatches(
1928            '...Password has already been set. Your Student Id is...',
1929            self.browser.contents)
1930        existing_pwdpin = self.pwdpins[1]
1931        parts = existing_pwdpin.split('-')[1:]
1932        existing_pwdseries, existing_pwdnumber = parts
1933        self.browser.getControl(name="ac_series").value = existing_pwdseries
1934        self.browser.getControl(name="ac_number").value = existing_pwdnumber
1935        self.browser.getControl(name="reg_number").value = '123456'
1936        self.browser.getControl("Set").click()
1937        self.assertMatches(
1938            '...You are using the wrong Access Code...',
1939            self.browser.contents)
1940        # The student can login with the new credentials
1941        self.browser.open(self.login_path)
1942        self.browser.getControl(name="form.login").value = student.student_id
1943        self.browser.getControl(
1944            name="form.password").value = self.existing_pwdnumber
1945        self.browser.getControl("Login").click()
1946        self.assertEqual(self.browser.url, student_path)
1947        self.assertTrue('You logged in' in self.browser.contents)
1948        return
1949
1950    def test_student_login(self):
1951        # Student cant login if their password is not set
1952        self.student.password = None
1953        self.browser.open(self.login_path)
1954        self.browser.getControl(name="form.login").value = self.student_id
1955        self.browser.getControl(name="form.password").value = 'spwd'
1956        self.browser.getControl("Login").click()
1957        self.assertTrue(
1958            'You entered invalid credentials.' in self.browser.contents)
1959        # We set the password again
1960        IUserAccount(
1961            self.app['students'][self.student_id]).setPassword('spwd')
1962        # Students can't login if their account is suspended/deactivated
1963        self.student.suspended = True
1964        self.browser.open(self.login_path)
1965        self.browser.getControl(name="form.login").value = self.student_id
1966        self.browser.getControl(name="form.password").value = 'spwd'
1967        self.browser.getControl("Login").click()
1968        self.assertMatches(
1969            '...<div class="alert-message warning">'
1970            'Your account has been deactivated.</div>...', self.browser.contents)
1971        # If suspended_comment is set this message will be flashed instead
1972        self.student.suspended_comment = u'Aetsch baetsch!'
1973        self.browser.getControl(name="form.login").value = self.student_id
1974        self.browser.getControl(name="form.password").value = 'spwd'
1975        self.browser.getControl("Login").click()
1976        self.assertMatches(
1977            '...<div class="alert-message warning">Aetsch baetsch!</div>...',
1978            self.browser.contents)
1979        self.student.suspended = False
1980        # Students can't login if a temporary password has been set and
1981        # is not expired
1982        self.app['students'][self.student_id].setTempPassword(
1983            'anybody', 'temp_spwd')
1984        self.browser.open(self.login_path)
1985        self.browser.getControl(name="form.login").value = self.student_id
1986        self.browser.getControl(name="form.password").value = 'spwd'
1987        self.browser.getControl("Login").click()
1988        self.assertMatches(
1989            '...Your account has been temporarily deactivated...',
1990            self.browser.contents)
1991        # The student can login with the temporary password
1992        self.browser.open(self.login_path)
1993        self.browser.getControl(name="form.login").value = self.student_id
1994        self.browser.getControl(name="form.password").value = 'temp_spwd'
1995        self.browser.getControl("Login").click()
1996        self.assertMatches(
1997            '...You logged in...', self.browser.contents)
1998        # Student can view the base data
1999        self.browser.open(self.student_path)
2000        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2001        self.assertEqual(self.browser.url, self.student_path)
2002        # When the password expires ...
2003        delta = timedelta(minutes=11)
2004        self.app['students'][self.student_id].temp_password[
2005            'timestamp'] = datetime.utcnow() - delta
2006        self.app['students'][self.student_id]._p_changed = True
2007        # ... the student will be automatically logged out
2008        self.assertRaises(
2009            Unauthorized, self.browser.open, self.student_path)
2010        # Then the student can login with the original password
2011        self.browser.open(self.login_path)
2012        self.browser.getControl(name="form.login").value = self.student_id
2013        self.browser.getControl(name="form.password").value = 'spwd'
2014        self.browser.getControl("Login").click()
2015        self.assertMatches(
2016            '...You logged in...', self.browser.contents)
2017
2018    def test_student_clearance(self):
2019        # Student cant login if their password is not set
2020        IWorkflowInfo(self.student).fireTransition('admit')
2021        self.browser.open(self.login_path)
2022        self.browser.getControl(name="form.login").value = self.student_id
2023        self.browser.getControl(name="form.password").value = 'spwd'
2024        self.browser.getControl("Login").click()
2025        self.assertMatches(
2026            '...You logged in...', self.browser.contents)
2027        # Admitted student can upload a passport picture
2028        self.browser.open(self.student_path + '/change_portrait')
2029        ctrl = self.browser.getControl(name='passportuploadedit')
2030        file_obj = open(SAMPLE_IMAGE, 'rb')
2031        file_ctrl = ctrl.mech_control
2032        file_ctrl.add_file(file_obj, filename='my_photo.jpg')
2033        self.browser.getControl(
2034            name='upload_passportuploadedit').click()
2035        self.assertTrue(
2036            '<img align="middle" height="125px" src="passport.jpg" />'
2037            in self.browser.contents)
2038        # Students can open admission letter
2039        self.browser.getLink("Base Data").click()
2040        self.browser.getLink("Download admission letter").click()
2041        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2042        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2043        # Student can view the clearance data
2044        self.browser.open(self.student_path)
2045        self.browser.getLink("Clearance Data").click()
2046        # Student can't open clearance edit form before starting clearance
2047        self.browser.open(self.student_path + '/cedit')
2048        self.assertMatches('...The requested form is locked...',
2049                           self.browser.contents)
2050        self.browser.getLink("Clearance Data").click()
2051        self.browser.getLink("Start clearance").click()
2052        self.student.email = None
2053        # Uups, we forgot to fill the email fields
2054        self.browser.getControl("Start clearance").click()
2055        self.assertMatches('...Not all required fields filled...',
2056                           self.browser.contents)
2057        self.browser.open(self.student_path + '/edit_base')
2058        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
2059        self.browser.getControl("Save").click()
2060        self.browser.open(self.student_path + '/start_clearance')
2061        self.browser.getControl(name="ac_series").value = '3'
2062        self.browser.getControl(name="ac_number").value = '4444444'
2063        self.browser.getControl("Start clearance now").click()
2064        self.assertMatches('...Activation code is invalid...',
2065                           self.browser.contents)
2066        self.browser.getControl(name="ac_series").value = self.existing_clrseries
2067        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
2068        # Owner is Hans Wurst, AC can't be invalidated
2069        self.browser.getControl("Start clearance now").click()
2070        self.assertMatches('...You are not the owner of this access code...',
2071                           self.browser.contents)
2072        # Set the correct owner
2073        self.existing_clrac.owner = self.student_id
2074        # clr_code might be set (and thus returns None) due importing
2075        # an empty clr_code column.
2076        self.student.clr_code = None
2077        self.browser.getControl("Start clearance now").click()
2078        self.assertMatches('...Clearance process has been started...',
2079                           self.browser.contents)
2080        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
2081        self.browser.getControl("Save", index=0).click()
2082        # Student can view the clearance data
2083        self.browser.getLink("Clearance Data").click()
2084        # and go back to the edit form
2085        self.browser.getLink("Edit").click()
2086        # Students can upload documents
2087        ctrl = self.browser.getControl(name='birthcertificateupload')
2088        file_obj = open(SAMPLE_IMAGE, 'rb')
2089        file_ctrl = ctrl.mech_control
2090        file_ctrl.add_file(file_obj, filename='my_birth_certificate.jpg')
2091        self.browser.getControl(
2092            name='upload_birthcertificateupload').click()
2093        self.assertTrue(
2094            '<a target="image" href="birth_certificate">Birth Certificate Scan</a>'
2095            in self.browser.contents)
2096        # Students can open clearance slip
2097        self.browser.getLink("View").click()
2098        self.browser.getLink("Download clearance slip").click()
2099        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2100        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2101        # Students can request clearance
2102        self.browser.open(self.edit_clearance_path)
2103        self.browser.getControl("Save and request clearance").click()
2104        self.browser.getControl(name="ac_series").value = self.existing_clrseries
2105        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
2106        self.browser.getControl("Request clearance now").click()
2107        self.assertMatches('...Clearance has been requested...',
2108                           self.browser.contents)
2109        # Student can't reopen clearance form after requesting clearance
2110        self.browser.open(self.student_path + '/cedit')
2111        self.assertMatches('...The requested form is locked...',
2112                           self.browser.contents)
2113
2114    def test_student_course_registration(self):
2115        # Student cant login if their password is not set
2116        IWorkflowInfo(self.student).fireTransition('admit')
2117        self.browser.open(self.login_path)
2118        self.browser.getControl(name="form.login").value = self.student_id
2119        self.browser.getControl(name="form.password").value = 'spwd'
2120        self.browser.getControl("Login").click()
2121        # Student can't add study level if not in state 'school fee paid'
2122        self.browser.open(self.student_path + '/studycourse/add')
2123        self.assertMatches('...The requested form is locked...',
2124                           self.browser.contents)
2125        # ... and must be transferred first
2126        IWorkflowState(self.student).setState('school fee paid')
2127        # Now students can add the current study level
2128        self.browser.getLink("Study Course").click()
2129        self.browser.getLink("Add course list").click()
2130        self.assertMatches('...Add current level 100 (Year 1)...',
2131                           self.browser.contents)
2132        self.browser.getControl("Create course list now").click()
2133        # A level with one course ticket was created
2134        self.assertEqual(self.student['studycourse']['100'].number_of_tickets, 1)
2135        self.browser.getLink("100").click()
2136        self.browser.getLink("Edit course list").click()
2137        self.browser.getLink("here").click()
2138        self.browser.getControl(name="form.course").value = ['COURSE1']
2139        self.browser.getControl("Add course ticket").click()
2140        self.assertMatches('...The ticket exists...',
2141                           self.browser.contents)
2142        self.student['studycourse'].current_level = 200
2143        self.browser.getLink("Study Course").click()
2144        self.browser.getLink("Add course list").click()
2145        self.assertMatches('...Add current level 200 (Year 2)...',
2146                           self.browser.contents)
2147        self.browser.getControl("Create course list now").click()
2148        self.browser.getLink("200").click()
2149        self.browser.getLink("Edit course list").click()
2150        self.browser.getLink("here").click()
2151        self.browser.getControl(name="form.course").value = ['COURSE1']
2152        self.course.credits = 100
2153        self.browser.getControl("Add course ticket").click()
2154        self.assertMatches(
2155            '...Total credits exceed 50...', self.browser.contents)
2156        self.course.credits = 10
2157        self.browser.getControl("Add course ticket").click()
2158        self.assertMatches('...The ticket exists...',
2159                           self.browser.contents)
2160        # Indeed the ticket exists as carry-over course from level 100
2161        # since its score was 0
2162        self.assertTrue(
2163            self.student['studycourse']['200']['COURSE1'].carry_over is True)
2164        # Students can open the pdf course registration slip
2165        self.browser.open(self.student_path + '/studycourse/200')
2166        self.browser.getLink("Download course registration slip").click()
2167        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2168        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2169        # Students can remove course tickets
2170        self.browser.open(self.student_path + '/studycourse/200/edit')
2171        self.browser.getControl("Remove selected", index=0).click()
2172        self.assertTrue('No ticket selected' in self.browser.contents)
2173        # No ticket can be selected since the carry-over course is a core course
2174        self.assertRaises(
2175            LookupError, self.browser.getControl, name='val_id')
2176        self.student['studycourse']['200']['COURSE1'].mandatory = False
2177        self.browser.open(self.student_path + '/studycourse/200/edit')
2178        # Course list can't be registered if total_credits exceeds max_credits
2179        self.student['studycourse']['200']['COURSE1'].credits = 60
2180        self.browser.getControl("Register course list").click()
2181        self.assertTrue('Maximum credits of 50 exceeded' in self.browser.contents)
2182        # Student can now remove the ticket
2183        ctrl = self.browser.getControl(name='val_id')
2184        ctrl.getControl(value='COURSE1').selected = True
2185        self.browser.getControl("Remove selected", index=0).click()
2186        self.assertTrue('Successfully removed' in self.browser.contents)
2187        # Removing course tickets is properly logged
2188        logfile = os.path.join(
2189            self.app['datacenter'].storage, 'logs', 'students.log')
2190        logcontent = open(logfile).read()
2191        self.assertTrue('K1000000 - students.browser.StudyLevelEditFormPage '
2192        '- K1000000 - removed: COURSE1 at 200' in logcontent)
2193        # They can add the same ticket using the edit page directly.
2194        # We can do the same by adding the course on the manage page directly
2195        self.browser.getControl(name="course").value = 'COURSE1'
2196        self.browser.getControl("Add course ticket").click()
2197        # Adding course tickets is logged
2198        logfile = os.path.join(
2199            self.app['datacenter'].storage, 'logs', 'students.log')
2200        logcontent = open(logfile).read()
2201        self.assertTrue('K1000000 - students.browser.StudyLevelEditFormPage - '
2202            'K1000000 - added: COURSE1|200|2004' in logcontent)
2203        # Course list can be registered
2204        self.browser.getControl("Register course list").click()
2205        self.assertTrue('Course list has been registered' in self.browser.contents)
2206        self.assertEqual(self.student.state, 'courses registered')
2207        # Students can open the pdf transcript
2208        self.browser.open(self.student_path + '/studycourse/transcript.pdf')
2209        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2210        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2211        return
2212
2213    def test_postgraduate_student_access(self):
2214        self.certificate.study_mode = 'pg_ft'
2215        self.certificate.start_level = 999
2216        self.certificate.end_level = 999
2217        self.student['studycourse'].current_level = 999
2218        IWorkflowState(self.student).setState('school fee paid')
2219        self.browser.open(self.login_path)
2220        self.browser.getControl(name="form.login").value = self.student_id
2221        self.browser.getControl(name="form.password").value = 'spwd'
2222        self.browser.getControl("Login").click()
2223        self.assertTrue(
2224            'You logged in.' in self.browser.contents)
2225        # Now students can add the current study level
2226        self.browser.getLink("Study Course").click()
2227        self.browser.getLink("Add course list").click()
2228        self.assertMatches('...Add current level Postgraduate Level...',
2229                           self.browser.contents)
2230        self.browser.getControl("Create course list now").click()
2231        # A level with one course ticket was created
2232        self.assertEqual(self.student['studycourse']['999'].number_of_tickets, 0)
2233        self.browser.getLink("999").click()
2234        self.browser.getLink("Edit course list").click()
2235        self.browser.getLink("here").click()
2236        self.browser.getControl(name="form.course").value = ['COURSE1']
2237        self.browser.getControl("Add course ticket").click()
2238        self.assertMatches('...Successfully added COURSE1...',
2239                           self.browser.contents)
2240        # Postgraduate students can't register course lists
2241        self.browser.getControl("Register course list").click()
2242        self.assertTrue("your course list can't bee registered"
2243            in self.browser.contents)
2244        self.assertEqual(self.student.state, 'school fee paid')
2245        return
2246
2247    def test_student_clearance_wo_clrcode(self):
2248        IWorkflowState(self.student).setState('clearance started')
2249        self.browser.open(self.login_path)
2250        self.browser.getControl(name="form.login").value = self.student_id
2251        self.browser.getControl(name="form.password").value = 'spwd'
2252        self.browser.getControl("Login").click()
2253        self.student.clearance_locked = False
2254        self.browser.open(self.edit_clearance_path)
2255        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
2256        self.browser.getControl("Save and request clearance").click()
2257        self.assertMatches('...Clearance has been requested...',
2258                           self.browser.contents)
2259
2260    def test_student_clearance_payment(self):
2261        # Login
2262        self.browser.open(self.login_path)
2263        self.browser.getControl(name="form.login").value = self.student_id
2264        self.browser.getControl(name="form.password").value = 'spwd'
2265        self.browser.getControl("Login").click()
2266
2267        # Students can add online clearance payment tickets
2268        self.browser.open(self.payments_path + '/addop')
2269        self.browser.getControl(name="form.p_category").value = ['clearance']
2270        self.browser.getControl("Create ticket").click()
2271        self.assertMatches('...ticket created...',
2272                           self.browser.contents)
2273
2274        # Students can't approve the payment
2275        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
2276        ctrl = self.browser.getControl(name='val_id')
2277        value = ctrl.options[0]
2278        self.browser.getLink(value).click()
2279        payment_url = self.browser.url
2280        self.assertRaises(
2281            Unauthorized, self.browser.open, payment_url + '/approve')
2282        # In the base package they can 'use' a fake approval view.
2283        # XXX: I tried to use
2284        # self.student['payments'][value].approveStudentPayment() instead.
2285        # But this function fails in
2286        # w.k.accesscodes.accesscode.create_accesscode.
2287        # grok.getSite returns None in tests.
2288        self.browser.open(payment_url + '/fake_approve')
2289        self.assertMatches('...Payment approved...',
2290                          self.browser.contents)
2291        expected = '''...
2292        <td>
2293          <span>Paid</span>
2294        </td>...'''
2295        expected = '''...
2296        <td>
2297          <span>Paid</span>
2298        </td>...'''
2299        self.assertMatches(expected,self.browser.contents)
2300        payment_id = self.student['payments'].keys()[0]
2301        payment = self.student['payments'][payment_id]
2302        self.assertEqual(payment.p_state, 'paid')
2303        self.assertEqual(payment.r_amount_approved, 3456.0)
2304        self.assertEqual(payment.r_code, 'AP')
2305        self.assertEqual(payment.r_desc, u'Payment approved by Anna Tester')
2306        # The new CLR-0 pin has been created
2307        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
2308        pin = self.app['accesscodes']['CLR-0'].keys()[0]
2309        ac = self.app['accesscodes']['CLR-0'][pin]
2310        self.assertEqual(ac.owner, self.student_id)
2311        self.assertEqual(ac.cost, 3456.0)
2312
2313        # Students can open the pdf payment slip
2314        self.browser.open(payment_url + '/payment_slip.pdf')
2315        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2316        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2317
2318        # The new CLR-0 pin can be used for starting clearance
2319        # but they have to upload a passport picture first
2320        # which is only possible in state admitted
2321        self.browser.open(self.student_path + '/change_portrait')
2322        self.assertMatches('...form is locked...',
2323                          self.browser.contents)
2324        IWorkflowInfo(self.student).fireTransition('admit')
2325        self.browser.open(self.student_path + '/change_portrait')
2326        image = open(SAMPLE_IMAGE, 'rb')
2327        ctrl = self.browser.getControl(name='passportuploadedit')
2328        file_ctrl = ctrl.mech_control
2329        file_ctrl.add_file(image, filename='my_photo.jpg')
2330        self.browser.getControl(
2331            name='upload_passportuploadedit').click()
2332        self.browser.open(self.student_path + '/start_clearance')
2333        parts = pin.split('-')[1:]
2334        clrseries, clrnumber = parts
2335        self.browser.getControl(name="ac_series").value = clrseries
2336        self.browser.getControl(name="ac_number").value = clrnumber
2337        self.browser.getControl("Start clearance now").click()
2338        self.assertMatches('...Clearance process has been started...',
2339                           self.browser.contents)
2340
2341    def test_student_schoolfee_payment(self):
2342        configuration = createObject('waeup.SessionConfiguration')
2343        configuration.academic_session = 2005
2344        self.app['configuration'].addSessionConfiguration(configuration)
2345        # Login
2346        self.browser.open(self.login_path)
2347        self.browser.getControl(name="form.login").value = self.student_id
2348        self.browser.getControl(name="form.password").value = 'spwd'
2349        self.browser.getControl("Login").click()
2350
2351        # Students can add online school fee payment tickets.
2352        IWorkflowState(self.student).setState('returning')
2353        self.browser.open(self.payments_path)
2354        self.assertRaises(
2355            LookupError, self.browser.getControl, name='val_id')
2356        self.browser.getLink("Add current session payment ticket").click()
2357        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2358        self.browser.getControl("Create ticket").click()
2359        self.assertMatches('...ticket created...',
2360                           self.browser.contents)
2361        ctrl = self.browser.getControl(name='val_id')
2362        value = ctrl.options[0]
2363        self.browser.getLink(value).click()
2364        self.assertMatches('...Amount Authorized...',
2365                           self.browser.contents)
2366        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2367        # Payment session and will be calculated as defined
2368        # in w.k.students.utils because we set changed the state
2369        # to returning
2370        self.assertEqual(self.student['payments'][value].p_session, 2005)
2371        self.assertEqual(self.student['payments'][value].p_level, 200)
2372
2373        # Student is the payer of the payment ticket.
2374        payer = IPayer(self.student['payments'][value])
2375        self.assertEqual(payer.display_fullname, 'Anna Tester')
2376        self.assertEqual(payer.id, self.student_id)
2377        self.assertEqual(payer.faculty, 'fac1')
2378        self.assertEqual(payer.department, 'dep1')
2379
2380        # We simulate the approval
2381        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
2382        self.browser.open(self.browser.url + '/fake_approve')
2383        self.assertMatches('...Payment approved...',
2384                          self.browser.contents)
2385
2386        # The new SFE-0 pin can be used for starting new session
2387        self.browser.open(self.studycourse_path)
2388        self.browser.getLink('Start new session').click()
2389        pin = self.app['accesscodes']['SFE-0'].keys()[0]
2390        parts = pin.split('-')[1:]
2391        sfeseries, sfenumber = parts
2392        self.browser.getControl(name="ac_series").value = sfeseries
2393        self.browser.getControl(name="ac_number").value = sfenumber
2394        self.browser.getControl("Start now").click()
2395        self.assertMatches('...Session started...',
2396                           self.browser.contents)
2397        self.assertTrue(self.student.state == 'school fee paid')
2398        return
2399
2400    def test_student_bedallocation_payment(self):
2401        # Login
2402        self.browser.open(self.login_path)
2403        self.browser.getControl(name="form.login").value = self.student_id
2404        self.browser.getControl(name="form.password").value = 'spwd'
2405        self.browser.getControl("Login").click()
2406        self.browser.open(self.payments_path)
2407        self.browser.open(self.payments_path + '/addop')
2408        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
2409        self.browser.getControl("Create ticket").click()
2410        self.assertMatches('...ticket created...',
2411                           self.browser.contents)
2412        # Students can remove only online payment tickets which have
2413        # not received a valid callback
2414        self.browser.open(self.payments_path)
2415        ctrl = self.browser.getControl(name='val_id')
2416        value = ctrl.options[0]
2417        ctrl.getControl(value=value).selected = True
2418        self.browser.getControl("Remove selected", index=0).click()
2419        self.assertTrue('Successfully removed' in self.browser.contents)
2420
2421    def test_student_maintenance_payment(self):
2422        # Login
2423        self.browser.open(self.login_path)
2424        self.browser.getControl(name="form.login").value = self.student_id
2425        self.browser.getControl(name="form.password").value = 'spwd'
2426        self.browser.getControl("Login").click()
2427        self.browser.open(self.payments_path)
2428        self.browser.open(self.payments_path + '/addop')
2429        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
2430        self.browser.getControl("Create ticket").click()
2431        self.assertMatches('...You have not yet booked accommodation...',
2432                           self.browser.contents)
2433        # We continue this test in test_student_accommodation
2434
2435    def test_student_previous_payments(self):
2436        configuration = createObject('waeup.SessionConfiguration')
2437        configuration.academic_session = 2000
2438        configuration.clearance_fee = 3456.0
2439        configuration.booking_fee = 123.4
2440        self.app['configuration'].addSessionConfiguration(configuration)
2441        configuration2 = createObject('waeup.SessionConfiguration')
2442        configuration2.academic_session = 2003
2443        configuration2.clearance_fee = 3456.0
2444        configuration2.booking_fee = 123.4
2445        self.app['configuration'].addSessionConfiguration(configuration2)
2446        configuration3 = createObject('waeup.SessionConfiguration')
2447        configuration3.academic_session = 2005
2448        configuration3.clearance_fee = 3456.0
2449        configuration3.booking_fee = 123.4
2450        self.app['configuration'].addSessionConfiguration(configuration3)
2451        self.student['studycourse'].entry_session = 2002
2452
2453        # Login
2454        self.browser.open(self.login_path)
2455        self.browser.getControl(name="form.login").value = self.student_id
2456        self.browser.getControl(name="form.password").value = 'spwd'
2457        self.browser.getControl("Login").click()
2458
2459        # Students can add previous school fee payment tickets in any state.
2460        IWorkflowState(self.student).setState('courses registered')
2461        self.browser.open(self.payments_path)
2462        self.browser.getLink("Add previous session payment ticket").click()
2463
2464        # Previous session payment form is provided
2465        self.assertEqual(self.student.current_session, 2004)
2466        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2467        self.browser.getControl(name="form.p_session").value = ['2000']
2468        self.browser.getControl(name="form.p_level").value = ['300']
2469        self.browser.getControl("Create ticket").click()
2470        self.assertMatches('...The previous session must not fall below...',
2471                           self.browser.contents)
2472        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2473        self.browser.getControl(name="form.p_session").value = ['2005']
2474        self.browser.getControl(name="form.p_level").value = ['300']
2475        self.browser.getControl("Create ticket").click()
2476        self.assertMatches('...This is not a previous session...',
2477                           self.browser.contents)
2478        # Students can pay current session school fee.
2479        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2480        self.browser.getControl(name="form.p_session").value = ['2004']
2481        self.browser.getControl(name="form.p_level").value = ['300']
2482        self.browser.getControl("Create ticket").click()
2483        self.assertMatches('...ticket created...',
2484                           self.browser.contents)
2485        ctrl = self.browser.getControl(name='val_id')
2486        value = ctrl.options[0]
2487        self.browser.getLink(value).click()
2488        self.assertMatches('...Amount Authorized...',
2489                           self.browser.contents)
2490        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2491
2492        # Payment session is properly set
2493        self.assertEqual(self.student['payments'][value].p_session, 2004)
2494        self.assertEqual(self.student['payments'][value].p_level, 300)
2495
2496        # We simulate the approval
2497        self.browser.open(self.browser.url + '/fake_approve')
2498        self.assertMatches('...Payment approved...',
2499                          self.browser.contents)
2500
2501        # No AC has been created
2502        self.assertEqual(len(self.app['accesscodes']['SFE-0'].keys()), 0)
2503        self.assertTrue(self.student['payments'][value].ac is None)
2504
2505        # Current payment flag is set False
2506        self.assertFalse(self.student['payments'][value].p_current)
2507
2508        # Button and form are not available for students who are in
2509        # states up to cleared
2510        self.student['studycourse'].entry_session = 2004
2511        IWorkflowState(self.student).setState('cleared')
2512        self.browser.open(self.payments_path)
2513        self.assertFalse(
2514            "Add previous session payment ticket" in self.browser.contents)
2515        self.browser.open(self.payments_path + '/addpp')
2516        self.assertTrue(
2517            "No previous payment to be made" in self.browser.contents)
2518        return
2519
2520    def test_postgraduate_student_payments(self):
2521        configuration = createObject('waeup.SessionConfiguration')
2522        configuration.academic_session = 2005
2523        self.app['configuration'].addSessionConfiguration(configuration)
2524        self.certificate.study_mode = 'pg_ft'
2525        self.certificate.start_level = 999
2526        self.certificate.end_level = 999
2527        self.student['studycourse'].current_level = 999
2528        # Login
2529        self.browser.open(self.login_path)
2530        self.browser.getControl(name="form.login").value = self.student_id
2531        self.browser.getControl(name="form.password").value = 'spwd'
2532        self.browser.getControl("Login").click()
2533        # Students can add online school fee payment tickets.
2534        IWorkflowState(self.student).setState('cleared')
2535        self.browser.open(self.payments_path)
2536        self.browser.getLink("Add current session payment ticket").click()
2537        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2538        self.browser.getControl("Create ticket").click()
2539        self.assertMatches('...ticket created...',
2540                           self.browser.contents)
2541        ctrl = self.browser.getControl(name='val_id')
2542        value = ctrl.options[0]
2543        self.browser.getLink(value).click()
2544        self.assertMatches('...Amount Authorized...',
2545                           self.browser.contents)
2546        # Payment session and level are current ones.
2547        # Postgrads have to pay school_fee_1.
2548        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
2549        self.assertEqual(self.student['payments'][value].p_session, 2004)
2550        self.assertEqual(self.student['payments'][value].p_level, 999)
2551
2552        # We simulate the approval
2553        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
2554        self.browser.open(self.browser.url + '/fake_approve')
2555        self.assertMatches('...Payment approved...',
2556                          self.browser.contents)
2557
2558        # The new SFE-0 pin can be used for starting session
2559        self.browser.open(self.studycourse_path)
2560        self.browser.getLink('Start new session').click()
2561        pin = self.app['accesscodes']['SFE-0'].keys()[0]
2562        parts = pin.split('-')[1:]
2563        sfeseries, sfenumber = parts
2564        self.browser.getControl(name="ac_series").value = sfeseries
2565        self.browser.getControl(name="ac_number").value = sfenumber
2566        self.browser.getControl("Start now").click()
2567        self.assertMatches('...Session started...',
2568                           self.browser.contents)
2569        self.assertTrue(self.student.state == 'school fee paid')
2570
2571        # Postgrad students do not need to register courses the
2572        # can just pay for the next session.
2573        self.browser.open(self.payments_path)
2574        # Remove first payment to be sure that we access the right ticket
2575        del self.student['payments'][value]
2576        self.browser.getLink("Add current session payment ticket").click()
2577        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2578        self.browser.getControl("Create ticket").click()
2579        ctrl = self.browser.getControl(name='val_id')
2580        value = ctrl.options[0]
2581        self.browser.getLink(value).click()
2582        # Payment session has increased by one, payment level remains the same.
2583        # Returning Postgraduates have to pay school_fee_2.
2584        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2585        self.assertEqual(self.student['payments'][value].p_session, 2005)
2586        self.assertEqual(self.student['payments'][value].p_level, 999)
2587
2588        # Student is still in old session
2589        self.assertEqual(self.student.current_session, 2004)
2590
2591        # We do not need to pay the ticket if any other
2592        # SFE pin is provided
2593        pin_container = self.app['accesscodes']
2594        pin_container.createBatch(
2595            datetime.utcnow(), 'some_userid', 'SFE', 9.99, 1)
2596        pin = pin_container['SFE-1'].values()[0].representation
2597        sfeseries, sfenumber = pin.split('-')[1:]
2598        # The new SFE-1 pin can be used for starting new session
2599        self.browser.open(self.studycourse_path)
2600        self.browser.getLink('Start new session').click()
2601        self.browser.getControl(name="ac_series").value = sfeseries
2602        self.browser.getControl(name="ac_number").value = sfenumber
2603        self.browser.getControl("Start now").click()
2604        self.assertMatches('...Session started...',
2605                           self.browser.contents)
2606        self.assertTrue(self.student.state == 'school fee paid')
2607        # Student is in new session
2608        self.assertEqual(self.student.current_session, 2005)
2609        self.assertEqual(self.student['studycourse'].current_level, 999)
2610        return
2611
2612    def test_student_accommodation(self):
2613        # Login
2614        self.browser.open(self.login_path)
2615        self.browser.getControl(name="form.login").value = self.student_id
2616        self.browser.getControl(name="form.password").value = 'spwd'
2617        self.browser.getControl("Login").click()
2618
2619        # Students can add online booking fee payment tickets and open the
2620        # callback view (see test_manage_payments)
2621        self.browser.getLink("Payments").click()
2622        self.browser.getLink("Add current session payment ticket").click()
2623        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
2624        self.browser.getControl("Create ticket").click()
2625        ctrl = self.browser.getControl(name='val_id')
2626        value = ctrl.options[0]
2627        self.browser.getLink(value).click()
2628        self.browser.open(self.browser.url + '/fake_approve')
2629        # The new HOS-0 pin has been created
2630        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
2631        pin = self.app['accesscodes']['HOS-0'].keys()[0]
2632        ac = self.app['accesscodes']['HOS-0'][pin]
2633        parts = pin.split('-')[1:]
2634        sfeseries, sfenumber = parts
2635
2636        # Students can use HOS code and book a bed space with it ...
2637        self.browser.open(self.acco_path)
2638        # ... but not if booking period has expired ...
2639        self.app['hostels'].enddate = datetime.now(pytz.utc)
2640        self.browser.getLink("Book accommodation").click()
2641        self.assertMatches('...Outside booking period: ...',
2642                           self.browser.contents)
2643        self.app['hostels'].enddate = datetime.now(pytz.utc) + timedelta(days=10)
2644        # ... or student is not the an allowed state ...
2645        self.browser.getLink("Book accommodation").click()
2646        self.assertMatches('...You are in the wrong...',
2647                           self.browser.contents)
2648        IWorkflowInfo(self.student).fireTransition('admit')
2649        self.browser.getLink("Book accommodation").click()
2650        self.assertMatches('...Activation Code:...',
2651                           self.browser.contents)
2652        # Student can't used faked ACs ...
2653        self.browser.getControl(name="ac_series").value = u'nonsense'
2654        self.browser.getControl(name="ac_number").value = sfenumber
2655        self.browser.getControl("Create bed ticket").click()
2656        self.assertMatches('...Activation code is invalid...',
2657                           self.browser.contents)
2658        # ... or ACs owned by somebody else.
2659        ac.owner = u'Anybody'
2660        self.browser.getControl(name="ac_series").value = sfeseries
2661        self.browser.getControl(name="ac_number").value = sfenumber
2662        self.browser.getControl("Create bed ticket").click()
2663        self.assertMatches('...You are not the owner of this access code...',
2664                           self.browser.contents)
2665        ac.owner = self.student_id
2666        self.browser.getControl(name="ac_series").value = sfeseries
2667        self.browser.getControl(name="ac_number").value = sfenumber
2668        self.browser.getControl("Create bed ticket").click()
2669        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
2670                           self.browser.contents)
2671
2672        # Bed has been allocated
2673        bed = self.app['hostels']['hall-1']['hall-1_A_101_A']
2674        self.assertTrue(bed.owner == self.student_id)
2675
2676        # BedTicketAddPage is now blocked
2677        self.browser.getLink("Book accommodation").click()
2678        self.assertMatches('...You already booked a bed space...',
2679            self.browser.contents)
2680
2681        # The bed ticket displays the data correctly
2682        self.browser.open(self.acco_path + '/2004')
2683        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
2684                           self.browser.contents)
2685        self.assertMatches('...2004/2005...', self.browser.contents)
2686        self.assertMatches('...regular_male_fr...', self.browser.contents)
2687        self.assertMatches('...%s...' % pin, self.browser.contents)
2688
2689        # Students can open the pdf slip
2690        self.browser.open(self.browser.url + '/bed_allocation_slip.pdf')
2691        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2692        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2693
2694        # Students can't relocate themselves
2695        self.assertFalse('Relocate' in self.browser.contents)
2696        relocate_path = self.acco_path + '/2004/relocate'
2697        self.assertRaises(
2698            Unauthorized, self.browser.open, relocate_path)
2699
2700        # Students can't the Remove button and check boxes
2701        self.browser.open(self.acco_path)
2702        self.assertFalse('Remove' in self.browser.contents)
2703        self.assertFalse('val_id' in self.browser.contents)
2704
2705        # Students can pay maintenance fee now
2706        self.browser.open(self.payments_path)
2707        self.browser.open(self.payments_path + '/addop')
2708        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
2709        self.browser.getControl("Create ticket").click()
2710        self.assertMatches('...Payment ticket created...',
2711                           self.browser.contents)
2712        return
2713
2714    def test_change_password_request(self):
2715        self.browser.open('http://localhost/app/changepw')
2716        self.browser.getControl(name="form.identifier").value = '123'
2717        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
2718        self.browser.getControl("Send login credentials").click()
2719        self.assertTrue('An email with' in self.browser.contents)
2720
2721    def test_student_expired_personal_data(self):
2722        # Login
2723        IWorkflowState(self.student).setState('school fee paid')
2724        delta = timedelta(days=180)
2725        self.student.personal_updated = datetime.utcnow() - delta
2726        self.browser.open(self.login_path)
2727        self.browser.getControl(name="form.login").value = self.student_id
2728        self.browser.getControl(name="form.password").value = 'spwd'
2729        self.browser.getControl("Login").click()
2730        self.assertEqual(self.browser.url, self.student_path)
2731        self.assertTrue(
2732            'You logged in' in self.browser.contents)
2733        # Students don't see personal_updated field in edit form
2734        self.browser.open(self.edit_personal_path)
2735        self.assertFalse('Updated' in self.browser.contents)
2736        self.browser.open(self.personal_path)
2737        self.assertTrue('Updated' in self.browser.contents)
2738        self.browser.getLink("Logout").click()
2739        delta = timedelta(days=181)
2740        self.student.personal_updated = datetime.utcnow() - delta
2741        self.browser.open(self.login_path)
2742        self.browser.getControl(name="form.login").value = self.student_id
2743        self.browser.getControl(name="form.password").value = 'spwd'
2744        self.browser.getControl("Login").click()
2745        self.assertEqual(self.browser.url, self.edit_personal_path)
2746        self.assertTrue(
2747            'Your personal data record is outdated.' in self.browser.contents)
2748
2749    def test_setReturningData(self):
2750        utils = getUtility(IStudentsUtils)
2751        self.student['studycourse'].current_level = 600
2752        utils.setReturningData(self.student)
2753        # The new level exceeds the certificates end_level.
2754        # In this case current_level remains unchanged and no error is raised.
2755        self.assertEqual(self.student['studycourse'].current_level, 600)
2756
2757class StudentRequestPWTests(StudentsFullSetup):
2758    # Tests for student registration
2759
2760    layer = FunctionalLayer
2761
2762    def test_request_pw(self):
2763        # Student with wrong number can't be found.
2764        self.browser.open('http://localhost/app/requestpw')
2765        self.browser.getControl(name="form.firstname").value = 'Anna'
2766        self.browser.getControl(name="form.number").value = 'anynumber'
2767        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
2768        self.browser.getControl("Send login credentials").click()
2769        self.assertTrue('No student record found.'
2770            in self.browser.contents)
2771        # Anonymous is not informed that firstname verification failed.
2772        # It seems that the record doesn't exist.
2773        self.browser.open('http://localhost/app/requestpw')
2774        self.browser.getControl(name="form.firstname").value = 'Johnny'
2775        self.browser.getControl(name="form.number").value = '123'
2776        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
2777        self.browser.getControl("Send login credentials").click()
2778        self.assertTrue('No student record found.'
2779            in self.browser.contents)
2780        # Even with the correct firstname we can't register if a
2781        # password has been set and used.
2782        self.browser.getControl(name="form.firstname").value = 'Anna'
2783        self.browser.getControl(name="form.number").value = '123'
2784        self.browser.getControl("Send login credentials").click()
2785        self.assertTrue('Your password has already been set and used.'
2786            in self.browser.contents)
2787        self.browser.open('http://localhost/app/requestpw')
2788        self.app['students'][self.student_id].password = None
2789        # The firstname field, used for verification, is not case-sensitive.
2790        self.browser.getControl(name="form.firstname").value = 'aNNa'
2791        self.browser.getControl(name="form.number").value = '123'
2792        self.browser.getControl(name="form.email").value = 'new@yy.zz'
2793        self.browser.getControl("Send login credentials").click()
2794        # Yeah, we succeded ...
2795        self.assertTrue('Your password request was successful.'
2796            in self.browser.contents)
2797        # We can also use the matric_number instead.
2798        self.browser.open('http://localhost/app/requestpw')
2799        self.browser.getControl(name="form.firstname").value = 'aNNa'
2800        self.browser.getControl(name="form.number").value = '234'
2801        self.browser.getControl(name="form.email").value = 'new@yy.zz'
2802        self.browser.getControl("Send login credentials").click()
2803        self.assertTrue('Your password request was successful.'
2804            in self.browser.contents)
2805        # ... and  student can be found in the catalog via the email address
2806        cat = queryUtility(ICatalog, name='students_catalog')
2807        results = list(
2808            cat.searchResults(
2809            email=('new@yy.zz', 'new@yy.zz')))
2810        self.assertEqual(self.student,results[0])
2811        logfile = os.path.join(
2812            self.app['datacenter'].storage, 'logs', 'main.log')
2813        logcontent = open(logfile).read()
2814        self.assertTrue('zope.anybody - students.browser.StudentRequestPasswordPage - '
2815                        '234 (K1000000) - new@yy.zz' in logcontent)
2816        return
2817
2818    def test_student_locked_level_forms(self):
2819
2820        # Add two study levels, one current and one previous
2821        studylevel = createObject(u'waeup.StudentStudyLevel')
2822        studylevel.level = 100
2823        self.student['studycourse'].addStudentStudyLevel(
2824            self.certificate, studylevel)
2825        studylevel = createObject(u'waeup.StudentStudyLevel')
2826        studylevel.level = 200
2827        self.student['studycourse'].addStudentStudyLevel(
2828            self.certificate, studylevel)
2829        IWorkflowState(self.student).setState('school fee paid')
2830        self.student['studycourse'].current_level = 200
2831
2832        self.browser.open(self.login_path)
2833        self.browser.getControl(name="form.login").value = self.student_id
2834        self.browser.getControl(name="form.password").value = 'spwd'
2835        self.browser.getControl("Login").click()
2836
2837        self.browser.open(self.student_path + '/studycourse/200/edit')
2838        self.assertFalse('The requested form is locked' in self.browser.contents)
2839        self.browser.open(self.student_path + '/studycourse/100/edit')
2840        self.assertTrue('The requested form is locked' in self.browser.contents)
2841
2842        self.browser.open(self.student_path + '/studycourse/200/ctadd')
2843        self.assertFalse('The requested form is locked' in self.browser.contents)
2844        self.browser.open(self.student_path + '/studycourse/100/ctadd')
2845        self.assertTrue('The requested form is locked' in self.browser.contents)
2846
2847        IWorkflowState(self.student).setState('courses registered')
2848        self.browser.open(self.student_path + '/studycourse/200/edit')
2849        self.assertTrue('The requested form is locked' in self.browser.contents)
2850        self.browser.open(self.student_path + '/studycourse/200/ctadd')
2851        self.assertTrue('The requested form is locked' in self.browser.contents)
2852
2853
2854class PublicPagesTests(StudentsFullSetup):
2855    # Tests for simple webservices
2856
2857    layer = FunctionalLayer
2858
2859    def test_paymentrequest(self):
2860        payment = createObject('waeup.StudentOnlinePayment')
2861        payment.p_category = u'schoolfee'
2862        payment.p_session = self.student.current_session
2863        payment.p_item = u'My Certificate'
2864        payment.p_id = u'anyid'
2865        self.student['payments']['anykey'] = payment
2866        # Request information about unpaid payment ticket
2867        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
2868        self.assertEqual(self.browser.contents, '-1')
2869        # Request information about paid payment ticket
2870        payment.p_state = u'paid'
2871        notify(grok.ObjectModifiedEvent(payment))
2872        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
2873        self.assertEqual(self.browser.contents,
2874            'FULL_NAME=Anna Tester&FACULTY=fac1&DEPARTMENT=dep1'
2875            '&PAYMENT_ITEM=My Certificate&PAYMENT_CATEGORY=School Fee'
2876            '&ACADEMIC_SESSION=2004/2005&MATRIC_NUMBER=234&REG_NUMBER=123'
2877            '&FEE_AMOUNT=0.0')
2878        self.browser.open('http://localhost/app/paymentrequest?NONSENSE=nonsense')
2879        self.assertEqual(self.browser.contents, '-1')
2880        self.browser.open('http://localhost/app/paymentrequest?P_ID=nonsense')
2881        self.assertEqual(self.browser.contents, '-1')
2882
2883class StudentDataExportTests(StudentsFullSetup, FunctionalAsyncTestCase):
2884    # Tests for StudentsContainer class views and pages
2885
2886    layer = FunctionalLayer
2887
2888    def wait_for_export_job_completed(self):
2889        # helper function waiting until the current export job is completed
2890        manager = getUtility(IJobManager)
2891        job_id = self.app['datacenter'].running_exports[0][0]
2892        job = manager.get(job_id)
2893        wait_for_result(job)
2894        return job_id
2895
2896    def test_faculties_export(self):
2897        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
2898        facs_path = 'http://localhost/app/faculties'
2899        self.browser.open(facs_path)
2900        self.browser.getLink("Export student data").click()
2901        self.browser.getControl("Configure new export").click()
2902        self.browser.getControl(name="exporter").value = ['bursary']
2903        self.browser.getControl(name="session").value = ['2004']
2904        self.browser.getControl(name="level").value = ['100']
2905        self.browser.getControl(name="mode").value = ['ug_ft']
2906        self.browser.getControl("Create CSV file").click()
2907
2908        # When the job is finished and we reload the page...
2909        job_id = self.wait_for_export_job_completed()
2910        self.browser.open(facs_path + '/exports')
2911        # ... the csv file can be downloaded ...
2912        self.browser.getLink("Download").click()
2913        self.assertEqual(self.browser.headers['content-type'],
2914            'text/csv; charset=UTF-8')
2915        self.assertTrue(
2916            'filename="WAeUP.Kofa_bursary_%s.csv' % job_id in
2917            self.browser.headers['content-disposition'])
2918        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
2919        job_id = self.app['datacenter'].running_exports[0][0]
2920        # ... and discarded
2921        self.browser.open(facs_path + '/exports')
2922        self.browser.getControl("Discard").click()
2923        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
2924        # Creation, downloading and discarding is logged
2925        logfile = os.path.join(
2926            self.app['datacenter'].storage, 'logs', 'datacenter.log')
2927        logcontent = open(logfile).read()
2928        self.assertTrue(
2929            'zope.mgr - students.browser.FacultiesExportJobContainerJobConfig '
2930            '- exported: bursary (2004, 100, ug_ft, None, None), job_id=%s'
2931            % job_id in logcontent
2932            )
2933        self.assertTrue(
2934            'zope.mgr - students.browser.ExportJobContainerDownload '
2935            '- downloaded: WAeUP.Kofa_bursary_%s.csv, job_id=%s'
2936            % (job_id, job_id) in logcontent
2937            )
2938        self.assertTrue(
2939            'zope.mgr - students.browser.ExportJobContainerOverview '
2940            '- discarded: job_id=%s' % job_id in logcontent
2941            )
2942
2943    def test_department_export(self):
2944        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
2945        dep1_path = 'http://localhost/app/faculties/fac1/dep1'
2946        self.browser.open(dep1_path)
2947        self.browser.getLink("Export student data").click()
2948        self.browser.getControl("Configure new export").click()
2949        self.browser.getControl(name="exporter").value = ['students']
2950        self.browser.getControl(name="session").value = ['2004']
2951        self.browser.getControl(name="level").value = ['100']
2952        self.browser.getControl(name="mode").value = ['ug_ft']
2953        self.browser.getControl("Create CSV file").click()
2954
2955        # When the job is finished and we reload the page...
2956        job_id = self.wait_for_export_job_completed()
2957        self.browser.open(dep1_path + '/exports')
2958        # ... the csv file can be downloaded ...
2959        self.browser.getLink("Download").click()
2960        self.assertEqual(self.browser.headers['content-type'],
2961            'text/csv; charset=UTF-8')
2962        self.assertTrue(
2963            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
2964            self.browser.headers['content-disposition'])
2965        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
2966        job_id = self.app['datacenter'].running_exports[0][0]
2967        # ... and discarded
2968        self.browser.open(dep1_path + '/exports')
2969        self.browser.getControl("Discard").click()
2970        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
2971        # Creation, downloading and discarding is logged
2972        logfile = os.path.join(
2973            self.app['datacenter'].storage, 'logs', 'datacenter.log')
2974        logcontent = open(logfile).read()
2975        self.assertTrue(
2976            'zope.mgr - students.browser.DepartmentExportJobContainerJobConfig '
2977            '- exported: students (2004, 100, ug_ft, dep1, None), job_id=%s'
2978            % job_id in logcontent
2979            )
2980        self.assertTrue(
2981            'zope.mgr - students.browser.ExportJobContainerDownload '
2982            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
2983            % (job_id, job_id) in logcontent
2984            )
2985        self.assertTrue(
2986            'zope.mgr - students.browser.ExportJobContainerOverview '
2987            '- discarded: job_id=%s' % job_id in logcontent
2988            )
2989
2990    def test_certificate_export(self):
2991        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
2992        cert1_path = 'http://localhost/app/faculties/fac1/dep1/certificates/CERT1'
2993        self.browser.open(cert1_path)
2994        self.browser.getLink("Export student data").click()
2995        self.browser.getControl("Configure new export").click()
2996        self.browser.getControl(name="exporter").value = ['students']
2997        self.browser.getControl(name="session").value = ['2004']
2998        self.browser.getControl(name="level").value = ['100']
2999        self.browser.getControl("Create CSV file").click()
3000
3001        # When the job is finished and we reload the page...
3002        job_id = self.wait_for_export_job_completed()
3003        self.browser.open(cert1_path + '/exports')
3004        # ... the csv file can be downloaded ...
3005        self.browser.getLink("Download").click()
3006        self.assertEqual(self.browser.headers['content-type'],
3007            'text/csv; charset=UTF-8')
3008        self.assertTrue(
3009            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3010            self.browser.headers['content-disposition'])
3011        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3012        job_id = self.app['datacenter'].running_exports[0][0]
3013        # ... and discarded
3014        self.browser.open(cert1_path + '/exports')
3015        self.browser.getControl("Discard").click()
3016        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3017        # Creation, downloading and discarding is logged
3018        logfile = os.path.join(
3019            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3020        logcontent = open(logfile).read()
3021        self.assertTrue(
3022            'zope.mgr - students.browser.CertificateExportJobContainerJobConfig '
3023            '- exported: students (2004, 100, None, None, CERT1), job_id=%s'
3024            % job_id in logcontent
3025            )
3026        self.assertTrue(
3027            'zope.mgr - students.browser.ExportJobContainerDownload '
3028            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3029            % (job_id, job_id) in logcontent
3030            )
3031        self.assertTrue(
3032            'zope.mgr - students.browser.ExportJobContainerOverview '
3033            '- discarded: job_id=%s' % job_id in logcontent
3034            )
3035
3036    def test_course_export_students(self):
3037        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3038        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
3039        self.browser.open(course1_path)
3040        self.browser.getLink("Export student data").click()
3041        self.browser.getControl("Configure new export").click()
3042        self.browser.getControl(name="exporter").value = ['students']
3043        self.browser.getControl(name="session").value = ['2004']
3044        self.browser.getControl(name="level").value = ['100']
3045        self.browser.getControl("Create CSV file").click()
3046
3047        # When the job is finished and we reload the page...
3048        job_id = self.wait_for_export_job_completed()
3049        self.browser.open(course1_path + '/exports')
3050        # ... the csv file can be downloaded ...
3051        self.browser.getLink("Download").click()
3052        self.assertEqual(self.browser.headers['content-type'],
3053            'text/csv; charset=UTF-8')
3054        self.assertTrue(
3055            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3056            self.browser.headers['content-disposition'])
3057        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3058        job_id = self.app['datacenter'].running_exports[0][0]
3059        # ... and discarded
3060        self.browser.open(course1_path + '/exports')
3061        self.browser.getControl("Discard").click()
3062        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3063        # Creation, downloading and discarding is logged
3064        logfile = os.path.join(
3065            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3066        logcontent = open(logfile).read()
3067        self.assertTrue(
3068            'zope.mgr - students.browser.CourseExportJobContainerJobConfig '
3069            '- exported: students (2004, 100, COURSE1), job_id=%s'
3070            % job_id in logcontent
3071            )
3072        self.assertTrue(
3073            'zope.mgr - students.browser.ExportJobContainerDownload '
3074            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3075            % (job_id, job_id) in logcontent
3076            )
3077        self.assertTrue(
3078            'zope.mgr - students.browser.ExportJobContainerOverview '
3079            '- discarded: job_id=%s' % job_id in logcontent
3080            )
3081
3082    def test_course_export_coursetickets(self):
3083        # We add study level 100 to the student's studycourse
3084        studylevel = StudentStudyLevel()
3085        studylevel.level = 100
3086        studylevel.level_session = 2004
3087        self.student['studycourse'].addStudentStudyLevel(
3088            self.certificate,studylevel)
3089        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3090        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
3091        self.browser.open(course1_path)
3092        self.browser.getLink("Export student data").click()
3093        self.browser.getControl("Configure new export").click()
3094        self.browser.getControl(name="exporter").value = ['coursetickets']
3095        self.browser.getControl(name="session").value = ['2004']
3096        self.browser.getControl(name="level").value = ['100']
3097        self.browser.getControl("Create CSV file").click()
3098        # When the job is finished and we reload the page...
3099        job_id = self.wait_for_export_job_completed()
3100        self.browser.open(course1_path + '/exports')
3101        # ... the csv file can be downloaded ...
3102        self.browser.getLink("Download").click()
3103        self.assertEqual(self.browser.headers['content-type'],
3104            'text/csv; charset=UTF-8')
3105        self.assertTrue(
3106            'filename="WAeUP.Kofa_coursetickets_%s.csv' % job_id in
3107            self.browser.headers['content-disposition'])
3108        # ... and contains the course ticket COURSE1
3109        self.assertEqual(self.browser.contents,
3110            'automatic,carry_over,code,credits,dcode,fcode,level,'
3111            'level_session,mandatory,passmark,score,semester,title,'
3112            'student_id,certcode\r\n1,0,COURSE1,10,dep1,fac1,100,2004,1,40,,1,'
3113            'Unnamed Course,K1000000,CERT1\r\n')
3114
3115        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3116        job_id = self.app['datacenter'].running_exports[0][0]
3117        # Thew job can be discarded
3118        self.browser.open(course1_path + '/exports')
3119        self.browser.getControl("Discard").click()
3120        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3121        # Creation, downloading and discarding is logged
3122        logfile = os.path.join(
3123            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3124        logcontent = open(logfile).read()
3125        self.assertTrue(
3126            'zope.mgr - students.browser.CourseExportJobContainerJobConfig '
3127            '- exported: coursetickets (2004, 100, COURSE1), job_id=%s'
3128            % job_id in logcontent
3129            )
3130        self.assertTrue(
3131            'zope.mgr - students.browser.ExportJobContainerDownload '
3132            '- downloaded: WAeUP.Kofa_coursetickets_%s.csv, job_id=%s'
3133            % (job_id, job_id) in logcontent
3134            )
3135        self.assertTrue(
3136            'zope.mgr - students.browser.ExportJobContainerOverview '
3137            '- discarded: job_id=%s' % job_id in logcontent
3138            )
3139
3140    def test_export_bursary_officers(self):
3141        # Create bursary officer
3142        self.app['users'].addUser('mrbursary', 'mrbursarysecret')
3143        self.app['users']['mrbursary'].email = 'mrbursary@foo.ng'
3144        self.app['users']['mrbursary'].title = 'Carlo Pitter'
3145        prmglobal = IPrincipalRoleManager(self.app)
3146        prmglobal.assignRoleToPrincipal('waeup.BursaryOfficer', 'mrbursary')
3147        # Login as bursary officer
3148        self.browser.open(self.login_path)
3149        self.browser.getControl(name="form.login").value = 'mrbursary'
3150        self.browser.getControl(name="form.password").value = 'mrbursarysecret'
3151        self.browser.getControl("Login").click()
3152        self.assertMatches('...You logged in...', self.browser.contents)
3153        self.browser.getLink("Academics").click()
3154        self.browser.getLink("Export student data").click()
3155        self.browser.getControl("Configure new export").click()
3156        # Only the bursary exporter is available for bursary officers
3157        # not only at facultiescontainer level ...
3158        self.assertFalse('<option value="students">' in self.browser.contents)
3159        self.assertTrue('<option value="bursary">' in self.browser.contents)
3160        self.browser.getControl(name="exporter").value = ['bursary']
3161        self.browser.getControl(name="session").value = ['2004']
3162        self.browser.getControl(name="level").value = ['100']
3163        self.browser.getControl("Create CSV file").click()
3164        self.assertTrue('Export started' in self.browser.contents)
3165        # ... but also at other levels
3166        self.browser.open('http://localhost/app/faculties/fac1/dep1')
3167        self.browser.getLink("Export student data").click()
3168        self.browser.getControl("Configure new export").click()
3169        self.assertFalse('<option value="students">' in self.browser.contents)
3170        self.assertTrue('<option value="bursary">' in self.browser.contents)
3171        # Thew job can be discarded
3172        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3173        #job_id = self.app['datacenter'].running_exports[0][0]
3174        job_id = self.wait_for_export_job_completed()
3175        self.browser.open('http://localhost/app/faculties/exports')
3176        self.browser.getControl("Discard").click()
3177        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
Note: See TracBrowser for help on using the repository browser.