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

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

Bypass school fee payment activation code creation if student is allowed
to proceed to next session.

  • Property svn:keywords set to Id
File size: 194.1 KB
Line 
1## $Id: test_browser.py 12889 2015-04-27 08:14:31Z 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        # The course ticket can be linked with the course.
1806        self.assertEqual(
1807            self.student['studycourse']['100']['COURSE1'].course,
1808            self.course)
1809        # Lecturer can neither access the student ...
1810        self.assertRaises(
1811            Unauthorized, self.browser.open, self.student_path)
1812        # ... nor the respective course ticket since
1813        # editing course tickets by lecturers is not feasible.
1814        self.assertTrue('COURSE1' in self.student['studycourse']['100'].keys())
1815        course_ticket_path = self.student_path + '/studycourse/100/COURSE1'
1816        self.assertRaises(
1817            Unauthorized, self.browser.open, course_ticket_path)
1818        # Course results can be batch edited via the edit_courses view
1819        self.app['faculties']['fac1']['dep1'].score_editing_disabled = True
1820        self.browser.open("http://localhost/app/faculties/fac1/dep1/courses/COURSE1")
1821        self.browser.getLink("Update scores").click()
1822        self.assertTrue('Score editing disabled' in self.browser.contents)
1823        self.app['faculties']['fac1']['dep1'].score_editing_disabled = False
1824        self.browser.getLink("Update scores").click()
1825        self.assertTrue('Current academic session not set' in self.browser.contents)
1826        self.app['configuration'].current_academic_session = 2004
1827        self.browser.getLink("Update scores").click()
1828        self.assertFalse(
1829            '<input type="text" name="scores" class="span1" />'
1830            in self.browser.contents)
1831        IWorkflowState(self.student).setState('courses validated')
1832        # Student must be in state 'courses validated'
1833        self.browser.open(
1834            "http://localhost/app/faculties/fac1/dep1/courses/COURSE1/edit_scores")
1835        self.assertTrue(
1836            '<input type="text" name="scores" class="span1" />'
1837            in self.browser.contents)
1838        self.browser.getControl(name="scores", index=0).value = '55'
1839        self.browser.getControl("Update scores").click()
1840        # New score has been set
1841        self.assertEqual(
1842            self.student['studycourse']['100']['COURSE1'].score, 55)
1843        # Score editing has been logged
1844        logfile = os.path.join(
1845            self.app['datacenter'].storage, 'logs', 'students.log')
1846        logcontent = open(logfile).read()
1847        self.assertTrue('mrslecturer - students.browser.EditScoresPage - '
1848                        'K1000000 100/COURSE1 score updated (55)' in logcontent)
1849        # Non-integer scores won't be accepted
1850        self.browser.open(
1851            "http://localhost/app/faculties/fac1/dep1/courses/COURSE1/edit_scores")
1852        self.assertTrue('value="55" />' in self.browser.contents)
1853        self.browser.getControl(name="scores", index=0).value = 'abc'
1854        self.browser.getControl("Update scores").click()
1855        self.assertTrue('Error: Score(s) of Anna Tester have not be updated'
1856            in self.browser.contents)
1857        # Scores can be removed
1858        self.browser.open(
1859            "http://localhost/app/faculties/fac1/dep1/courses/COURSE1/edit_scores")
1860        self.browser.getControl(name="scores", index=0).value = ''
1861        self.browser.getControl("Update scores").click()
1862        self.assertEqual(
1863            self.student['studycourse']['100']['COURSE1'].score, None)
1864        logcontent = open(logfile).read()
1865        self.assertTrue('mrslecturer - students.browser.EditScoresPage - '
1866                        'K1000000 100/COURSE1 score updated (None)' in logcontent)
1867
1868    def test_change_current_mode(self):
1869        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1870        self.browser.open(self.clearance_path)
1871        self.assertFalse('Employer' in self.browser.contents)
1872        self.browser.open(self.manage_clearance_path)
1873        self.assertFalse('Employer' in self.browser.contents)
1874        self.student.clearance_locked = False
1875        self.browser.open(self.edit_clearance_path)
1876        self.assertFalse('Employer' in self.browser.contents)
1877        # Now we change the study mode of the certificate and a different
1878        # interface is used by clearance views.
1879        self.certificate.study_mode = 'pg_ft'
1880        # Invariants are not being checked here?!
1881        self.certificate.end_level = 100
1882        self.browser.open(self.clearance_path)
1883        self.assertTrue('Employer' in self.browser.contents)
1884        self.browser.open(self.manage_clearance_path)
1885        self.assertTrue('Employer' in self.browser.contents)
1886        self.browser.open(self.edit_clearance_path)
1887        self.assertTrue('Employer' in self.browser.contents)
1888
1889    def test_find_students_in_faculties(self):
1890        # Create local students manager in faculty
1891        self.app['users'].addUser('mrmanager', 'mrmanagersecret')
1892        self.app['users']['mrmanager'].email = 'mrmanager@foo.ng'
1893        self.app['users']['mrmanager'].title = u'Volk Wagen'
1894        # Assign LocalStudentsManager role for faculty
1895        fac = self.app['faculties']['fac1']
1896        prmlocal = IPrincipalRoleManager(fac)
1897        prmlocal.assignRoleToPrincipal(
1898            'waeup.local.LocalStudentsManager', 'mrmanager')
1899        notify(LocalRoleSetEvent(
1900            fac, 'waeup.local.LocalStudentsManager', 'mrmanager',
1901            granted=True))
1902        # Login as manager
1903        self.browser.open(self.login_path)
1904        self.browser.getControl(name="form.login").value = 'mrmanager'
1905        self.browser.getControl(name="form.password").value = 'mrmanagersecret'
1906        self.browser.getControl("Login").click()
1907        self.assertMatches('...You logged in...', self.browser.contents)
1908        # Manager can see his roles
1909        self.browser.getLink("My Roles").click()
1910        self.assertMatches(
1911            '...<span>Students Manager</span>...',
1912            self.browser.contents)
1913        # The manager can go to his faculty
1914        self.browser.getLink(
1915            "http://localhost/app/faculties/fac1").click()
1916        # and find students
1917        self.browser.getLink("Find students").click()
1918        self.browser.getControl("Find student").click()
1919        self.assertTrue('Empty search string' in self.browser.contents)
1920        self.browser.getControl(name="searchtype").value = ['student_id']
1921        self.browser.getControl(name="searchterm").value = self.student_id
1922        self.browser.getControl("Find student").click()
1923        self.assertTrue('Anna Tester' in self.browser.contents)
1924
1925    def test_activate_deactivate_buttons(self):
1926        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1927        self.browser.open(self.student_path)
1928        self.browser.getLink("Deactivate").click()
1929        self.assertTrue(
1930            'Student account has been deactivated.' in self.browser.contents)
1931        self.assertTrue(
1932            'Base Data (account deactivated)' in self.browser.contents)
1933        self.assertTrue(self.student.suspended)
1934        self.browser.getLink("Activate").click()
1935        self.assertTrue(
1936            'Student account has been activated.' in self.browser.contents)
1937        self.assertFalse(
1938            'Base Data (account deactivated)' in self.browser.contents)
1939        self.assertFalse(self.student.suspended)
1940        # History messages have been added ...
1941        self.browser.getLink("History").click()
1942        self.assertTrue(
1943            'Student account deactivated by Manager<br />' in self.browser.contents)
1944        self.assertTrue(
1945            'Student account activated by Manager<br />' in self.browser.contents)
1946        # ... and actions have been logged.
1947        logfile = os.path.join(
1948            self.app['datacenter'].storage, 'logs', 'students.log')
1949        logcontent = open(logfile).read()
1950        self.assertTrue('zope.mgr - students.browser.StudentDeactivatePage - '
1951                        'K1000000 - account deactivated' in logcontent)
1952        self.assertTrue('zope.mgr - students.browser.StudentActivatePage - '
1953                        'K1000000 - account activated' in logcontent)
1954
1955    def test_manage_student_transfer(self):
1956        # Add second certificate
1957        self.certificate2 = createObject('waeup.Certificate')
1958        self.certificate2.code = u'CERT2'
1959        self.certificate2.study_mode = 'ug_ft'
1960        self.certificate2.start_level = 999
1961        self.certificate2.end_level = 999
1962        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
1963            self.certificate2)
1964
1965        # Add study level to old study course
1966        studylevel = createObject(u'waeup.StudentStudyLevel')
1967        studylevel.level = 200
1968        self.student['studycourse'].addStudentStudyLevel(
1969            self.certificate, studylevel)
1970        studylevel = createObject(u'waeup.StudentStudyLevel')
1971        studylevel.level = 999
1972        self.student['studycourse'].addStudentStudyLevel(
1973            self.certificate, studylevel)
1974
1975        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1976        self.browser.open(self.student_path)
1977        self.browser.getLink("Transfer").click()
1978        self.browser.getControl(name="form.certificate").value = ['CERT2']
1979        self.browser.getControl(name="form.current_session").value = ['2011']
1980        self.browser.getControl(name="form.current_level").value = ['200']
1981        self.browser.getControl("Transfer").click()
1982        self.assertTrue(
1983            'Current level does not match certificate levels'
1984            in self.browser.contents)
1985        self.browser.getControl(name="form.current_level").value = ['999']
1986        self.browser.getControl("Transfer").click()
1987        self.assertTrue('Successfully transferred' in self.browser.contents)
1988        # The catalog has been updated
1989        cat = queryUtility(ICatalog, name='students_catalog')
1990        results = list(
1991            cat.searchResults(
1992            certcode=('CERT2', 'CERT2')))
1993        self.assertTrue(results[0] is self.student)
1994        results = list(
1995            cat.searchResults(
1996            current_session=(2011, 2011)))
1997        self.assertTrue(results[0] is self.student)
1998        # Add study level to new study course
1999        studylevel = createObject(u'waeup.StudentStudyLevel')
2000        studylevel.level = 999
2001        self.student['studycourse'].addStudentStudyLevel(
2002            self.certificate, studylevel)
2003
2004        # Edit and add pages are locked for old study courses
2005        self.browser.open(self.student_path + '/studycourse/manage')
2006        self.assertFalse('The requested form is locked' in self.browser.contents)
2007        self.browser.open(self.student_path + '/studycourse_1/manage')
2008        self.assertTrue('The requested form is locked' in self.browser.contents)
2009
2010        self.browser.open(self.student_path + '/studycourse/start_session')
2011        self.assertFalse('The requested form is locked' in self.browser.contents)
2012        self.browser.open(self.student_path + '/studycourse_1/start_session')
2013        self.assertTrue('The requested form is locked' in self.browser.contents)
2014
2015        IWorkflowState(self.student).setState('school fee paid')
2016        self.browser.open(self.student_path + '/studycourse/add')
2017        self.assertFalse('The requested form is locked' in self.browser.contents)
2018        self.browser.open(self.student_path + '/studycourse_1/add')
2019        self.assertTrue('The requested form is locked' in self.browser.contents)
2020
2021        self.browser.open(self.student_path + '/studycourse/999/manage')
2022        self.assertFalse('The requested form is locked' in self.browser.contents)
2023        self.browser.open(self.student_path + '/studycourse_1/999/manage')
2024        self.assertTrue('The requested form is locked' in self.browser.contents)
2025
2026        self.browser.open(self.student_path + '/studycourse/999/validate_courses')
2027        self.assertFalse('The requested form is locked' in self.browser.contents)
2028        self.browser.open(self.student_path + '/studycourse_1/999/validate_courses')
2029        self.assertTrue('The requested form is locked' in self.browser.contents)
2030
2031        self.browser.open(self.student_path + '/studycourse/999/reject_courses')
2032        self.assertFalse('The requested form is locked' in self.browser.contents)
2033        self.browser.open(self.student_path + '/studycourse_1/999/reject_courses')
2034        self.assertTrue('The requested form is locked' in self.browser.contents)
2035
2036        self.browser.open(self.student_path + '/studycourse/999/add')
2037        self.assertFalse('The requested form is locked' in self.browser.contents)
2038        self.browser.open(self.student_path + '/studycourse_1/999/add')
2039        self.assertTrue('The requested form is locked' in self.browser.contents)
2040
2041        self.browser.open(self.student_path + '/studycourse/999/edit')
2042        self.assertFalse('The requested form is locked' in self.browser.contents)
2043        self.browser.open(self.student_path + '/studycourse_1/999/edit')
2044        self.assertTrue('The requested form is locked' in self.browser.contents)
2045
2046        # Revert transfer
2047        self.browser.open(self.student_path + '/studycourse_1')
2048        self.browser.getLink("Reactivate").click()
2049        self.browser.getControl("Revert now").click()
2050        self.assertTrue('Previous transfer reverted' in self.browser.contents)
2051        results = list(
2052            cat.searchResults(
2053            certcode=('CERT1', 'CERT1')))
2054        self.assertTrue(results[0] is self.student)
2055        self.assertEqual([i for i in self.student.keys()],
2056            [u'accommodation', u'payments', u'studycourse'])
2057
2058    def test_login_as_student(self):
2059        # StudentImpersonators can login as student
2060        # Create clearance officer
2061        self.app['users'].addUser('mrofficer', 'mrofficersecret')
2062        self.app['users']['mrofficer'].email = 'mrofficer@foo.ng'
2063        self.app['users']['mrofficer'].title = 'Harry Actor'
2064        prmglobal = IPrincipalRoleManager(self.app)
2065        prmglobal.assignRoleToPrincipal('waeup.StudentImpersonator', 'mrofficer')
2066        prmglobal.assignRoleToPrincipal('waeup.StudentsManager', 'mrofficer')
2067        # Login as student impersonator
2068        self.browser.open(self.login_path)
2069        self.browser.getControl(name="form.login").value = 'mrofficer'
2070        self.browser.getControl(name="form.password").value = 'mrofficersecret'
2071        self.browser.getControl("Login").click()
2072        self.assertMatches('...You logged in...', self.browser.contents)
2073        self.browser.open(self.student_path)
2074        self.browser.getLink("Login as").click()
2075        self.browser.getControl("Set password now").click()
2076        temp_password = self.browser.getControl(name='form.password').value
2077        self.browser.getControl("Login now").click()
2078        self.assertMatches(
2079            '...You successfully logged in as...', self.browser.contents)
2080        # We are logged in as student and can see the 'My Data' tab
2081        self.assertMatches(
2082            '...<a href="#" class="dropdown-toggle" data-toggle="dropdown">...',
2083            self.browser.contents)
2084        self.assertMatches(
2085            '...My Data...',
2086            self.browser.contents)
2087        self.browser.getLink("Logout").click()
2088        # The student can't login with the original password ...
2089        self.browser.open(self.login_path)
2090        self.browser.getControl(name="form.login").value = self.student_id
2091        self.browser.getControl(name="form.password").value = 'spwd'
2092        self.browser.getControl("Login").click()
2093        self.assertMatches(
2094            '...Your account has been temporarily deactivated...',
2095            self.browser.contents)
2096        # ... but with the temporary password
2097        self.browser.open(self.login_path)
2098        self.browser.getControl(name="form.login").value = self.student_id
2099        self.browser.getControl(name="form.password").value = temp_password
2100        self.browser.getControl("Login").click()
2101        self.assertMatches('...You logged in...', self.browser.contents)
2102        # Creation of temp_password is properly logged
2103        logfile = os.path.join(
2104            self.app['datacenter'].storage, 'logs', 'students.log')
2105        logcontent = open(logfile).read()
2106        self.assertTrue(
2107            'mrofficer - students.browser.LoginAsStudentStep1 - K1000000 - '
2108            'temp_password generated: %s' % temp_password in logcontent)
2109
2110    def test_transcripts(self):
2111        studylevel = createObject(u'waeup.StudentStudyLevel')
2112        studylevel.level = 100
2113        studylevel.level_session = 2005
2114        self.student['studycourse'].entry_mode = 'ug_ft'
2115        self.student['studycourse'].addStudentStudyLevel(
2116            self.certificate, studylevel)
2117        studylevel2 = createObject(u'waeup.StudentStudyLevel')
2118        studylevel2.level = 110
2119        studylevel2.level_session = 2006
2120        self.student['studycourse'].addStudentStudyLevel(
2121            self.certificate, studylevel2)
2122        # Add second course (COURSE has been added automatically)
2123        courseticket = createObject('waeup.CourseTicket')
2124        courseticket.code = 'ANYCODE'
2125        courseticket.title = u'Any TITLE'
2126        courseticket.credits = 13
2127        courseticket.score = 66
2128        courseticket.semester = 1
2129        courseticket.dcode = u'ANYDCODE'
2130        courseticket.fcode = u'ANYFCODE'
2131        self.student['studycourse']['110']['COURSE2'] = courseticket
2132        self.student['studycourse']['100']['COURSE1'].score = 55
2133        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[0], 3.0)
2134        self.assertEqual(self.student['studycourse']['110'].gpa_params_rectified[0], 4.0)
2135        # Get transcript data
2136        td = self.student['studycourse'].getTranscriptData()
2137        self.assertEqual(td[0][0]['level_key'], '100')
2138        self.assertEqual(td[0][0]['sgpa'], 3.0)
2139        self.assertEqual(td[0][0]['level'].level, 100)
2140        self.assertEqual(td[0][0]['level'].level_session, 2005)
2141        self.assertEqual(td[0][0]['tickets_1'][0].code, 'COURSE1')
2142        self.assertEqual(td[0][1]['level_key'], '110')
2143        self.assertEqual(td[0][1]['sgpa'], 4.0)
2144        self.assertEqual(td[0][1]['level'].level, 110)
2145        self.assertEqual(td[0][1]['level'].level_session, 2006)
2146        self.assertEqual(td[0][1]['tickets_1'][0].code, 'ANYCODE')
2147        self.assertEqual(td[1], 3.57)
2148        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
2149        self.browser.open(self.student_path + '/studycourse/transcript')
2150        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2151        self.assertTrue('Transcript' in self.browser.contents)
2152        # Officers can open the pdf transcript
2153        self.browser.open(self.student_path + '/studycourse/transcript.pdf')
2154        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2155        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2156        path = os.path.join(samples_dir(), 'transcript.pdf')
2157        open(path, 'wb').write(self.browser.contents)
2158        print "Sample PDF transcript.pdf written to %s" % path
2159
2160    def test_process_transcript_request(self):
2161        IWorkflowState(self.student).setState('transcript requested')
2162        notify(grok.ObjectModifiedEvent(self.student))
2163        self.student.transcript_comment = (
2164            u'On 07/08/2013 08:59:54 UTC K1000000 wrote:\n\nComment line 1 \n'
2165            'Comment line2\n\nDispatch Address:\nAddress line 1 \n'
2166            'Address line2\n\n')
2167        # Create transcript officer
2168        self.app['users'].addUser('mrtranscript', 'mrtranscriptsecret')
2169        self.app['users']['mrtranscript'].email = 'mrtranscript@foo.ng'
2170        self.app['users']['mrtranscript'].title = 'Ruth Gordon'
2171        prmglobal = IPrincipalRoleManager(self.app)
2172        prmglobal.assignRoleToPrincipal('waeup.TranscriptOfficer', 'mrtranscript')
2173        # Login as transcript officer
2174        self.browser.open(self.login_path)
2175        self.browser.getControl(name="form.login").value = 'mrtranscript'
2176        self.browser.getControl(name="form.password").value = 'mrtranscriptsecret'
2177        self.browser.getControl("Login").click()
2178        self.assertMatches('...You logged in...', self.browser.contents)
2179        # Officer can see his roles
2180        self.browser.getLink("My Roles").click()
2181        self.assertMatches(
2182            '...<div>Transcript Officer</div>...',
2183            self.browser.contents)
2184        # Officer can search for students in state 'transcripr requested'
2185        self.browser.open(self.container_path)
2186        self.browser.getControl(name="searchtype").value = ['transcript']
2187        self.browser.getControl("Find student(s)").click()
2188        self.assertTrue('Anna Tester' in self.browser.contents)
2189        self.browser.getLink("K1000000").click()
2190        self.browser.getLink("Manage transcript request").click()
2191        self.assertTrue(' UTC K1000000 wrote:<br><br>Comment line 1 <br>'
2192        'Comment line2<br><br>Dispatch Address:<br>Address line 1 <br>'
2193        'Address line2<br><br></p>' in self.browser.contents)
2194        self.browser.getControl(name="comment").value = (
2195            'Hello,\nYour transcript has been sent to the address provided.')
2196        self.browser.getControl("Save comment and mark as processed").click()
2197        self.assertTrue(
2198            'UTC mrtranscript wrote:\n\nHello,\nYour transcript has '
2199            'been sent to the address provided.\n\n'
2200            in self.student.transcript_comment)
2201        # The comment has been logged
2202        logfile = os.path.join(
2203            self.app['datacenter'].storage, 'logs', 'students.log')
2204        logcontent = open(logfile).read()
2205        self.assertTrue(
2206            'mrtranscript - students.browser.StudentTranscriptRequestProcessFormPage - '
2207            'K1000000 - comment: Hello,<br>'
2208            'Your transcript has been sent to the address provided'
2209            in logcontent)
2210
2211class StudentUITests(StudentsFullSetup):
2212    # Tests for Student class views and pages
2213
2214    def test_student_change_password(self):
2215        # Students can change the password
2216        self.student.personal_updated = datetime.utcnow()
2217        self.browser.open(self.login_path)
2218        self.browser.getControl(name="form.login").value = self.student_id
2219        self.browser.getControl(name="form.password").value = 'spwd'
2220        self.browser.getControl("Login").click()
2221        self.assertEqual(self.browser.url, self.student_path)
2222        self.assertTrue('You logged in' in self.browser.contents)
2223        # Change password
2224        self.browser.getLink("Change password").click()
2225        self.browser.getControl(name="change_password").value = 'pw'
2226        self.browser.getControl(
2227            name="change_password_repeat").value = 'pw'
2228        self.browser.getControl("Save").click()
2229        self.assertTrue('Password must have at least' in self.browser.contents)
2230        self.browser.getControl(name="change_password").value = 'new_password'
2231        self.browser.getControl(
2232            name="change_password_repeat").value = 'new_passssword'
2233        self.browser.getControl("Save").click()
2234        self.assertTrue('Passwords do not match' in self.browser.contents)
2235        self.browser.getControl(name="change_password").value = 'new_password'
2236        self.browser.getControl(
2237            name="change_password_repeat").value = 'new_password'
2238        self.browser.getControl("Save").click()
2239        self.assertTrue('Password changed' in self.browser.contents)
2240        # We are still logged in. Changing the password hasn't thrown us out.
2241        self.browser.getLink("Base Data").click()
2242        self.assertEqual(self.browser.url, self.student_path)
2243        # We can logout
2244        self.browser.getLink("Logout").click()
2245        self.assertTrue('You have been logged out' in self.browser.contents)
2246        self.assertEqual(self.browser.url, 'http://localhost/app/index')
2247        # We can login again with the new password
2248        self.browser.getLink("Login").click()
2249        self.browser.open(self.login_path)
2250        self.browser.getControl(name="form.login").value = self.student_id
2251        self.browser.getControl(name="form.password").value = 'new_password'
2252        self.browser.getControl("Login").click()
2253        self.assertEqual(self.browser.url, self.student_path)
2254        self.assertTrue('You logged in' in self.browser.contents)
2255        return
2256
2257    def test_setpassword(self):
2258        # Set password for first-time access
2259        student = Student()
2260        student.reg_number = u'123456'
2261        student.firstname = u'Klaus'
2262        student.lastname = u'Tester'
2263        self.app['students'].addStudent(student)
2264        setpassword_path = 'http://localhost/app/setpassword'
2265        student_path = 'http://localhost/app/students/%s' % student.student_id
2266        self.browser.open(setpassword_path)
2267        self.browser.getControl(name="ac_series").value = self.existing_pwdseries
2268        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
2269        self.browser.getControl(name="reg_number").value = '223456'
2270        self.browser.getControl("Set").click()
2271        self.assertMatches('...No student found...',
2272                           self.browser.contents)
2273        self.browser.getControl(name="reg_number").value = '123456'
2274        self.browser.getControl(name="ac_number").value = '999999'
2275        self.browser.getControl("Set").click()
2276        self.assertMatches('...Access code is invalid...',
2277                           self.browser.contents)
2278        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
2279        self.browser.getControl("Set").click()
2280        self.assertMatches('...Password has been set. Your Student Id is...',
2281                           self.browser.contents)
2282        self.browser.getControl("Set").click()
2283        self.assertMatches(
2284            '...Password has already been set. Your Student Id is...',
2285            self.browser.contents)
2286        existing_pwdpin = self.pwdpins[1]
2287        parts = existing_pwdpin.split('-')[1:]
2288        existing_pwdseries, existing_pwdnumber = parts
2289        self.browser.getControl(name="ac_series").value = existing_pwdseries
2290        self.browser.getControl(name="ac_number").value = existing_pwdnumber
2291        self.browser.getControl(name="reg_number").value = '123456'
2292        self.browser.getControl("Set").click()
2293        self.assertMatches(
2294            '...You are using the wrong Access Code...',
2295            self.browser.contents)
2296        # The student can login with the new credentials
2297        self.browser.open(self.login_path)
2298        self.browser.getControl(name="form.login").value = student.student_id
2299        self.browser.getControl(
2300            name="form.password").value = self.existing_pwdnumber
2301        self.browser.getControl("Login").click()
2302        self.assertEqual(self.browser.url, student_path)
2303        self.assertTrue('You logged in' in self.browser.contents)
2304        return
2305
2306    def test_student_login(self):
2307        # Student cant login if their password is not set
2308        self.student.password = None
2309        self.browser.open(self.login_path)
2310        self.browser.getControl(name="form.login").value = self.student_id
2311        self.browser.getControl(name="form.password").value = 'spwd'
2312        self.browser.getControl("Login").click()
2313        self.assertTrue(
2314            'You entered invalid credentials.' in self.browser.contents)
2315        # We set the password again
2316        IUserAccount(
2317            self.app['students'][self.student_id]).setPassword('spwd')
2318        # Students can't login if their account is suspended/deactivated
2319        self.student.suspended = True
2320        self.browser.open(self.login_path)
2321        self.browser.getControl(name="form.login").value = self.student_id
2322        self.browser.getControl(name="form.password").value = 'spwd'
2323        self.browser.getControl("Login").click()
2324        self.assertMatches(
2325            '...<div class="alert alert-warning">'
2326            'Your account has been deactivated.</div>...', self.browser.contents)
2327        # If suspended_comment is set this message will be flashed instead
2328        self.student.suspended_comment = u'Aetsch baetsch!'
2329        self.browser.getControl(name="form.login").value = self.student_id
2330        self.browser.getControl(name="form.password").value = 'spwd'
2331        self.browser.getControl("Login").click()
2332        self.assertMatches(
2333            '...<div class="alert alert-warning">Aetsch baetsch!</div>...',
2334            self.browser.contents)
2335        self.student.suspended = False
2336        # Students can't login if a temporary password has been set and
2337        # is not expired
2338        self.app['students'][self.student_id].setTempPassword(
2339            'anybody', 'temp_spwd')
2340        self.browser.open(self.login_path)
2341        self.browser.getControl(name="form.login").value = self.student_id
2342        self.browser.getControl(name="form.password").value = 'spwd'
2343        self.browser.getControl("Login").click()
2344        self.assertMatches(
2345            '...Your account has been temporarily deactivated...',
2346            self.browser.contents)
2347        # The student can login with the temporary password
2348        self.browser.open(self.login_path)
2349        self.browser.getControl(name="form.login").value = self.student_id
2350        self.browser.getControl(name="form.password").value = 'temp_spwd'
2351        self.browser.getControl("Login").click()
2352        self.assertMatches(
2353            '...You logged in...', self.browser.contents)
2354        # Student can view the base data
2355        self.browser.open(self.student_path)
2356        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2357        self.assertEqual(self.browser.url, self.student_path)
2358        # When the password expires ...
2359        delta = timedelta(minutes=11)
2360        self.app['students'][self.student_id].temp_password[
2361            'timestamp'] = datetime.utcnow() - delta
2362        self.app['students'][self.student_id]._p_changed = True
2363        # ... the student will be automatically logged out
2364        self.assertRaises(
2365            Unauthorized, self.browser.open, self.student_path)
2366        # Then the student can login with the original password
2367        self.browser.open(self.login_path)
2368        self.browser.getControl(name="form.login").value = self.student_id
2369        self.browser.getControl(name="form.password").value = 'spwd'
2370        self.browser.getControl("Login").click()
2371        self.assertMatches(
2372            '...You logged in...', self.browser.contents)
2373
2374    def test_student_clearance(self):
2375        # Student cant login if their password is not set
2376        IWorkflowInfo(self.student).fireTransition('admit')
2377        self.browser.open(self.login_path)
2378        self.browser.getControl(name="form.login").value = self.student_id
2379        self.browser.getControl(name="form.password").value = 'spwd'
2380        self.browser.getControl("Login").click()
2381        self.assertMatches(
2382            '...You logged in...', self.browser.contents)
2383        # Admitted student can upload a passport picture
2384        self.browser.open(self.student_path + '/change_portrait')
2385        ctrl = self.browser.getControl(name='passportuploadedit')
2386        file_obj = open(SAMPLE_IMAGE, 'rb')
2387        file_ctrl = ctrl.mech_control
2388        file_ctrl.add_file(file_obj, filename='my_photo.jpg')
2389        self.browser.getControl(
2390            name='upload_passportuploadedit').click()
2391        self.assertTrue(
2392            'src="http://localhost/app/students/K1000000/passport.jpg"'
2393            in self.browser.contents)
2394        # Students can open admission letter
2395        self.browser.getLink("Base Data").click()
2396        self.browser.getLink("Download admission letter").click()
2397        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2398        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2399        # Student can view the clearance data
2400        self.browser.open(self.student_path)
2401        self.browser.getLink("Clearance Data").click()
2402        # Student can't open clearance edit form before starting clearance
2403        self.browser.open(self.student_path + '/cedit')
2404        self.assertMatches('...The requested form is locked...',
2405                           self.browser.contents)
2406        self.browser.getLink("Clearance Data").click()
2407        self.browser.getLink("Start clearance").click()
2408        self.student.email = None
2409        # Uups, we forgot to fill the email fields
2410        self.browser.getControl("Start clearance").click()
2411        self.assertMatches('...Not all required fields filled...',
2412                           self.browser.contents)
2413        self.browser.open(self.student_path + '/edit_base')
2414        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
2415        self.browser.getControl("Save").click()
2416        self.browser.open(self.student_path + '/start_clearance')
2417        self.browser.getControl(name="ac_series").value = '3'
2418        self.browser.getControl(name="ac_number").value = '4444444'
2419        self.browser.getControl("Start clearance now").click()
2420        self.assertMatches('...Activation code is invalid...',
2421                           self.browser.contents)
2422        self.browser.getControl(name="ac_series").value = self.existing_clrseries
2423        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
2424        # Owner is Hans Wurst, AC can't be invalidated
2425        self.browser.getControl("Start clearance now").click()
2426        self.assertMatches('...You are not the owner of this access code...',
2427                           self.browser.contents)
2428        # Set the correct owner
2429        self.existing_clrac.owner = self.student_id
2430        # clr_code might be set (and thus returns None) due importing
2431        # an empty clr_code column.
2432        self.student.clr_code = None
2433        self.browser.getControl("Start clearance now").click()
2434        self.assertMatches('...Clearance process has been started...',
2435                           self.browser.contents)
2436        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
2437        self.browser.getControl("Save", index=0).click()
2438        # Student can view the clearance data
2439        self.browser.getLink("Clearance Data").click()
2440        # and go back to the edit form
2441        self.browser.getLink("Edit").click()
2442        # Students can upload documents
2443        ctrl = self.browser.getControl(name='birthcertificateupload')
2444        file_obj = open(SAMPLE_IMAGE, 'rb')
2445        file_ctrl = ctrl.mech_control
2446        file_ctrl.add_file(file_obj, filename='my_birth_certificate.jpg')
2447        self.browser.getControl(
2448            name='upload_birthcertificateupload').click()
2449        self.assertTrue(
2450            'href="http://localhost/app/students/K1000000/birth_certificate"'
2451            in self.browser.contents)
2452        # Students can open clearance slip
2453        self.browser.getLink("View").click()
2454        self.browser.getLink("Download clearance slip").click()
2455        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2456        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2457        # Students can request clearance
2458        self.browser.open(self.edit_clearance_path)
2459        self.browser.getControl("Save and request clearance").click()
2460        self.browser.getControl(name="ac_series").value = self.existing_clrseries
2461        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
2462        self.browser.getControl("Request clearance now").click()
2463        self.assertMatches('...Clearance has been requested...',
2464                           self.browser.contents)
2465        # Student can't reopen clearance form after requesting clearance
2466        self.browser.open(self.student_path + '/cedit')
2467        self.assertMatches('...The requested form is locked...',
2468                           self.browser.contents)
2469
2470    def test_student_course_registration(self):
2471        # Student cant login if their password is not set
2472        IWorkflowInfo(self.student).fireTransition('admit')
2473        self.browser.open(self.login_path)
2474        self.browser.getControl(name="form.login").value = self.student_id
2475        self.browser.getControl(name="form.password").value = 'spwd'
2476        self.browser.getControl("Login").click()
2477        # Student can't add study level if not in state 'school fee paid'
2478        self.browser.open(self.student_path + '/studycourse/add')
2479        self.assertMatches('...The requested form is locked...',
2480                           self.browser.contents)
2481        # ... and must be transferred first
2482        IWorkflowState(self.student).setState('school fee paid')
2483        # Now students can add the current study level
2484        self.browser.getLink("Study Course").click()
2485        self.student['studycourse'].current_level = None
2486        self.browser.getLink("Add course list").click()
2487        self.assertMatches('...Your data are incomplete...',
2488                           self.browser.contents)
2489        self.student['studycourse'].current_level = 100
2490        self.browser.getLink("Add course list").click()
2491        self.assertMatches('...Add current level 100 (Year 1)...',
2492                           self.browser.contents)
2493        self.browser.getControl("Create course list now").click()
2494        # A level with one course ticket was created
2495        self.assertEqual(self.student['studycourse']['100'].number_of_tickets, 1)
2496        self.browser.getLink("100").click()
2497        self.browser.getLink("Edit course list").click()
2498        self.browser.getLink("here").click()
2499        self.browser.getControl(name="form.course").value = ['COURSE1']
2500        self.browser.getControl("Add course ticket").click()
2501        self.assertMatches('...The ticket exists...',
2502                           self.browser.contents)
2503        self.student['studycourse'].current_level = 200
2504        self.browser.getLink("Study Course").click()
2505        self.browser.getLink("Add course list").click()
2506        self.assertMatches('...Add current level 200 (Year 2)...',
2507                           self.browser.contents)
2508        self.browser.getControl("Create course list now").click()
2509        self.browser.getLink("200").click()
2510        self.browser.getLink("Edit course list").click()
2511        self.browser.getLink("here").click()
2512        self.browser.getControl(name="form.course").value = ['COURSE1']
2513        self.course.credits = 100
2514        self.browser.getControl("Add course ticket").click()
2515        self.assertMatches(
2516            '...Total credits exceed 50...', self.browser.contents)
2517        self.course.credits = 10
2518        self.browser.getControl("Add course ticket").click()
2519        self.assertMatches('...The ticket exists...',
2520                           self.browser.contents)
2521        # Indeed the ticket exists as carry-over course from level 100
2522        # since its score was 0
2523        self.assertTrue(
2524            self.student['studycourse']['200']['COURSE1'].carry_over is True)
2525        # Students can open the pdf course registration slip
2526        self.browser.open(self.student_path + '/studycourse/200')
2527        self.browser.getLink("Download course registration slip").click()
2528        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2529        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2530        # Students can remove course tickets
2531        self.browser.open(self.student_path + '/studycourse/200/edit')
2532        self.browser.getControl("Remove selected", index=0).click()
2533        self.assertTrue('No ticket selected' in self.browser.contents)
2534        # No ticket can be selected since the carry-over course is a core course
2535        self.assertRaises(
2536            LookupError, self.browser.getControl, name='val_id')
2537        self.student['studycourse']['200']['COURSE1'].mandatory = False
2538        self.browser.open(self.student_path + '/studycourse/200/edit')
2539        # Course list can't be registered if total_credits exceeds max_credits
2540        self.student['studycourse']['200']['COURSE1'].credits = 60
2541        self.browser.getControl("Register course list").click()
2542        self.assertTrue('Maximum credits of 50 exceeded' in self.browser.contents)
2543        # Student can now remove the ticket
2544        ctrl = self.browser.getControl(name='val_id')
2545        ctrl.getControl(value='COURSE1').selected = True
2546        self.browser.getControl("Remove selected", index=0).click()
2547        self.assertTrue('Successfully removed' in self.browser.contents)
2548        # Removing course tickets is properly logged
2549        logfile = os.path.join(
2550            self.app['datacenter'].storage, 'logs', 'students.log')
2551        logcontent = open(logfile).read()
2552        self.assertTrue('K1000000 - students.browser.StudyLevelEditFormPage '
2553        '- K1000000 - removed: COURSE1 at 200' in logcontent)
2554        # They can add the same ticket using the edit page directly.
2555        # We can do the same by adding the course on the manage page directly
2556        self.browser.getControl(name="course").value = 'COURSE1'
2557        self.browser.getControl("Add course ticket").click()
2558        # Adding course tickets is logged
2559        logfile = os.path.join(
2560            self.app['datacenter'].storage, 'logs', 'students.log')
2561        logcontent = open(logfile).read()
2562        self.assertTrue('K1000000 - students.browser.StudyLevelEditFormPage - '
2563            'K1000000 - added: COURSE1|200|2004' in logcontent)
2564        # Course list can be registered
2565        self.browser.getControl("Register course list").click()
2566        self.assertTrue('Course list has been registered' in self.browser.contents)
2567        self.assertEqual(self.student.state, 'courses registered')
2568        # Students can view the transcript
2569        #self.browser.open(self.studycourse_path)
2570        #self.browser.getLink("Transcript").click()
2571        #self.browser.getLink("Academic Transcript").click()
2572        #self.assertEqual(self.browser.headers['Status'], '200 Ok')
2573        #self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2574        return
2575
2576    def test_postgraduate_student_access(self):
2577        self.certificate.study_mode = 'pg_ft'
2578        self.certificate.start_level = 999
2579        self.certificate.end_level = 999
2580        self.student['studycourse'].current_level = 999
2581        IWorkflowState(self.student).setState('school fee paid')
2582        self.browser.open(self.login_path)
2583        self.browser.getControl(name="form.login").value = self.student_id
2584        self.browser.getControl(name="form.password").value = 'spwd'
2585        self.browser.getControl("Login").click()
2586        self.assertTrue(
2587            'You logged in.' in self.browser.contents)
2588        # Now students can add the current study level
2589        self.browser.getLink("Study Course").click()
2590        self.browser.getLink("Add course list").click()
2591        self.assertMatches('...Add current level Postgraduate Level...',
2592                           self.browser.contents)
2593        self.browser.getControl("Create course list now").click()
2594        # A level with one course ticket was created
2595        self.assertEqual(self.student['studycourse']['999'].number_of_tickets, 0)
2596        self.browser.getLink("999").click()
2597        self.browser.getLink("Edit course list").click()
2598        self.browser.getLink("here").click()
2599        self.browser.getControl(name="form.course").value = ['COURSE1']
2600        self.browser.getControl("Add course ticket").click()
2601        self.assertMatches('...Successfully added COURSE1...',
2602                           self.browser.contents)
2603        # Postgraduate students can't register course lists
2604        self.browser.getControl("Register course list").click()
2605        self.assertTrue("your course list can't bee registered"
2606            in self.browser.contents)
2607        self.assertEqual(self.student.state, 'school fee paid')
2608        return
2609
2610    def test_student_clearance_wo_clrcode(self):
2611        IWorkflowState(self.student).setState('clearance started')
2612        self.browser.open(self.login_path)
2613        self.browser.getControl(name="form.login").value = self.student_id
2614        self.browser.getControl(name="form.password").value = 'spwd'
2615        self.browser.getControl("Login").click()
2616        self.student.clearance_locked = False
2617        self.browser.open(self.edit_clearance_path)
2618        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
2619        self.browser.getControl("Save and request clearance").click()
2620        self.assertMatches('...Clearance has been requested...',
2621                           self.browser.contents)
2622
2623    def test_student_clearance_payment(self):
2624        # Login
2625        self.browser.open(self.login_path)
2626        self.browser.getControl(name="form.login").value = self.student_id
2627        self.browser.getControl(name="form.password").value = 'spwd'
2628        self.browser.getControl("Login").click()
2629
2630        # Students can add online clearance payment tickets
2631        self.browser.open(self.payments_path + '/addop')
2632        self.browser.getControl(name="form.p_category").value = ['clearance']
2633        self.browser.getControl("Create ticket").click()
2634        self.assertMatches('...ticket created...',
2635                           self.browser.contents)
2636
2637        # Students can't approve the payment
2638        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
2639        ctrl = self.browser.getControl(name='val_id')
2640        value = ctrl.options[0]
2641        self.browser.getLink(value).click()
2642        payment_url = self.browser.url
2643        self.assertRaises(
2644            Unauthorized, self.browser.open, payment_url + '/approve')
2645        # In the base package they can 'use' a fake approval view.
2646        # XXX: I tried to use
2647        # self.student['payments'][value].approveStudentPayment() instead.
2648        # But this function fails in
2649        # w.k.accesscodes.accesscode.create_accesscode.
2650        # grok.getSite returns None in tests.
2651        self.browser.open(payment_url + '/fake_approve')
2652        self.assertMatches('...Payment approved...',
2653                          self.browser.contents)
2654        expected = '''...
2655        <td>
2656          <span>Paid</span>
2657        </td>...'''
2658        expected = '''...
2659        <td>
2660          <span>Paid</span>
2661        </td>...'''
2662        self.assertMatches(expected,self.browser.contents)
2663        payment_id = self.student['payments'].keys()[0]
2664        payment = self.student['payments'][payment_id]
2665        self.assertEqual(payment.p_state, 'paid')
2666        self.assertEqual(payment.r_amount_approved, 3456.0)
2667        self.assertEqual(payment.r_code, 'AP')
2668        self.assertEqual(payment.r_desc, u'Payment approved by Anna Tester')
2669        # The new CLR-0 pin has been created
2670        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
2671        pin = self.app['accesscodes']['CLR-0'].keys()[0]
2672        ac = self.app['accesscodes']['CLR-0'][pin]
2673        self.assertEqual(ac.owner, self.student_id)
2674        self.assertEqual(ac.cost, 3456.0)
2675
2676        # Students can open the pdf payment slip
2677        self.browser.open(payment_url + '/payment_slip.pdf')
2678        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2679        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2680
2681        # The new CLR-0 pin can be used for starting clearance
2682        # but they have to upload a passport picture first
2683        # which is only possible in state admitted
2684        self.browser.open(self.student_path + '/change_portrait')
2685        self.assertMatches('...form is locked...',
2686                          self.browser.contents)
2687        IWorkflowInfo(self.student).fireTransition('admit')
2688        self.browser.open(self.student_path + '/change_portrait')
2689        image = open(SAMPLE_IMAGE, 'rb')
2690        ctrl = self.browser.getControl(name='passportuploadedit')
2691        file_ctrl = ctrl.mech_control
2692        file_ctrl.add_file(image, filename='my_photo.jpg')
2693        self.browser.getControl(
2694            name='upload_passportuploadedit').click()
2695        self.browser.open(self.student_path + '/start_clearance')
2696        parts = pin.split('-')[1:]
2697        clrseries, clrnumber = parts
2698        self.browser.getControl(name="ac_series").value = clrseries
2699        self.browser.getControl(name="ac_number").value = clrnumber
2700        self.browser.getControl("Start clearance now").click()
2701        self.assertMatches('...Clearance process has been started...',
2702                           self.browser.contents)
2703
2704    def test_student_schoolfee_payment(self):
2705        configuration = createObject('waeup.SessionConfiguration')
2706        configuration.academic_session = 2005
2707        self.app['configuration'].addSessionConfiguration(configuration)
2708        # Login
2709        self.browser.open(self.login_path)
2710        self.browser.getControl(name="form.login").value = self.student_id
2711        self.browser.getControl(name="form.password").value = 'spwd'
2712        self.browser.getControl("Login").click()
2713
2714        # Students can add online school fee payment tickets.
2715        IWorkflowState(self.student).setState('returning')
2716        self.browser.open(self.payments_path)
2717        self.assertRaises(
2718            LookupError, self.browser.getControl, name='val_id')
2719        self.browser.getLink("Add current session payment ticket").click()
2720        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2721        self.browser.getControl("Create ticket").click()
2722        self.assertMatches('...ticket created...',
2723                           self.browser.contents)
2724        ctrl = self.browser.getControl(name='val_id')
2725        value = ctrl.options[0]
2726        self.browser.getLink(value).click()
2727        self.assertMatches('...Amount Authorized...',
2728                           self.browser.contents)
2729        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2730        # Payment session and will be calculated as defined
2731        # in w.k.students.utils because we set changed the state
2732        # to returning
2733        self.assertEqual(self.student['payments'][value].p_session, 2005)
2734        self.assertEqual(self.student['payments'][value].p_level, 200)
2735
2736        # Student is the payer of the payment ticket.
2737        payer = IPayer(self.student['payments'][value])
2738        self.assertEqual(payer.display_fullname, 'Anna Tester')
2739        self.assertEqual(payer.id, self.student_id)
2740        self.assertEqual(payer.faculty, 'fac1')
2741        self.assertEqual(payer.department, 'dep1')
2742
2743        # We simulate the approval
2744        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
2745        self.browser.open(self.browser.url + '/fake_approve')
2746        self.assertMatches('...Payment approved...',
2747                          self.browser.contents)
2748
2749        ## The new SFE-0 pin can be used for starting new session
2750        #self.browser.open(self.studycourse_path)
2751        #self.browser.getLink('Start new session').click()
2752        #pin = self.app['accesscodes']['SFE-0'].keys()[0]
2753        #parts = pin.split('-')[1:]
2754        #sfeseries, sfenumber = parts
2755        #self.browser.getControl(name="ac_series").value = sfeseries
2756        #self.browser.getControl(name="ac_number").value = sfenumber
2757        #self.browser.getControl("Start now").click()
2758        #self.assertMatches('...Session started...',
2759        #                   self.browser.contents)
2760
2761        self.assertTrue(self.student.state == 'school fee paid')
2762        return
2763
2764    def test_student_bedallocation_payment(self):
2765        # Login
2766        self.browser.open(self.login_path)
2767        self.browser.getControl(name="form.login").value = self.student_id
2768        self.browser.getControl(name="form.password").value = 'spwd'
2769        self.browser.getControl("Login").click()
2770        self.browser.open(self.payments_path)
2771        self.browser.open(self.payments_path + '/addop')
2772        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
2773        self.browser.getControl("Create ticket").click()
2774        self.assertMatches('...ticket created...',
2775                           self.browser.contents)
2776        # Students can remove only online payment tickets which have
2777        # not received a valid callback
2778        self.browser.open(self.payments_path)
2779        ctrl = self.browser.getControl(name='val_id')
2780        value = ctrl.options[0]
2781        ctrl.getControl(value=value).selected = True
2782        self.browser.getControl("Remove selected", index=0).click()
2783        self.assertTrue('Successfully removed' in self.browser.contents)
2784
2785    def test_student_maintenance_payment(self):
2786        # Login
2787        self.browser.open(self.login_path)
2788        self.browser.getControl(name="form.login").value = self.student_id
2789        self.browser.getControl(name="form.password").value = 'spwd'
2790        self.browser.getControl("Login").click()
2791        self.browser.open(self.payments_path)
2792        self.browser.open(self.payments_path + '/addop')
2793        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
2794        self.browser.getControl("Create ticket").click()
2795        self.assertMatches('...You have not yet booked accommodation...',
2796                           self.browser.contents)
2797        # We continue this test in test_student_accommodation
2798
2799    def test_student_previous_payments(self):
2800        configuration = createObject('waeup.SessionConfiguration')
2801        configuration.academic_session = 2000
2802        configuration.clearance_fee = 3456.0
2803        configuration.booking_fee = 123.4
2804        self.app['configuration'].addSessionConfiguration(configuration)
2805        configuration2 = createObject('waeup.SessionConfiguration')
2806        configuration2.academic_session = 2003
2807        configuration2.clearance_fee = 3456.0
2808        configuration2.booking_fee = 123.4
2809        self.app['configuration'].addSessionConfiguration(configuration2)
2810        configuration3 = createObject('waeup.SessionConfiguration')
2811        configuration3.academic_session = 2005
2812        configuration3.clearance_fee = 3456.0
2813        configuration3.booking_fee = 123.4
2814        self.app['configuration'].addSessionConfiguration(configuration3)
2815        self.student['studycourse'].entry_session = 2002
2816
2817        # Login
2818        self.browser.open(self.login_path)
2819        self.browser.getControl(name="form.login").value = self.student_id
2820        self.browser.getControl(name="form.password").value = 'spwd'
2821        self.browser.getControl("Login").click()
2822
2823        # Students can add previous school fee payment tickets in any state.
2824        IWorkflowState(self.student).setState('courses registered')
2825        self.browser.open(self.payments_path)
2826        self.browser.getLink("Add previous session payment ticket").click()
2827
2828        # Previous session payment form is provided
2829        self.assertEqual(self.student.current_session, 2004)
2830        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2831        self.browser.getControl(name="form.p_session").value = ['2000']
2832        self.browser.getControl(name="form.p_level").value = ['300']
2833        self.browser.getControl("Create ticket").click()
2834        self.assertMatches('...The previous session must not fall below...',
2835                           self.browser.contents)
2836        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2837        self.browser.getControl(name="form.p_session").value = ['2005']
2838        self.browser.getControl(name="form.p_level").value = ['300']
2839        self.browser.getControl("Create ticket").click()
2840        self.assertMatches('...This is not a previous session...',
2841                           self.browser.contents)
2842        # Students can pay current session school fee.
2843        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2844        self.browser.getControl(name="form.p_session").value = ['2004']
2845        self.browser.getControl(name="form.p_level").value = ['300']
2846        self.browser.getControl("Create ticket").click()
2847        self.assertMatches('...ticket created...',
2848                           self.browser.contents)
2849        ctrl = self.browser.getControl(name='val_id')
2850        value = ctrl.options[0]
2851        self.browser.getLink(value).click()
2852        self.assertMatches('...Amount Authorized...',
2853                           self.browser.contents)
2854        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2855
2856        # Payment session is properly set
2857        self.assertEqual(self.student['payments'][value].p_session, 2004)
2858        self.assertEqual(self.student['payments'][value].p_level, 300)
2859
2860        # We simulate the approval
2861        self.browser.open(self.browser.url + '/fake_approve')
2862        self.assertMatches('...Payment approved...',
2863                          self.browser.contents)
2864
2865        # No AC has been created
2866        self.assertEqual(len(self.app['accesscodes']['SFE-0'].keys()), 0)
2867        self.assertTrue(self.student['payments'][value].ac is None)
2868
2869        # Current payment flag is set False
2870        self.assertFalse(self.student['payments'][value].p_current)
2871
2872        # Button and form are not available for students who are in
2873        # states up to cleared
2874        self.student['studycourse'].entry_session = 2004
2875        IWorkflowState(self.student).setState('cleared')
2876        self.browser.open(self.payments_path)
2877        self.assertFalse(
2878            "Add previous session payment ticket" in self.browser.contents)
2879        self.browser.open(self.payments_path + '/addpp')
2880        self.assertTrue(
2881            "No previous payment to be made" in self.browser.contents)
2882        return
2883
2884    def test_postgraduate_student_payments(self):
2885        configuration = createObject('waeup.SessionConfiguration')
2886        configuration.academic_session = 2005
2887        self.app['configuration'].addSessionConfiguration(configuration)
2888        self.certificate.study_mode = 'pg_ft'
2889        self.certificate.start_level = 999
2890        self.certificate.end_level = 999
2891        self.student['studycourse'].current_level = 999
2892        # Login
2893        self.browser.open(self.login_path)
2894        self.browser.getControl(name="form.login").value = self.student_id
2895        self.browser.getControl(name="form.password").value = 'spwd'
2896        self.browser.getControl("Login").click()
2897        # Students can add online school fee payment tickets.
2898        IWorkflowState(self.student).setState('cleared')
2899        self.browser.open(self.payments_path)
2900        self.browser.getLink("Add current session payment ticket").click()
2901        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2902        self.browser.getControl("Create ticket").click()
2903        self.assertMatches('...ticket created...',
2904                           self.browser.contents)
2905        ctrl = self.browser.getControl(name='val_id')
2906        value = ctrl.options[0]
2907        self.browser.getLink(value).click()
2908        self.assertMatches('...Amount Authorized...',
2909                           self.browser.contents)
2910        # Payment session and level are current ones.
2911        # Postgrads have to pay school_fee_1.
2912        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
2913        self.assertEqual(self.student['payments'][value].p_session, 2004)
2914        self.assertEqual(self.student['payments'][value].p_level, 999)
2915
2916        # We simulate the approval
2917        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
2918        self.browser.open(self.browser.url + '/fake_approve')
2919        self.assertMatches('...Payment approved...',
2920                          self.browser.contents)
2921
2922        ## The new SFE-0 pin can be used for starting session
2923        #self.browser.open(self.studycourse_path)
2924        #self.browser.getLink('Start new session').click()
2925        #pin = self.app['accesscodes']['SFE-0'].keys()[0]
2926        #parts = pin.split('-')[1:]
2927        #sfeseries, sfenumber = parts
2928        #self.browser.getControl(name="ac_series").value = sfeseries
2929        #self.browser.getControl(name="ac_number").value = sfenumber
2930        #self.browser.getControl("Start now").click()
2931        #self.assertMatches('...Session started...',
2932        #                   self.browser.contents)
2933
2934        self.assertTrue(self.student.state == 'school fee paid')
2935
2936        # Postgrad students do not need to register courses the
2937        # can just pay for the next session.
2938        self.browser.open(self.payments_path)
2939        # Remove first payment to be sure that we access the right ticket
2940        del self.student['payments'][value]
2941        self.browser.getLink("Add current session payment ticket").click()
2942        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2943        self.browser.getControl("Create ticket").click()
2944        ctrl = self.browser.getControl(name='val_id')
2945        value = ctrl.options[0]
2946        self.browser.getLink(value).click()
2947        # Payment session has increased by one, payment level remains the same.
2948        # Returning Postgraduates have to pay school_fee_2.
2949        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2950        self.assertEqual(self.student['payments'][value].p_session, 2005)
2951        self.assertEqual(self.student['payments'][value].p_level, 999)
2952
2953        # Student is still in old session
2954        self.assertEqual(self.student.current_session, 2004)
2955
2956        # We do not need to pay the ticket if any other
2957        # SFE pin is provided
2958        pin_container = self.app['accesscodes']
2959        pin_container.createBatch(
2960            datetime.utcnow(), 'some_userid', 'SFE', 9.99, 1)
2961        pin = pin_container['SFE-1'].values()[0].representation
2962        sfeseries, sfenumber = pin.split('-')[1:]
2963        # The new SFE-1 pin can be used for starting new session
2964        self.browser.open(self.studycourse_path)
2965        self.browser.getLink('Start new session').click()
2966        self.browser.getControl(name="ac_series").value = sfeseries
2967        self.browser.getControl(name="ac_number").value = sfenumber
2968        self.browser.getControl("Start now").click()
2969        self.assertMatches('...Session started...',
2970                           self.browser.contents)
2971        self.assertTrue(self.student.state == 'school fee paid')
2972        # Student is in new session
2973        self.assertEqual(self.student.current_session, 2005)
2974        self.assertEqual(self.student['studycourse'].current_level, 999)
2975        return
2976
2977    def test_student_accommodation(self):
2978        # Login
2979        self.browser.open(self.login_path)
2980        self.browser.getControl(name="form.login").value = self.student_id
2981        self.browser.getControl(name="form.password").value = 'spwd'
2982        self.browser.getControl("Login").click()
2983
2984        # Students can add online booking fee payment tickets and open the
2985        # callback view (see test_manage_payments)
2986        self.browser.getLink("Payments").click()
2987        self.browser.getLink("Add current session payment ticket").click()
2988        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
2989        self.browser.getControl("Create ticket").click()
2990        ctrl = self.browser.getControl(name='val_id')
2991        value = ctrl.options[0]
2992        self.browser.getLink(value).click()
2993        self.browser.open(self.browser.url + '/fake_approve')
2994        # The new HOS-0 pin has been created
2995        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
2996        pin = self.app['accesscodes']['HOS-0'].keys()[0]
2997        ac = self.app['accesscodes']['HOS-0'][pin]
2998        parts = pin.split('-')[1:]
2999        sfeseries, sfenumber = parts
3000
3001        # Students can use HOS code and book a bed space with it ...
3002        self.browser.open(self.acco_path)
3003        # ... but not if booking period has expired ...
3004        self.app['hostels'].enddate = datetime.now(pytz.utc)
3005        self.browser.getLink("Book accommodation").click()
3006        self.assertMatches('...Outside booking period: ...',
3007                           self.browser.contents)
3008        self.app['hostels'].enddate = datetime.now(pytz.utc) + timedelta(days=10)
3009        # ... or student is not the an allowed state ...
3010        self.browser.getLink("Book accommodation").click()
3011        self.assertMatches('...You are in the wrong...',
3012                           self.browser.contents)
3013        IWorkflowInfo(self.student).fireTransition('admit')
3014        self.browser.getLink("Book accommodation").click()
3015        self.assertMatches('...Activation Code:...',
3016                           self.browser.contents)
3017        # Student can't used faked ACs ...
3018        self.browser.getControl(name="ac_series").value = u'nonsense'
3019        self.browser.getControl(name="ac_number").value = sfenumber
3020        self.browser.getControl("Create bed ticket").click()
3021        self.assertMatches('...Activation code is invalid...',
3022                           self.browser.contents)
3023        # ... or ACs owned by somebody else.
3024        ac.owner = u'Anybody'
3025        self.browser.getControl(name="ac_series").value = sfeseries
3026        self.browser.getControl(name="ac_number").value = sfenumber
3027        self.browser.getControl("Create bed ticket").click()
3028        self.assertMatches('...You are not the owner of this access code...',
3029                           self.browser.contents)
3030        ac.owner = self.student_id
3031        self.browser.getControl(name="ac_series").value = sfeseries
3032        self.browser.getControl(name="ac_number").value = sfenumber
3033        self.browser.getControl("Create bed ticket").click()
3034        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
3035                           self.browser.contents)
3036
3037        # Bed has been allocated
3038        bed = self.app['hostels']['hall-1']['hall-1_A_101_A']
3039        self.assertTrue(bed.owner == self.student_id)
3040
3041        # BedTicketAddPage is now blocked
3042        self.browser.getLink("Book accommodation").click()
3043        self.assertMatches('...You already booked a bed space...',
3044            self.browser.contents)
3045
3046        # The bed ticket displays the data correctly
3047        self.browser.open(self.acco_path + '/2004')
3048        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
3049                           self.browser.contents)
3050        self.assertMatches('...2004/2005...', self.browser.contents)
3051        self.assertMatches('...regular_male_fr...', self.browser.contents)
3052        self.assertMatches('...%s...' % pin, self.browser.contents)
3053
3054        # Students can open the pdf slip
3055        self.browser.open(self.browser.url + '/bed_allocation_slip.pdf')
3056        self.assertEqual(self.browser.headers['Status'], '200 Ok')
3057        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
3058
3059        # Students can't relocate themselves
3060        self.assertFalse('Relocate' in self.browser.contents)
3061        relocate_path = self.acco_path + '/2004/relocate'
3062        self.assertRaises(
3063            Unauthorized, self.browser.open, relocate_path)
3064
3065        # Students can't the Remove button and check boxes
3066        self.browser.open(self.acco_path)
3067        self.assertFalse('Remove' in self.browser.contents)
3068        self.assertFalse('val_id' in self.browser.contents)
3069
3070        # Students can pay maintenance fee now
3071        self.browser.open(self.payments_path)
3072        self.browser.open(self.payments_path + '/addop')
3073        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
3074        self.browser.getControl("Create ticket").click()
3075        self.assertMatches('...Payment ticket created...',
3076                           self.browser.contents)
3077
3078        ctrl = self.browser.getControl(name='val_id')
3079        value = ctrl.options[0]
3080        # Maintennace fee is taken from the hostel object
3081        self.assertEqual(self.student['payments'][value].amount_auth, 876.0)
3082        # If the hostel's maintenance fee isn't set, the fee is
3083        # taken from the session configuration object.
3084        self.app['hostels']['hall-1'].maint_fee = 0.0
3085        self.browser.open(self.payments_path + '/addop')
3086        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
3087        self.browser.getControl("Create ticket").click()
3088        ctrl = self.browser.getControl(name='val_id')
3089        value = ctrl.options[1]
3090        self.assertEqual(self.student['payments'][value].amount_auth, 987.0)
3091        return
3092
3093    def test_change_password_request(self):
3094        self.browser.open('http://localhost/app/changepw')
3095        self.browser.getControl(name="form.identifier").value = '123'
3096        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
3097        self.browser.getControl("Send login credentials").click()
3098        self.assertTrue('An email with' in self.browser.contents)
3099
3100    def test_student_expired_personal_data(self):
3101        # Login
3102        IWorkflowState(self.student).setState('school fee paid')
3103        delta = timedelta(days=180)
3104        self.student.personal_updated = datetime.utcnow() - delta
3105        self.browser.open(self.login_path)
3106        self.browser.getControl(name="form.login").value = self.student_id
3107        self.browser.getControl(name="form.password").value = 'spwd'
3108        self.browser.getControl("Login").click()
3109        self.assertEqual(self.browser.url, self.student_path)
3110        self.assertTrue(
3111            'You logged in' in self.browser.contents)
3112        # Students don't see personal_updated field in edit form
3113        self.browser.open(self.edit_personal_path)
3114        self.assertFalse('Updated' in self.browser.contents)
3115        self.browser.open(self.personal_path)
3116        self.assertTrue('Updated' in self.browser.contents)
3117        self.browser.getLink("Logout").click()
3118        delta = timedelta(days=181)
3119        self.student.personal_updated = datetime.utcnow() - delta
3120        self.browser.open(self.login_path)
3121        self.browser.getControl(name="form.login").value = self.student_id
3122        self.browser.getControl(name="form.password").value = 'spwd'
3123        self.browser.getControl("Login").click()
3124        self.assertEqual(self.browser.url, self.edit_personal_path)
3125        self.assertTrue(
3126            'Your personal data record is outdated.' in self.browser.contents)
3127
3128    def test_request_transcript(self):
3129        IWorkflowState(self.student).setState('graduated')
3130        self.browser.open(self.login_path)
3131        self.browser.getControl(name="form.login").value = self.student_id
3132        self.browser.getControl(name="form.password").value = 'spwd'
3133        self.browser.getControl("Login").click()
3134        self.assertMatches(
3135            '...You logged in...', self.browser.contents)
3136        # Create payment ticket
3137        self.browser.open(self.payments_path)
3138        self.browser.open(self.payments_path + '/addop')
3139        self.browser.getControl(name="form.p_category").value = ['transcript']
3140        self.browser.getControl("Create ticket").click()
3141        ctrl = self.browser.getControl(name='val_id')
3142        value = ctrl.options[0]
3143        self.browser.getLink(value).click()
3144        self.assertMatches('...Amount Authorized...',
3145                           self.browser.contents)
3146        self.assertEqual(self.student['payments'][value].amount_auth, 4567.0)
3147        # Student is the payer of the payment ticket.
3148        payer = IPayer(self.student['payments'][value])
3149        self.assertEqual(payer.display_fullname, 'Anna Tester')
3150        self.assertEqual(payer.id, self.student_id)
3151        self.assertEqual(payer.faculty, 'fac1')
3152        self.assertEqual(payer.department, 'dep1')
3153        # We simulate the approval and fetch the pin
3154        self.assertEqual(len(self.app['accesscodes']['TSC-0']),0)
3155        self.browser.open(self.browser.url + '/fake_approve')
3156        self.assertMatches('...Payment approved...',
3157                          self.browser.contents)
3158        pin = self.app['accesscodes']['TSC-0'].keys()[0]
3159        parts = pin.split('-')[1:]
3160        tscseries, tscnumber = parts
3161        # Student can use the pin to send the transcript request
3162        self.browser.open(self.student_path)
3163        self.browser.getLink("Request transcript").click()
3164        self.browser.getControl(name="ac_series").value = tscseries
3165        self.browser.getControl(name="ac_number").value = tscnumber
3166        self.browser.getControl(name="comment").value = 'Comment line 1 \nComment line2'
3167        self.browser.getControl(name="address").value = 'Address line 1 \nAddress line2'
3168        self.browser.getControl("Submit").click()
3169        self.assertMatches('...Transcript processing has been started...',
3170                          self.browser.contents)
3171        self.assertEqual(self.student.state, 'transcript requested')
3172        self.assertMatches(
3173            '... UTC K1000000 wrote:\n\nComment line 1 \n'
3174            'Comment line2\n\nDispatch Address:\nAddress line 1 \n'
3175            'Address line2\n\n', self.student.transcript_comment)
3176        # The comment has been logged
3177        logfile = os.path.join(
3178            self.app['datacenter'].storage, 'logs', 'students.log')
3179        logcontent = open(logfile).read()
3180        self.assertTrue(
3181            'K1000000 - students.browser.StudentTranscriptRequestPage - '
3182            'K1000000 - comment: Comment line 1 <br>Comment line2\n'
3183            in logcontent)
3184
3185class StudentRequestPWTests(StudentsFullSetup):
3186    # Tests for student registration
3187
3188    layer = FunctionalLayer
3189
3190    def test_request_pw(self):
3191        # Student with wrong number can't be found.
3192        self.browser.open('http://localhost/app/requestpw')
3193        self.browser.getControl(name="form.firstname").value = 'Anna'
3194        self.browser.getControl(name="form.number").value = 'anynumber'
3195        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
3196        self.browser.getControl("Send login credentials").click()
3197        self.assertTrue('No student record found.'
3198            in self.browser.contents)
3199        # Anonymous is not informed that firstname verification failed.
3200        # It seems that the record doesn't exist.
3201        self.browser.open('http://localhost/app/requestpw')
3202        self.browser.getControl(name="form.firstname").value = 'Johnny'
3203        self.browser.getControl(name="form.number").value = '123'
3204        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
3205        self.browser.getControl("Send login credentials").click()
3206        self.assertTrue('No student record found.'
3207            in self.browser.contents)
3208        # Even with the correct firstname we can't register if a
3209        # password has been set and used.
3210        self.browser.getControl(name="form.firstname").value = 'Anna'
3211        self.browser.getControl(name="form.number").value = '123'
3212        self.browser.getControl("Send login credentials").click()
3213        self.assertTrue('Your password has already been set and used.'
3214            in self.browser.contents)
3215        self.browser.open('http://localhost/app/requestpw')
3216        self.app['students'][self.student_id].password = None
3217        # The firstname field, used for verification, is not case-sensitive.
3218        self.browser.getControl(name="form.firstname").value = 'aNNa'
3219        self.browser.getControl(name="form.number").value = '123'
3220        self.browser.getControl(name="form.email").value = 'new@yy.zz'
3221        self.browser.getControl("Send login credentials").click()
3222        # Yeah, we succeded ...
3223        self.assertTrue('Your password request was successful.'
3224            in self.browser.contents)
3225        # We can also use the matric_number instead.
3226        self.browser.open('http://localhost/app/requestpw')
3227        self.browser.getControl(name="form.firstname").value = 'aNNa'
3228        self.browser.getControl(name="form.number").value = '234'
3229        self.browser.getControl(name="form.email").value = 'new@yy.zz'
3230        self.browser.getControl("Send login credentials").click()
3231        self.assertTrue('Your password request was successful.'
3232            in self.browser.contents)
3233        # ... and  student can be found in the catalog via the email address
3234        cat = queryUtility(ICatalog, name='students_catalog')
3235        results = list(
3236            cat.searchResults(
3237            email=('new@yy.zz', 'new@yy.zz')))
3238        self.assertEqual(self.student,results[0])
3239        logfile = os.path.join(
3240            self.app['datacenter'].storage, 'logs', 'main.log')
3241        logcontent = open(logfile).read()
3242        self.assertTrue('zope.anybody - students.browser.StudentRequestPasswordPage - '
3243                        '234 (K1000000) - new@yy.zz' in logcontent)
3244        return
3245
3246    def test_student_locked_level_forms(self):
3247
3248        # Add two study levels, one current and one previous
3249        studylevel = createObject(u'waeup.StudentStudyLevel')
3250        studylevel.level = 100
3251        self.student['studycourse'].addStudentStudyLevel(
3252            self.certificate, studylevel)
3253        studylevel = createObject(u'waeup.StudentStudyLevel')
3254        studylevel.level = 200
3255        self.student['studycourse'].addStudentStudyLevel(
3256            self.certificate, studylevel)
3257        IWorkflowState(self.student).setState('school fee paid')
3258        self.student['studycourse'].current_level = 200
3259
3260        self.browser.open(self.login_path)
3261        self.browser.getControl(name="form.login").value = self.student_id
3262        self.browser.getControl(name="form.password").value = 'spwd'
3263        self.browser.getControl("Login").click()
3264
3265        self.browser.open(self.student_path + '/studycourse/200/edit')
3266        self.assertFalse('The requested form is locked' in self.browser.contents)
3267        self.browser.open(self.student_path + '/studycourse/100/edit')
3268        self.assertTrue('The requested form is locked' in self.browser.contents)
3269
3270        self.browser.open(self.student_path + '/studycourse/200/ctadd')
3271        self.assertFalse('The requested form is locked' in self.browser.contents)
3272        self.browser.open(self.student_path + '/studycourse/100/ctadd')
3273        self.assertTrue('The requested form is locked' in self.browser.contents)
3274
3275        IWorkflowState(self.student).setState('courses registered')
3276        self.browser.open(self.student_path + '/studycourse/200/edit')
3277        self.assertTrue('The requested form is locked' in self.browser.contents)
3278        self.browser.open(self.student_path + '/studycourse/200/ctadd')
3279        self.assertTrue('The requested form is locked' in self.browser.contents)
3280
3281
3282class PublicPagesTests(StudentsFullSetup):
3283    # Tests for simple webservices
3284
3285    layer = FunctionalLayer
3286
3287    def test_paymentrequest(self):
3288        payment = createObject('waeup.StudentOnlinePayment')
3289        payment.p_category = u'schoolfee'
3290        payment.p_session = self.student.current_session
3291        payment.p_item = u'My Certificate'
3292        payment.p_id = u'anyid'
3293        self.student['payments']['anykey'] = payment
3294        # Request information about unpaid payment ticket
3295        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
3296        self.assertEqual(self.browser.contents, '-1')
3297        # Request information about paid payment ticket
3298        payment.p_state = u'paid'
3299        notify(grok.ObjectModifiedEvent(payment))
3300        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
3301        self.assertEqual(self.browser.contents,
3302            'FULL_NAME=Anna Tester&FACULTY=fac1&DEPARTMENT=dep1'
3303            '&PAYMENT_ITEM=My Certificate&PAYMENT_CATEGORY=School Fee'
3304            '&ACADEMIC_SESSION=2004/2005&MATRIC_NUMBER=234&REG_NUMBER=123'
3305            '&FEE_AMOUNT=0.0')
3306        self.browser.open('http://localhost/app/paymentrequest?NONSENSE=nonsense')
3307        self.assertEqual(self.browser.contents, '-1')
3308        self.browser.open('http://localhost/app/paymentrequest?P_ID=nonsense')
3309        self.assertEqual(self.browser.contents, '-1')
3310
3311class StudentDataExportTests(StudentsFullSetup, FunctionalAsyncTestCase):
3312    # Tests for StudentsContainer class views and pages
3313
3314    layer = FunctionalLayer
3315
3316    def wait_for_export_job_completed(self):
3317        # helper function waiting until the current export job is completed
3318        manager = getUtility(IJobManager)
3319        job_id = self.app['datacenter'].running_exports[0][0]
3320        job = manager.get(job_id)
3321        wait_for_result(job)
3322        return job_id
3323
3324    def test_datacenter_export(self):
3325        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3326        self.browser.open('http://localhost/app/datacenter/@@exportconfig')
3327        self.browser.getControl(name="exporter").value = ['bursary']
3328        self.browser.getControl(name="session").value = ['2004']
3329        self.browser.getControl(name="level").value = ['100']
3330        self.browser.getControl(name="mode").value = ['ug_ft']
3331        self.browser.getControl(name="payments_start").value = '13/12/2012'
3332        self.browser.getControl(name="payments_end").value = '14/12/2012'
3333        self.browser.getControl("Create CSV file").click()
3334
3335        # When the job is finished and we reload the page...
3336        job_id = self.wait_for_export_job_completed()
3337        # ... the csv file can be downloaded ...
3338        self.browser.open('http://localhost/app/datacenter/@@export')
3339        self.browser.getLink("Download").click()
3340        self.assertEqual(self.browser.headers['content-type'],
3341            'text/csv; charset=UTF-8')
3342        self.assertTrue(
3343            'filename="WAeUP.Kofa_bursary_%s.csv' % job_id in
3344            self.browser.headers['content-disposition'])
3345        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3346        job_id = self.app['datacenter'].running_exports[0][0]
3347        # ... and discarded
3348        self.browser.open('http://localhost/app/datacenter/@@export')
3349        self.browser.getControl("Discard").click()
3350        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3351        # Creation, downloading and discarding is logged
3352        logfile = os.path.join(
3353            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3354        logcontent = open(logfile).read()
3355        self.assertTrue(
3356            'zope.mgr - students.browser.DatacenterExportJobContainerJobConfig '
3357            '- exported: bursary (2004, 100, ug_ft, None, None, None, '
3358            '13/12/2012, 14/12/2012), job_id=%s'
3359            % job_id in logcontent
3360            )
3361        self.assertTrue(
3362            'zope.mgr - browser.pages.ExportCSVView '
3363            '- downloaded: WAeUP.Kofa_bursary_%s.csv, job_id=%s'
3364            % (job_id, job_id) in logcontent
3365            )
3366        self.assertTrue(
3367            'zope.mgr - browser.pages.ExportCSVPage '
3368            '- discarded: job_id=%s' % job_id in logcontent
3369            )
3370
3371    def test_datacenter_export_selected(self):
3372        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3373        self.browser.open('http://localhost/app/datacenter/@@exportselected')
3374        self.browser.getControl(name="exporter").value = ['students']
3375        self.browser.getControl(name="students").value = 'K1000000'
3376        self.browser.getControl("Create CSV file").click()
3377        # When the job is finished and we reload the page...
3378        job_id = self.wait_for_export_job_completed()
3379        # ... the csv file can be downloaded ...
3380        self.browser.open('http://localhost/app/datacenter/@@export')
3381        self.browser.getLink("Download").click()
3382        self.assertEqual(self.browser.headers['content-type'],
3383            'text/csv; charset=UTF-8')
3384        self.assertTrue(
3385            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3386            self.browser.headers['content-disposition'])
3387        self.assertTrue(
3388            'adm_code,clearance_locked,clr_code,date_of_birth,email,employer,'
3389            'firstname,lastname,matric_number,middlename,nationality,'
3390            'officer_comment,perm_address,personal_updated,phone,reg_number,'
3391            'sex,student_id,suspended,suspended_comment,transcript_comment,'
3392            'password,state,history,certcode,is_postgrad,current_level,'
3393            'current_session\r\n'
3394            ',1,,1981-02-04#,aa@aa.ng,,Anna,Tester,234,,,,,,'
3395            '1234#,123,m,K1000000,0,,,{SSHA}' in self.browser.contents)
3396
3397    def test_payment_dates(self):
3398        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3399        self.browser.open('http://localhost/app/datacenter/@@exportconfig')
3400        self.browser.getControl(name="exporter").value = ['bursary']
3401        self.browser.getControl(name="session").value = ['2004']
3402        self.browser.getControl(name="level").value = ['100']
3403        self.browser.getControl(name="mode").value = ['ug_ft']
3404        self.browser.getControl(name="payments_start").value = '13/12/2012'
3405        # If one payment date is missing, an error message appears
3406        self.browser.getControl(name="payments_end").value = ''
3407        self.browser.getControl("Create CSV file").click()
3408        self.assertTrue('Payment dates do not match format d/m/Y'
3409            in self.browser.contents)
3410
3411    def test_faculties_export(self):
3412        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3413        facs_path = 'http://localhost/app/faculties'
3414        self.browser.open(facs_path)
3415        self.browser.getLink("Export student data").click()
3416        self.browser.getControl("Configure new export").click()
3417        self.browser.getControl(name="exporter").value = ['bursary']
3418        self.browser.getControl(name="session").value = ['2004']
3419        self.browser.getControl(name="level").value = ['100']
3420        self.browser.getControl(name="mode").value = ['ug_ft']
3421        self.browser.getControl(name="payments_start").value = '13/12/2012'
3422        self.browser.getControl(name="payments_end").value = '14/12/2012'
3423        self.browser.getControl("Create CSV file").click()
3424
3425        # When the job is finished and we reload the page...
3426        job_id = self.wait_for_export_job_completed()
3427        self.browser.open(facs_path + '/exports')
3428        # ... the csv file can be downloaded ...
3429        self.browser.getLink("Download").click()
3430        self.assertEqual(self.browser.headers['content-type'],
3431            'text/csv; charset=UTF-8')
3432        self.assertTrue(
3433            'filename="WAeUP.Kofa_bursary_%s.csv' % job_id in
3434            self.browser.headers['content-disposition'])
3435        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3436        job_id = self.app['datacenter'].running_exports[0][0]
3437        # ... and discarded
3438        self.browser.open(facs_path + '/exports')
3439        self.browser.getControl("Discard").click()
3440        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3441        # Creation, downloading and discarding is logged
3442        logfile = os.path.join(
3443            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3444        logcontent = open(logfile).read()
3445        self.assertTrue(
3446            'zope.mgr - students.browser.FacultiesExportJobContainerJobConfig '
3447            '- exported: bursary (2004, 100, ug_ft, None, None, None, '
3448            '13/12/2012, 14/12/2012), job_id=%s'
3449            % job_id in logcontent
3450            )
3451        self.assertTrue(
3452            'zope.mgr - students.browser.ExportJobContainerDownload '
3453            '- downloaded: WAeUP.Kofa_bursary_%s.csv, job_id=%s'
3454            % (job_id, job_id) in logcontent
3455            )
3456        self.assertTrue(
3457            'zope.mgr - students.browser.ExportJobContainerOverview '
3458            '- discarded: job_id=%s' % job_id in logcontent
3459            )
3460
3461    def test_faculty_export(self):
3462        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3463        fac1_path = 'http://localhost/app/faculties/fac1'
3464        self.browser.open(fac1_path)
3465        self.browser.getLink("Export student data").click()
3466        self.browser.getControl("Configure new export").click()
3467        self.browser.getControl(name="exporter").value = ['students']
3468        self.browser.getControl(name="session").value = ['2004']
3469        self.browser.getControl(name="level").value = ['100']
3470        self.browser.getControl(name="mode").value = ['ug_ft']
3471        # The testbrowser does not hide the payment period fields, but
3472        # values are ignored when using the students exporter.
3473        self.browser.getControl(name="payments_start").value = '13/12/2012'
3474        self.browser.getControl(name="payments_end").value = '14/12/2012'
3475        self.browser.getControl("Create CSV file").click()
3476
3477        # When the job is finished and we reload the page...
3478        job_id = self.wait_for_export_job_completed()
3479        self.browser.open(fac1_path + '/exports')
3480        # ... the csv file can be downloaded ...
3481        self.browser.getLink("Download").click()
3482        self.assertEqual(self.browser.headers['content-type'],
3483            'text/csv; charset=UTF-8')
3484        self.assertTrue(
3485            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3486            self.browser.headers['content-disposition'])
3487        self.assertTrue(
3488            'adm_code,clearance_locked,clr_code,date_of_birth,email,employer,'
3489            'firstname,lastname,matric_number,middlename,nationality,'
3490            'officer_comment,perm_address,personal_updated,phone,reg_number,'
3491            'sex,student_id,suspended,suspended_comment,transcript_comment,'
3492            'password,state,history,certcode,is_postgrad,current_level,'
3493            'current_session\r\n'
3494            ',1,,1981-02-04#,aa@aa.ng,,Anna,Tester,234,,,,,,'
3495            '1234#,123,m,K1000000,0,,,{SSHA}' in self.browser.contents)
3496        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3497        job_id = self.app['datacenter'].running_exports[0][0]
3498        # ... and discarded
3499        self.browser.open(fac1_path + '/exports')
3500        self.browser.getControl("Discard").click()
3501        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3502        # Creation, downloading and discarding is logged
3503        logfile = os.path.join(
3504            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3505        logcontent = open(logfile).read()
3506        self.assertTrue(
3507            'zope.mgr - students.browser.FacultyExportJobContainerJobConfig '
3508            '- exported: students (2004, 100, ug_ft, fac1, None, None, '
3509            '13/12/2012, 14/12/2012), job_id=%s'
3510            % job_id in logcontent
3511            )
3512        self.assertTrue(
3513            'zope.mgr - students.browser.ExportJobContainerDownload '
3514            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3515            % (job_id, job_id) in logcontent
3516            )
3517        self.assertTrue(
3518            'zope.mgr - students.browser.ExportJobContainerOverview '
3519            '- discarded: job_id=%s' % job_id in logcontent
3520            )
3521
3522    def test_department_export(self):
3523        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3524        dep1_path = 'http://localhost/app/faculties/fac1/dep1'
3525        self.browser.open(dep1_path)
3526        self.browser.getLink("Export student data").click()
3527        self.browser.getControl("Configure new export").click()
3528        self.browser.getControl(name="exporter").value = ['students']
3529        self.browser.getControl(name="session").value = ['2004']
3530        self.browser.getControl(name="level").value = ['100']
3531        self.browser.getControl(name="mode").value = ['ug_ft']
3532        # The testbrowser does not hide the payment period fields, but
3533        # values are ignored when using the students exporter.
3534        self.browser.getControl(name="payments_start").value = '13/12/2012'
3535        self.browser.getControl(name="payments_end").value = '14/12/2012'
3536        self.browser.getControl("Create CSV file").click()
3537
3538        # When the job is finished and we reload the page...
3539        job_id = self.wait_for_export_job_completed()
3540        self.browser.open(dep1_path + '/exports')
3541        # ... the csv file can be downloaded ...
3542        self.browser.getLink("Download").click()
3543        self.assertEqual(self.browser.headers['content-type'],
3544            'text/csv; charset=UTF-8')
3545        self.assertTrue(
3546            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3547            self.browser.headers['content-disposition'])
3548        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3549        job_id = self.app['datacenter'].running_exports[0][0]
3550        # ... and discarded
3551        self.browser.open(dep1_path + '/exports')
3552        self.browser.getControl("Discard").click()
3553        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3554        # Creation, downloading and discarding is logged
3555        logfile = os.path.join(
3556            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3557        logcontent = open(logfile).read()
3558        self.assertTrue(
3559            'zope.mgr - students.browser.DepartmentExportJobContainerJobConfig '
3560            '- exported: students (2004, 100, ug_ft, None, dep1, None, '
3561            '13/12/2012, 14/12/2012), job_id=%s'
3562            % job_id in logcontent
3563            )
3564        self.assertTrue(
3565            'zope.mgr - students.browser.ExportJobContainerDownload '
3566            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3567            % (job_id, job_id) in logcontent
3568            )
3569        self.assertTrue(
3570            'zope.mgr - students.browser.ExportJobContainerOverview '
3571            '- discarded: job_id=%s' % job_id in logcontent
3572            )
3573
3574    def test_certificate_export(self):
3575        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3576        cert1_path = 'http://localhost/app/faculties/fac1/dep1/certificates/CERT1'
3577        self.browser.open(cert1_path)
3578        self.browser.getLink("Export student data").click()
3579        self.browser.getControl("Configure new export").click()
3580        self.browser.getControl(name="exporter").value = ['students']
3581        self.browser.getControl(name="session").value = ['2004']
3582        self.browser.getControl(name="level").value = ['100']
3583        self.browser.getControl("Create CSV file").click()
3584
3585        # When the job is finished and we reload the page...
3586        job_id = self.wait_for_export_job_completed()
3587        self.browser.open(cert1_path + '/exports')
3588        # ... the csv file can be downloaded ...
3589        self.browser.getLink("Download").click()
3590        self.assertEqual(self.browser.headers['content-type'],
3591            'text/csv; charset=UTF-8')
3592        self.assertTrue(
3593            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3594            self.browser.headers['content-disposition'])
3595        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3596        job_id = self.app['datacenter'].running_exports[0][0]
3597        # ... and discarded
3598        self.browser.open(cert1_path + '/exports')
3599        self.browser.getControl("Discard").click()
3600        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3601        # Creation, downloading and discarding is logged
3602        logfile = os.path.join(
3603            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3604        logcontent = open(logfile).read()
3605        self.assertTrue(
3606            'zope.mgr - students.browser.CertificateExportJobContainerJobConfig '
3607            '- exported: students (2004, 100, None, None, None, CERT1, None, None), '
3608            'job_id=%s'
3609            % job_id in logcontent
3610            )
3611        self.assertTrue(
3612            'zope.mgr - students.browser.ExportJobContainerDownload '
3613            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3614            % (job_id, job_id) in logcontent
3615            )
3616        self.assertTrue(
3617            'zope.mgr - students.browser.ExportJobContainerOverview '
3618            '- discarded: job_id=%s' % job_id in logcontent
3619            )
3620
3621    def test_course_export_students(self):
3622        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3623        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
3624        self.browser.open(course1_path)
3625        self.browser.getLink("Export student data").click()
3626        self.browser.getControl("Configure new export").click()
3627        self.browser.getControl(name="exporter").value = ['students']
3628        self.browser.getControl(name="session").value = ['2004']
3629        self.browser.getControl(name="level").value = ['100']
3630        self.browser.getControl("Create CSV file").click()
3631
3632        # When the job is finished and we reload the page...
3633        job_id = self.wait_for_export_job_completed()
3634        self.browser.open(course1_path + '/exports')
3635        # ... the csv file can be downloaded ...
3636        self.browser.getLink("Download").click()
3637        self.assertEqual(self.browser.headers['content-type'],
3638            'text/csv; charset=UTF-8')
3639        self.assertTrue(
3640            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3641            self.browser.headers['content-disposition'])
3642        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3643        job_id = self.app['datacenter'].running_exports[0][0]
3644        # ... and discarded
3645        self.browser.open(course1_path + '/exports')
3646        self.browser.getControl("Discard").click()
3647        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3648        # Creation, downloading and discarding is logged
3649        logfile = os.path.join(
3650            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3651        logcontent = open(logfile).read()
3652        self.assertTrue(
3653            'zope.mgr - students.browser.CourseExportJobContainerJobConfig '
3654            '- exported: students (2004, 100, COURSE1), job_id=%s'
3655            % job_id in logcontent
3656            )
3657        self.assertTrue(
3658            'zope.mgr - students.browser.ExportJobContainerDownload '
3659            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3660            % (job_id, job_id) in logcontent
3661            )
3662        self.assertTrue(
3663            'zope.mgr - students.browser.ExportJobContainerOverview '
3664            '- discarded: job_id=%s' % job_id in logcontent
3665            )
3666
3667    def test_course_export_coursetickets(self):
3668        # We add study level 100 to the student's studycourse
3669        studylevel = StudentStudyLevel()
3670        studylevel.level = 100
3671        studylevel.level_session = 2004
3672        self.student['studycourse'].addStudentStudyLevel(
3673            self.certificate,studylevel)
3674        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3675        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
3676        self.browser.open(course1_path)
3677        self.browser.getLink("Export student data").click()
3678        self.browser.getControl("Configure new export").click()
3679        self.browser.getControl(name="exporter").value = ['coursetickets']
3680        self.browser.getControl(name="session").value = ['2004']
3681        self.browser.getControl(name="level").value = ['100']
3682        self.browser.getControl("Create CSV file").click()
3683        # When the job is finished and we reload the page...
3684        job_id = self.wait_for_export_job_completed()
3685        self.browser.open(course1_path + '/exports')
3686        # ... the csv file can be downloaded ...
3687        self.browser.getLink("Download").click()
3688        self.assertEqual(self.browser.headers['content-type'],
3689            'text/csv; charset=UTF-8')
3690        self.assertTrue(
3691            'filename="WAeUP.Kofa_coursetickets_%s.csv' % job_id in
3692            self.browser.headers['content-disposition'])
3693        # ... and contains the course ticket COURSE1
3694        self.assertEqual(self.browser.contents,
3695            'automatic,carry_over,code,credits,dcode,fcode,level,'
3696            'level_session,mandatory,passmark,score,semester,title,'
3697            'student_id,certcode,display_fullname\r\n1,0,COURSE1,10,'
3698            'dep1,fac1,100,2004,1,40,,1,'
3699            'Unnamed Course,K1000000,CERT1,Anna Tester\r\n')
3700
3701        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3702        job_id = self.app['datacenter'].running_exports[0][0]
3703        # Thew job can be discarded
3704        self.browser.open(course1_path + '/exports')
3705        self.browser.getControl("Discard").click()
3706        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3707        # Creation, downloading and discarding is logged
3708        logfile = os.path.join(
3709            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3710        logcontent = open(logfile).read()
3711        self.assertTrue(
3712            'zope.mgr - students.browser.CourseExportJobContainerJobConfig '
3713            '- exported: coursetickets (2004, 100, COURSE1), job_id=%s'
3714            % job_id in logcontent
3715            )
3716        self.assertTrue(
3717            'zope.mgr - students.browser.ExportJobContainerDownload '
3718            '- downloaded: WAeUP.Kofa_coursetickets_%s.csv, job_id=%s'
3719            % (job_id, job_id) in logcontent
3720            )
3721        self.assertTrue(
3722            'zope.mgr - students.browser.ExportJobContainerOverview '
3723            '- discarded: job_id=%s' % job_id in logcontent
3724            )
3725
3726    def test_export_departmet_officers(self):
3727        # Create department officer
3728        self.app['users'].addUser('mrdepartment', 'mrdepartmentsecret')
3729        self.app['users']['mrdepartment'].email = 'mrdepartment@foo.ng'
3730        self.app['users']['mrdepartment'].title = 'Carlo Pitter'
3731        # Assign local role
3732        department = self.app['faculties']['fac1']['dep1']
3733        prmlocal = IPrincipalRoleManager(department)
3734        prmlocal.assignRoleToPrincipal('waeup.local.DepartmentOfficer', 'mrdepartment')
3735        # Login as department officer
3736        self.browser.open(self.login_path)
3737        self.browser.getControl(name="form.login").value = 'mrdepartment'
3738        self.browser.getControl(name="form.password").value = 'mrdepartmentsecret'
3739        self.browser.getControl("Login").click()
3740        self.assertMatches('...You logged in...', self.browser.contents)
3741        self.browser.open("http://localhost/app/faculties/fac1/dep1")
3742        self.browser.getLink("Export student data").click()
3743        self.browser.getControl("Configure new export").click()
3744        # Only the paymentsoverview exporter is available for department officers
3745        self.assertFalse('<option value="students">' in self.browser.contents)
3746        self.assertTrue(
3747            '<option value="paymentsoverview">' in self.browser.contents)
3748        self.browser.getControl(name="exporter").value = ['paymentsoverview']
3749        self.browser.getControl(name="session").value = ['2004']
3750        self.browser.getControl(name="level").value = ['100']
3751        self.browser.getControl("Create CSV file").click()
3752        self.assertTrue('Export started' in self.browser.contents)
3753        # Thew job can be discarded
3754        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3755        #job_id = self.app['datacenter'].running_exports[0][0]
3756        job_id = self.wait_for_export_job_completed()
3757        self.browser.open("http://localhost/app/faculties/fac1/dep1/exports")
3758        self.browser.getControl("Discard").click()
3759        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3760
3761    def test_export_bursary_officers(self):
3762        # Create bursary officer
3763        self.app['users'].addUser('mrbursary', 'mrbursarysecret')
3764        self.app['users']['mrbursary'].email = 'mrbursary@foo.ng'
3765        self.app['users']['mrbursary'].title = 'Carlo Pitter'
3766        prmglobal = IPrincipalRoleManager(self.app)
3767        prmglobal.assignRoleToPrincipal('waeup.BursaryOfficer', 'mrbursary')
3768        # Login as bursary officer
3769        self.browser.open(self.login_path)
3770        self.browser.getControl(name="form.login").value = 'mrbursary'
3771        self.browser.getControl(name="form.password").value = 'mrbursarysecret'
3772        self.browser.getControl("Login").click()
3773        self.assertMatches('...You logged in...', self.browser.contents)
3774        self.browser.getLink("Academics").click()
3775        self.browser.getLink("Export student data").click()
3776        self.browser.getControl("Configure new export").click()
3777        # Only the bursary exporter is available for bursary officers
3778        # not only at facultiescontainer level ...
3779        self.assertFalse('<option value="students">' in self.browser.contents)
3780        self.assertTrue('<option value="bursary">' in self.browser.contents)
3781        self.browser.getControl(name="exporter").value = ['bursary']
3782        self.browser.getControl(name="session").value = ['2004']
3783        self.browser.getControl(name="level").value = ['100']
3784        self.browser.getControl("Create CSV file").click()
3785        self.assertTrue('Export started' in self.browser.contents)
3786        # ... but also at other levels
3787        self.browser.open('http://localhost/app/faculties/fac1/dep1')
3788        self.browser.getLink("Export student data").click()
3789        self.browser.getControl("Configure new export").click()
3790        self.assertFalse('<option value="students">' in self.browser.contents)
3791        self.assertTrue('<option value="bursary">' in self.browser.contents)
3792        # Thew job can be discarded
3793        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3794        #job_id = self.app['datacenter'].running_exports[0][0]
3795        job_id = self.wait_for_export_job_completed()
3796        self.browser.open('http://localhost/app/faculties/exports')
3797        self.browser.getControl("Discard").click()
3798        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
Note: See TracBrowser for help on using the repository browser.