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

Last change on this file since 10245 was 10178, checked in by Henrik Bettermann, 12 years ago

Add view for displaying cumulative transcript data.

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