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

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

Provide customizable method for disabling student payments.

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