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

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

Adding student payment tickets is logged.

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