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

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

Improve test to get a more realistic transcript.

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