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

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

Reinvent temporary/unrectified/momentary/sessional gpa as a property attribute of study levels. The sessional gpa shown on transcript slips is now called 'rectified sessional gpa'.

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