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

Last change on this file since 13003 was 13003, checked in by Henrik Bettermann, 9 years ago

More docs and necessary adjustment of code.

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