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

Last change on this file since 13066 was 13055, checked in by Henrik Bettermann, 10 years ago

Rename student views which are not layout-aware and thus not pages.

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