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

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

Adjust test.

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