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

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

Add test for transcript request processing.

  • Property svn:keywords set to Id
File size: 172.5 KB
Line 
1## $Id: test_browser.py 10464 2013-08-07 10:21:49Z 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[1], 23)
802        # weigheted credits = 3 * 10 + 4 * 13
803        self.assertEqual(self.student['studycourse']['100'].gpa_params[2], 82.0)
804        # sgpa = 82 / 23
805        self.assertEqual(self.student['studycourse']['100'].gpa_params[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[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[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[0], 4.0)
850        # ... but not at level 200
851        self.assertEqual(self.student['studycourse']['200'].gpa_params[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[0], 3.0)
1900        self.assertEqual(self.student['studycourse']['110'].gpa_params[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        self.student.transcript_comment = (
1929            u'On 07/08/2013 08:59:54 UTC K1000000 wrote:\n\nComment line 1 \n'
1930            'Comment line2\n\nDispatch Address:\nAddress line 1 \n'
1931            'Address line2\n\n')
1932        # Create transcript officer
1933        self.app['users'].addUser('mrtranscript', 'mrtranscriptsecret')
1934        self.app['users']['mrtranscript'].email = 'mrtranscript@foo.ng'
1935        self.app['users']['mrtranscript'].title = 'Ruth Gordon'
1936        prmglobal = IPrincipalRoleManager(self.app)
1937        prmglobal.assignRoleToPrincipal('waeup.TranscriptOfficer', 'mrtranscript')
1938        # Login as transcript officer
1939        self.browser.open(self.login_path)
1940        self.browser.getControl(name="form.login").value = 'mrtranscript'
1941        self.browser.getControl(name="form.password").value = 'mrtranscriptsecret'
1942        self.browser.getControl("Login").click()
1943        self.assertMatches('...You logged in...', self.browser.contents)
1944        # Officer can see his roles
1945        self.browser.getLink("My Roles").click()
1946        self.assertMatches(
1947            '...<div>Transcript Officer</div>...',
1948            self.browser.contents)
1949        self.browser.open(self.student_path)
1950        self.browser.getLink("Manage transcript request").click()
1951        self.assertTrue(' UTC K1000000 wrote:<br><br>Comment line 1 <br>'
1952        'Comment line2<br><br>Dispatch Address:<br>Address line 1 <br>'
1953        'Address line2<br><br></p>' in self.browser.contents)
1954        self.browser.getControl(name="comment").value = (
1955            'Hello,\nYour transcript has been sent to the address provided.')
1956        self.browser.getControl("Save comment and mark as processed").click()
1957        self.assertTrue(
1958            'UTC mrtranscript wrote:\n\nHello,\nYour transcript has '
1959            'been sent to the address provided.\n\n'
1960            in self.student.transcript_comment)
1961        # The comment has been logged
1962        logfile = os.path.join(
1963            self.app['datacenter'].storage, 'logs', 'students.log')
1964        logcontent = open(logfile).read()
1965        self.assertTrue(
1966            'mrtranscript - students.browser.StudentTranscriptRequestProcessFormPage - '
1967            'K1000000 - comment: Hello,<br>'
1968            'Your transcript has been sent to the address provided'
1969            in logcontent)
1970
1971class StudentUITests(StudentsFullSetup):
1972    # Tests for Student class views and pages
1973
1974    def test_student_change_password(self):
1975        # Students can change the password
1976        self.student.personal_updated = datetime.utcnow()
1977        self.browser.open(self.login_path)
1978        self.browser.getControl(name="form.login").value = self.student_id
1979        self.browser.getControl(name="form.password").value = 'spwd'
1980        self.browser.getControl("Login").click()
1981        self.assertEqual(self.browser.url, self.student_path)
1982        self.assertTrue('You logged in' in self.browser.contents)
1983        # Change password
1984        self.browser.getLink("Change password").click()
1985        self.browser.getControl(name="change_password").value = 'pw'
1986        self.browser.getControl(
1987            name="change_password_repeat").value = 'pw'
1988        self.browser.getControl("Save").click()
1989        self.assertTrue('Password must have at least' in self.browser.contents)
1990        self.browser.getControl(name="change_password").value = 'new_password'
1991        self.browser.getControl(
1992            name="change_password_repeat").value = 'new_passssword'
1993        self.browser.getControl("Save").click()
1994        self.assertTrue('Passwords do not match' in self.browser.contents)
1995        self.browser.getControl(name="change_password").value = 'new_password'
1996        self.browser.getControl(
1997            name="change_password_repeat").value = 'new_password'
1998        self.browser.getControl("Save").click()
1999        self.assertTrue('Password changed' in self.browser.contents)
2000        # We are still logged in. Changing the password hasn't thrown us out.
2001        self.browser.getLink("Base Data").click()
2002        self.assertEqual(self.browser.url, self.student_path)
2003        # We can logout
2004        self.browser.getLink("Logout").click()
2005        self.assertTrue('You have been logged out' in self.browser.contents)
2006        self.assertEqual(self.browser.url, 'http://localhost/app')
2007        # We can login again with the new password
2008        self.browser.getLink("Login").click()
2009        self.browser.open(self.login_path)
2010        self.browser.getControl(name="form.login").value = self.student_id
2011        self.browser.getControl(name="form.password").value = 'new_password'
2012        self.browser.getControl("Login").click()
2013        self.assertEqual(self.browser.url, self.student_path)
2014        self.assertTrue('You logged in' in self.browser.contents)
2015        return
2016
2017    def test_setpassword(self):
2018        # Set password for first-time access
2019        student = Student()
2020        student.reg_number = u'123456'
2021        student.firstname = u'Klaus'
2022        student.lastname = u'Tester'
2023        self.app['students'].addStudent(student)
2024        setpassword_path = 'http://localhost/app/setpassword'
2025        student_path = 'http://localhost/app/students/%s' % student.student_id
2026        self.browser.open(setpassword_path)
2027        self.browser.getControl(name="ac_series").value = self.existing_pwdseries
2028        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
2029        self.browser.getControl(name="reg_number").value = '223456'
2030        self.browser.getControl("Set").click()
2031        self.assertMatches('...No student found...',
2032                           self.browser.contents)
2033        self.browser.getControl(name="reg_number").value = '123456'
2034        self.browser.getControl(name="ac_number").value = '999999'
2035        self.browser.getControl("Set").click()
2036        self.assertMatches('...Access code is invalid...',
2037                           self.browser.contents)
2038        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
2039        self.browser.getControl("Set").click()
2040        self.assertMatches('...Password has been set. Your Student Id is...',
2041                           self.browser.contents)
2042        self.browser.getControl("Set").click()
2043        self.assertMatches(
2044            '...Password has already been set. Your Student Id is...',
2045            self.browser.contents)
2046        existing_pwdpin = self.pwdpins[1]
2047        parts = existing_pwdpin.split('-')[1:]
2048        existing_pwdseries, existing_pwdnumber = parts
2049        self.browser.getControl(name="ac_series").value = existing_pwdseries
2050        self.browser.getControl(name="ac_number").value = existing_pwdnumber
2051        self.browser.getControl(name="reg_number").value = '123456'
2052        self.browser.getControl("Set").click()
2053        self.assertMatches(
2054            '...You are using the wrong Access Code...',
2055            self.browser.contents)
2056        # The student can login with the new credentials
2057        self.browser.open(self.login_path)
2058        self.browser.getControl(name="form.login").value = student.student_id
2059        self.browser.getControl(
2060            name="form.password").value = self.existing_pwdnumber
2061        self.browser.getControl("Login").click()
2062        self.assertEqual(self.browser.url, student_path)
2063        self.assertTrue('You logged in' in self.browser.contents)
2064        return
2065
2066    def test_student_login(self):
2067        # Student cant login if their password is not set
2068        self.student.password = None
2069        self.browser.open(self.login_path)
2070        self.browser.getControl(name="form.login").value = self.student_id
2071        self.browser.getControl(name="form.password").value = 'spwd'
2072        self.browser.getControl("Login").click()
2073        self.assertTrue(
2074            'You entered invalid credentials.' in self.browser.contents)
2075        # We set the password again
2076        IUserAccount(
2077            self.app['students'][self.student_id]).setPassword('spwd')
2078        # Students can't login if their account is suspended/deactivated
2079        self.student.suspended = True
2080        self.browser.open(self.login_path)
2081        self.browser.getControl(name="form.login").value = self.student_id
2082        self.browser.getControl(name="form.password").value = 'spwd'
2083        self.browser.getControl("Login").click()
2084        self.assertMatches(
2085            '...<div class="alert-message warning">'
2086            'Your account has been deactivated.</div>...', self.browser.contents)
2087        # If suspended_comment is set this message will be flashed instead
2088        self.student.suspended_comment = u'Aetsch baetsch!'
2089        self.browser.getControl(name="form.login").value = self.student_id
2090        self.browser.getControl(name="form.password").value = 'spwd'
2091        self.browser.getControl("Login").click()
2092        self.assertMatches(
2093            '...<div class="alert-message warning">Aetsch baetsch!</div>...',
2094            self.browser.contents)
2095        self.student.suspended = False
2096        # Students can't login if a temporary password has been set and
2097        # is not expired
2098        self.app['students'][self.student_id].setTempPassword(
2099            'anybody', 'temp_spwd')
2100        self.browser.open(self.login_path)
2101        self.browser.getControl(name="form.login").value = self.student_id
2102        self.browser.getControl(name="form.password").value = 'spwd'
2103        self.browser.getControl("Login").click()
2104        self.assertMatches(
2105            '...Your account has been temporarily deactivated...',
2106            self.browser.contents)
2107        # The student can login with the temporary password
2108        self.browser.open(self.login_path)
2109        self.browser.getControl(name="form.login").value = self.student_id
2110        self.browser.getControl(name="form.password").value = 'temp_spwd'
2111        self.browser.getControl("Login").click()
2112        self.assertMatches(
2113            '...You logged in...', self.browser.contents)
2114        # Student can view the base data
2115        self.browser.open(self.student_path)
2116        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2117        self.assertEqual(self.browser.url, self.student_path)
2118        # When the password expires ...
2119        delta = timedelta(minutes=11)
2120        self.app['students'][self.student_id].temp_password[
2121            'timestamp'] = datetime.utcnow() - delta
2122        self.app['students'][self.student_id]._p_changed = True
2123        # ... the student will be automatically logged out
2124        self.assertRaises(
2125            Unauthorized, self.browser.open, self.student_path)
2126        # Then the student can login with the original password
2127        self.browser.open(self.login_path)
2128        self.browser.getControl(name="form.login").value = self.student_id
2129        self.browser.getControl(name="form.password").value = 'spwd'
2130        self.browser.getControl("Login").click()
2131        self.assertMatches(
2132            '...You logged in...', self.browser.contents)
2133
2134    def test_student_clearance(self):
2135        # Student cant login if their password is not set
2136        IWorkflowInfo(self.student).fireTransition('admit')
2137        self.browser.open(self.login_path)
2138        self.browser.getControl(name="form.login").value = self.student_id
2139        self.browser.getControl(name="form.password").value = 'spwd'
2140        self.browser.getControl("Login").click()
2141        self.assertMatches(
2142            '...You logged in...', self.browser.contents)
2143        # Admitted student can upload a passport picture
2144        self.browser.open(self.student_path + '/change_portrait')
2145        ctrl = self.browser.getControl(name='passportuploadedit')
2146        file_obj = open(SAMPLE_IMAGE, 'rb')
2147        file_ctrl = ctrl.mech_control
2148        file_ctrl.add_file(file_obj, filename='my_photo.jpg')
2149        self.browser.getControl(
2150            name='upload_passportuploadedit').click()
2151        self.assertTrue(
2152            '<img align="middle" height="125px" src="passport.jpg" />'
2153            in self.browser.contents)
2154        # Students can open admission letter
2155        self.browser.getLink("Base Data").click()
2156        self.browser.getLink("Download admission letter").click()
2157        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2158        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2159        # Student can view the clearance data
2160        self.browser.open(self.student_path)
2161        self.browser.getLink("Clearance Data").click()
2162        # Student can't open clearance edit form before starting clearance
2163        self.browser.open(self.student_path + '/cedit')
2164        self.assertMatches('...The requested form is locked...',
2165                           self.browser.contents)
2166        self.browser.getLink("Clearance Data").click()
2167        self.browser.getLink("Start clearance").click()
2168        self.student.email = None
2169        # Uups, we forgot to fill the email fields
2170        self.browser.getControl("Start clearance").click()
2171        self.assertMatches('...Not all required fields filled...',
2172                           self.browser.contents)
2173        self.browser.open(self.student_path + '/edit_base')
2174        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
2175        self.browser.getControl("Save").click()
2176        self.browser.open(self.student_path + '/start_clearance')
2177        self.browser.getControl(name="ac_series").value = '3'
2178        self.browser.getControl(name="ac_number").value = '4444444'
2179        self.browser.getControl("Start clearance now").click()
2180        self.assertMatches('...Activation code is invalid...',
2181                           self.browser.contents)
2182        self.browser.getControl(name="ac_series").value = self.existing_clrseries
2183        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
2184        # Owner is Hans Wurst, AC can't be invalidated
2185        self.browser.getControl("Start clearance now").click()
2186        self.assertMatches('...You are not the owner of this access code...',
2187                           self.browser.contents)
2188        # Set the correct owner
2189        self.existing_clrac.owner = self.student_id
2190        # clr_code might be set (and thus returns None) due importing
2191        # an empty clr_code column.
2192        self.student.clr_code = None
2193        self.browser.getControl("Start clearance now").click()
2194        self.assertMatches('...Clearance process has been started...',
2195                           self.browser.contents)
2196        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
2197        self.browser.getControl("Save", index=0).click()
2198        # Student can view the clearance data
2199        self.browser.getLink("Clearance Data").click()
2200        # and go back to the edit form
2201        self.browser.getLink("Edit").click()
2202        # Students can upload documents
2203        ctrl = self.browser.getControl(name='birthcertificateupload')
2204        file_obj = open(SAMPLE_IMAGE, 'rb')
2205        file_ctrl = ctrl.mech_control
2206        file_ctrl.add_file(file_obj, filename='my_birth_certificate.jpg')
2207        self.browser.getControl(
2208            name='upload_birthcertificateupload').click()
2209        self.assertTrue(
2210            '<a target="image" href="birth_certificate">Birth Certificate Scan</a>'
2211            in self.browser.contents)
2212        # Students can open clearance slip
2213        self.browser.getLink("View").click()
2214        self.browser.getLink("Download clearance slip").click()
2215        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2216        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2217        # Students can request clearance
2218        self.browser.open(self.edit_clearance_path)
2219        self.browser.getControl("Save and request clearance").click()
2220        self.browser.getControl(name="ac_series").value = self.existing_clrseries
2221        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
2222        self.browser.getControl("Request clearance now").click()
2223        self.assertMatches('...Clearance has been requested...',
2224                           self.browser.contents)
2225        # Student can't reopen clearance form after requesting clearance
2226        self.browser.open(self.student_path + '/cedit')
2227        self.assertMatches('...The requested form is locked...',
2228                           self.browser.contents)
2229
2230    def test_student_course_registration(self):
2231        # Student cant login if their password is not set
2232        IWorkflowInfo(self.student).fireTransition('admit')
2233        self.browser.open(self.login_path)
2234        self.browser.getControl(name="form.login").value = self.student_id
2235        self.browser.getControl(name="form.password").value = 'spwd'
2236        self.browser.getControl("Login").click()
2237        # Student can't add study level if not in state 'school fee paid'
2238        self.browser.open(self.student_path + '/studycourse/add')
2239        self.assertMatches('...The requested form is locked...',
2240                           self.browser.contents)
2241        # ... and must be transferred first
2242        IWorkflowState(self.student).setState('school fee paid')
2243        # Now students can add the current study level
2244        self.browser.getLink("Study Course").click()
2245        self.browser.getLink("Add course list").click()
2246        self.assertMatches('...Add current level 100 (Year 1)...',
2247                           self.browser.contents)
2248        self.browser.getControl("Create course list now").click()
2249        # A level with one course ticket was created
2250        self.assertEqual(self.student['studycourse']['100'].number_of_tickets, 1)
2251        self.browser.getLink("100").click()
2252        self.browser.getLink("Edit course list").click()
2253        self.browser.getLink("here").click()
2254        self.browser.getControl(name="form.course").value = ['COURSE1']
2255        self.browser.getControl("Add course ticket").click()
2256        self.assertMatches('...The ticket exists...',
2257                           self.browser.contents)
2258        self.student['studycourse'].current_level = 200
2259        self.browser.getLink("Study Course").click()
2260        self.browser.getLink("Add course list").click()
2261        self.assertMatches('...Add current level 200 (Year 2)...',
2262                           self.browser.contents)
2263        self.browser.getControl("Create course list now").click()
2264        self.browser.getLink("200").click()
2265        self.browser.getLink("Edit course list").click()
2266        self.browser.getLink("here").click()
2267        self.browser.getControl(name="form.course").value = ['COURSE1']
2268        self.course.credits = 100
2269        self.browser.getControl("Add course ticket").click()
2270        self.assertMatches(
2271            '...Total credits exceed 50...', self.browser.contents)
2272        self.course.credits = 10
2273        self.browser.getControl("Add course ticket").click()
2274        self.assertMatches('...The ticket exists...',
2275                           self.browser.contents)
2276        # Indeed the ticket exists as carry-over course from level 100
2277        # since its score was 0
2278        self.assertTrue(
2279            self.student['studycourse']['200']['COURSE1'].carry_over is True)
2280        # Students can open the pdf course registration slip
2281        self.browser.open(self.student_path + '/studycourse/200')
2282        self.browser.getLink("Download course registration slip").click()
2283        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2284        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2285        # Students can remove course tickets
2286        self.browser.open(self.student_path + '/studycourse/200/edit')
2287        self.browser.getControl("Remove selected", index=0).click()
2288        self.assertTrue('No ticket selected' in self.browser.contents)
2289        # No ticket can be selected since the carry-over course is a core course
2290        self.assertRaises(
2291            LookupError, self.browser.getControl, name='val_id')
2292        self.student['studycourse']['200']['COURSE1'].mandatory = False
2293        self.browser.open(self.student_path + '/studycourse/200/edit')
2294        # Course list can't be registered if total_credits exceeds max_credits
2295        self.student['studycourse']['200']['COURSE1'].credits = 60
2296        self.browser.getControl("Register course list").click()
2297        self.assertTrue('Maximum credits of 50 exceeded' in self.browser.contents)
2298        # Student can now remove the ticket
2299        ctrl = self.browser.getControl(name='val_id')
2300        ctrl.getControl(value='COURSE1').selected = True
2301        self.browser.getControl("Remove selected", index=0).click()
2302        self.assertTrue('Successfully removed' in self.browser.contents)
2303        # Removing course tickets is properly logged
2304        logfile = os.path.join(
2305            self.app['datacenter'].storage, 'logs', 'students.log')
2306        logcontent = open(logfile).read()
2307        self.assertTrue('K1000000 - students.browser.StudyLevelEditFormPage '
2308        '- K1000000 - removed: COURSE1 at 200' in logcontent)
2309        # They can add the same ticket using the edit page directly.
2310        # We can do the same by adding the course on the manage page directly
2311        self.browser.getControl(name="course").value = 'COURSE1'
2312        self.browser.getControl("Add course ticket").click()
2313        # Adding course tickets is logged
2314        logfile = os.path.join(
2315            self.app['datacenter'].storage, 'logs', 'students.log')
2316        logcontent = open(logfile).read()
2317        self.assertTrue('K1000000 - students.browser.StudyLevelEditFormPage - '
2318            'K1000000 - added: COURSE1|200|2004' in logcontent)
2319        # Course list can be registered
2320        self.browser.getControl("Register course list").click()
2321        self.assertTrue('Course list has been registered' in self.browser.contents)
2322        self.assertEqual(self.student.state, 'courses registered')
2323        # Students can view the transcript
2324        #self.browser.open(self.studycourse_path)
2325        #self.browser.getLink("Transcript").click()
2326        #self.browser.getLink("Academic Transcript").click()
2327        #self.assertEqual(self.browser.headers['Status'], '200 Ok')
2328        #self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2329        return
2330
2331    def test_postgraduate_student_access(self):
2332        self.certificate.study_mode = 'pg_ft'
2333        self.certificate.start_level = 999
2334        self.certificate.end_level = 999
2335        self.student['studycourse'].current_level = 999
2336        IWorkflowState(self.student).setState('school fee paid')
2337        self.browser.open(self.login_path)
2338        self.browser.getControl(name="form.login").value = self.student_id
2339        self.browser.getControl(name="form.password").value = 'spwd'
2340        self.browser.getControl("Login").click()
2341        self.assertTrue(
2342            'You logged in.' in self.browser.contents)
2343        # Now students can add the current study level
2344        self.browser.getLink("Study Course").click()
2345        self.browser.getLink("Add course list").click()
2346        self.assertMatches('...Add current level Postgraduate Level...',
2347                           self.browser.contents)
2348        self.browser.getControl("Create course list now").click()
2349        # A level with one course ticket was created
2350        self.assertEqual(self.student['studycourse']['999'].number_of_tickets, 0)
2351        self.browser.getLink("999").click()
2352        self.browser.getLink("Edit course list").click()
2353        self.browser.getLink("here").click()
2354        self.browser.getControl(name="form.course").value = ['COURSE1']
2355        self.browser.getControl("Add course ticket").click()
2356        self.assertMatches('...Successfully added COURSE1...',
2357                           self.browser.contents)
2358        # Postgraduate students can't register course lists
2359        self.browser.getControl("Register course list").click()
2360        self.assertTrue("your course list can't bee registered"
2361            in self.browser.contents)
2362        self.assertEqual(self.student.state, 'school fee paid')
2363        return
2364
2365    def test_student_clearance_wo_clrcode(self):
2366        IWorkflowState(self.student).setState('clearance started')
2367        self.browser.open(self.login_path)
2368        self.browser.getControl(name="form.login").value = self.student_id
2369        self.browser.getControl(name="form.password").value = 'spwd'
2370        self.browser.getControl("Login").click()
2371        self.student.clearance_locked = False
2372        self.browser.open(self.edit_clearance_path)
2373        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
2374        self.browser.getControl("Save and request clearance").click()
2375        self.assertMatches('...Clearance has been requested...',
2376                           self.browser.contents)
2377
2378    def test_student_clearance_payment(self):
2379        # Login
2380        self.browser.open(self.login_path)
2381        self.browser.getControl(name="form.login").value = self.student_id
2382        self.browser.getControl(name="form.password").value = 'spwd'
2383        self.browser.getControl("Login").click()
2384
2385        # Students can add online clearance payment tickets
2386        self.browser.open(self.payments_path + '/addop')
2387        self.browser.getControl(name="form.p_category").value = ['clearance']
2388        self.browser.getControl("Create ticket").click()
2389        self.assertMatches('...ticket created...',
2390                           self.browser.contents)
2391
2392        # Students can't approve the payment
2393        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
2394        ctrl = self.browser.getControl(name='val_id')
2395        value = ctrl.options[0]
2396        self.browser.getLink(value).click()
2397        payment_url = self.browser.url
2398        self.assertRaises(
2399            Unauthorized, self.browser.open, payment_url + '/approve')
2400        # In the base package they can 'use' a fake approval view.
2401        # XXX: I tried to use
2402        # self.student['payments'][value].approveStudentPayment() instead.
2403        # But this function fails in
2404        # w.k.accesscodes.accesscode.create_accesscode.
2405        # grok.getSite returns None in tests.
2406        self.browser.open(payment_url + '/fake_approve')
2407        self.assertMatches('...Payment approved...',
2408                          self.browser.contents)
2409        expected = '''...
2410        <td>
2411          <span>Paid</span>
2412        </td>...'''
2413        expected = '''...
2414        <td>
2415          <span>Paid</span>
2416        </td>...'''
2417        self.assertMatches(expected,self.browser.contents)
2418        payment_id = self.student['payments'].keys()[0]
2419        payment = self.student['payments'][payment_id]
2420        self.assertEqual(payment.p_state, 'paid')
2421        self.assertEqual(payment.r_amount_approved, 3456.0)
2422        self.assertEqual(payment.r_code, 'AP')
2423        self.assertEqual(payment.r_desc, u'Payment approved by Anna Tester')
2424        # The new CLR-0 pin has been created
2425        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
2426        pin = self.app['accesscodes']['CLR-0'].keys()[0]
2427        ac = self.app['accesscodes']['CLR-0'][pin]
2428        self.assertEqual(ac.owner, self.student_id)
2429        self.assertEqual(ac.cost, 3456.0)
2430
2431        # Students can open the pdf payment slip
2432        self.browser.open(payment_url + '/payment_slip.pdf')
2433        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2434        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2435
2436        # The new CLR-0 pin can be used for starting clearance
2437        # but they have to upload a passport picture first
2438        # which is only possible in state admitted
2439        self.browser.open(self.student_path + '/change_portrait')
2440        self.assertMatches('...form is locked...',
2441                          self.browser.contents)
2442        IWorkflowInfo(self.student).fireTransition('admit')
2443        self.browser.open(self.student_path + '/change_portrait')
2444        image = open(SAMPLE_IMAGE, 'rb')
2445        ctrl = self.browser.getControl(name='passportuploadedit')
2446        file_ctrl = ctrl.mech_control
2447        file_ctrl.add_file(image, filename='my_photo.jpg')
2448        self.browser.getControl(
2449            name='upload_passportuploadedit').click()
2450        self.browser.open(self.student_path + '/start_clearance')
2451        parts = pin.split('-')[1:]
2452        clrseries, clrnumber = parts
2453        self.browser.getControl(name="ac_series").value = clrseries
2454        self.browser.getControl(name="ac_number").value = clrnumber
2455        self.browser.getControl("Start clearance now").click()
2456        self.assertMatches('...Clearance process has been started...',
2457                           self.browser.contents)
2458
2459    def test_student_schoolfee_payment(self):
2460        configuration = createObject('waeup.SessionConfiguration')
2461        configuration.academic_session = 2005
2462        self.app['configuration'].addSessionConfiguration(configuration)
2463        # Login
2464        self.browser.open(self.login_path)
2465        self.browser.getControl(name="form.login").value = self.student_id
2466        self.browser.getControl(name="form.password").value = 'spwd'
2467        self.browser.getControl("Login").click()
2468
2469        # Students can add online school fee payment tickets.
2470        IWorkflowState(self.student).setState('returning')
2471        self.browser.open(self.payments_path)
2472        self.assertRaises(
2473            LookupError, self.browser.getControl, name='val_id')
2474        self.browser.getLink("Add current session payment ticket").click()
2475        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2476        self.browser.getControl("Create ticket").click()
2477        self.assertMatches('...ticket created...',
2478                           self.browser.contents)
2479        ctrl = self.browser.getControl(name='val_id')
2480        value = ctrl.options[0]
2481        self.browser.getLink(value).click()
2482        self.assertMatches('...Amount Authorized...',
2483                           self.browser.contents)
2484        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2485        # Payment session and will be calculated as defined
2486        # in w.k.students.utils because we set changed the state
2487        # to returning
2488        self.assertEqual(self.student['payments'][value].p_session, 2005)
2489        self.assertEqual(self.student['payments'][value].p_level, 200)
2490
2491        # Student is the payer of the payment ticket.
2492        payer = IPayer(self.student['payments'][value])
2493        self.assertEqual(payer.display_fullname, 'Anna Tester')
2494        self.assertEqual(payer.id, self.student_id)
2495        self.assertEqual(payer.faculty, 'fac1')
2496        self.assertEqual(payer.department, 'dep1')
2497
2498        # We simulate the approval
2499        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
2500        self.browser.open(self.browser.url + '/fake_approve')
2501        self.assertMatches('...Payment approved...',
2502                          self.browser.contents)
2503
2504        # The new SFE-0 pin can be used for starting new session
2505        self.browser.open(self.studycourse_path)
2506        self.browser.getLink('Start new session').click()
2507        pin = self.app['accesscodes']['SFE-0'].keys()[0]
2508        parts = pin.split('-')[1:]
2509        sfeseries, sfenumber = parts
2510        self.browser.getControl(name="ac_series").value = sfeseries
2511        self.browser.getControl(name="ac_number").value = sfenumber
2512        self.browser.getControl("Start now").click()
2513        self.assertMatches('...Session started...',
2514                           self.browser.contents)
2515        self.assertTrue(self.student.state == 'school fee paid')
2516        return
2517
2518    def test_student_bedallocation_payment(self):
2519        # Login
2520        self.browser.open(self.login_path)
2521        self.browser.getControl(name="form.login").value = self.student_id
2522        self.browser.getControl(name="form.password").value = 'spwd'
2523        self.browser.getControl("Login").click()
2524        self.browser.open(self.payments_path)
2525        self.browser.open(self.payments_path + '/addop')
2526        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
2527        self.browser.getControl("Create ticket").click()
2528        self.assertMatches('...ticket created...',
2529                           self.browser.contents)
2530        # Students can remove only online payment tickets which have
2531        # not received a valid callback
2532        self.browser.open(self.payments_path)
2533        ctrl = self.browser.getControl(name='val_id')
2534        value = ctrl.options[0]
2535        ctrl.getControl(value=value).selected = True
2536        self.browser.getControl("Remove selected", index=0).click()
2537        self.assertTrue('Successfully removed' in self.browser.contents)
2538
2539    def test_student_maintenance_payment(self):
2540        # Login
2541        self.browser.open(self.login_path)
2542        self.browser.getControl(name="form.login").value = self.student_id
2543        self.browser.getControl(name="form.password").value = 'spwd'
2544        self.browser.getControl("Login").click()
2545        self.browser.open(self.payments_path)
2546        self.browser.open(self.payments_path + '/addop')
2547        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
2548        self.browser.getControl("Create ticket").click()
2549        self.assertMatches('...You have not yet booked accommodation...',
2550                           self.browser.contents)
2551        # We continue this test in test_student_accommodation
2552
2553    def test_student_previous_payments(self):
2554        configuration = createObject('waeup.SessionConfiguration')
2555        configuration.academic_session = 2000
2556        configuration.clearance_fee = 3456.0
2557        configuration.booking_fee = 123.4
2558        self.app['configuration'].addSessionConfiguration(configuration)
2559        configuration2 = createObject('waeup.SessionConfiguration')
2560        configuration2.academic_session = 2003
2561        configuration2.clearance_fee = 3456.0
2562        configuration2.booking_fee = 123.4
2563        self.app['configuration'].addSessionConfiguration(configuration2)
2564        configuration3 = createObject('waeup.SessionConfiguration')
2565        configuration3.academic_session = 2005
2566        configuration3.clearance_fee = 3456.0
2567        configuration3.booking_fee = 123.4
2568        self.app['configuration'].addSessionConfiguration(configuration3)
2569        self.student['studycourse'].entry_session = 2002
2570
2571        # Login
2572        self.browser.open(self.login_path)
2573        self.browser.getControl(name="form.login").value = self.student_id
2574        self.browser.getControl(name="form.password").value = 'spwd'
2575        self.browser.getControl("Login").click()
2576
2577        # Students can add previous school fee payment tickets in any state.
2578        IWorkflowState(self.student).setState('courses registered')
2579        self.browser.open(self.payments_path)
2580        self.browser.getLink("Add previous session payment ticket").click()
2581
2582        # Previous session payment form is provided
2583        self.assertEqual(self.student.current_session, 2004)
2584        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2585        self.browser.getControl(name="form.p_session").value = ['2000']
2586        self.browser.getControl(name="form.p_level").value = ['300']
2587        self.browser.getControl("Create ticket").click()
2588        self.assertMatches('...The previous session must not fall below...',
2589                           self.browser.contents)
2590        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2591        self.browser.getControl(name="form.p_session").value = ['2005']
2592        self.browser.getControl(name="form.p_level").value = ['300']
2593        self.browser.getControl("Create ticket").click()
2594        self.assertMatches('...This is not a previous session...',
2595                           self.browser.contents)
2596        # Students can pay current session school fee.
2597        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2598        self.browser.getControl(name="form.p_session").value = ['2004']
2599        self.browser.getControl(name="form.p_level").value = ['300']
2600        self.browser.getControl("Create ticket").click()
2601        self.assertMatches('...ticket created...',
2602                           self.browser.contents)
2603        ctrl = self.browser.getControl(name='val_id')
2604        value = ctrl.options[0]
2605        self.browser.getLink(value).click()
2606        self.assertMatches('...Amount Authorized...',
2607                           self.browser.contents)
2608        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2609
2610        # Payment session is properly set
2611        self.assertEqual(self.student['payments'][value].p_session, 2004)
2612        self.assertEqual(self.student['payments'][value].p_level, 300)
2613
2614        # We simulate the approval
2615        self.browser.open(self.browser.url + '/fake_approve')
2616        self.assertMatches('...Payment approved...',
2617                          self.browser.contents)
2618
2619        # No AC has been created
2620        self.assertEqual(len(self.app['accesscodes']['SFE-0'].keys()), 0)
2621        self.assertTrue(self.student['payments'][value].ac is None)
2622
2623        # Current payment flag is set False
2624        self.assertFalse(self.student['payments'][value].p_current)
2625
2626        # Button and form are not available for students who are in
2627        # states up to cleared
2628        self.student['studycourse'].entry_session = 2004
2629        IWorkflowState(self.student).setState('cleared')
2630        self.browser.open(self.payments_path)
2631        self.assertFalse(
2632            "Add previous session payment ticket" in self.browser.contents)
2633        self.browser.open(self.payments_path + '/addpp')
2634        self.assertTrue(
2635            "No previous payment to be made" in self.browser.contents)
2636        return
2637
2638    def test_postgraduate_student_payments(self):
2639        configuration = createObject('waeup.SessionConfiguration')
2640        configuration.academic_session = 2005
2641        self.app['configuration'].addSessionConfiguration(configuration)
2642        self.certificate.study_mode = 'pg_ft'
2643        self.certificate.start_level = 999
2644        self.certificate.end_level = 999
2645        self.student['studycourse'].current_level = 999
2646        # Login
2647        self.browser.open(self.login_path)
2648        self.browser.getControl(name="form.login").value = self.student_id
2649        self.browser.getControl(name="form.password").value = 'spwd'
2650        self.browser.getControl("Login").click()
2651        # Students can add online school fee payment tickets.
2652        IWorkflowState(self.student).setState('cleared')
2653        self.browser.open(self.payments_path)
2654        self.browser.getLink("Add current session payment ticket").click()
2655        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2656        self.browser.getControl("Create ticket").click()
2657        self.assertMatches('...ticket created...',
2658                           self.browser.contents)
2659        ctrl = self.browser.getControl(name='val_id')
2660        value = ctrl.options[0]
2661        self.browser.getLink(value).click()
2662        self.assertMatches('...Amount Authorized...',
2663                           self.browser.contents)
2664        # Payment session and level are current ones.
2665        # Postgrads have to pay school_fee_1.
2666        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
2667        self.assertEqual(self.student['payments'][value].p_session, 2004)
2668        self.assertEqual(self.student['payments'][value].p_level, 999)
2669
2670        # We simulate the approval
2671        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
2672        self.browser.open(self.browser.url + '/fake_approve')
2673        self.assertMatches('...Payment approved...',
2674                          self.browser.contents)
2675
2676        # The new SFE-0 pin can be used for starting session
2677        self.browser.open(self.studycourse_path)
2678        self.browser.getLink('Start new session').click()
2679        pin = self.app['accesscodes']['SFE-0'].keys()[0]
2680        parts = pin.split('-')[1:]
2681        sfeseries, sfenumber = parts
2682        self.browser.getControl(name="ac_series").value = sfeseries
2683        self.browser.getControl(name="ac_number").value = sfenumber
2684        self.browser.getControl("Start now").click()
2685        self.assertMatches('...Session started...',
2686                           self.browser.contents)
2687        self.assertTrue(self.student.state == 'school fee paid')
2688
2689        # Postgrad students do not need to register courses the
2690        # can just pay for the next session.
2691        self.browser.open(self.payments_path)
2692        # Remove first payment to be sure that we access the right ticket
2693        del self.student['payments'][value]
2694        self.browser.getLink("Add current session payment ticket").click()
2695        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2696        self.browser.getControl("Create ticket").click()
2697        ctrl = self.browser.getControl(name='val_id')
2698        value = ctrl.options[0]
2699        self.browser.getLink(value).click()
2700        # Payment session has increased by one, payment level remains the same.
2701        # Returning Postgraduates have to pay school_fee_2.
2702        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2703        self.assertEqual(self.student['payments'][value].p_session, 2005)
2704        self.assertEqual(self.student['payments'][value].p_level, 999)
2705
2706        # Student is still in old session
2707        self.assertEqual(self.student.current_session, 2004)
2708
2709        # We do not need to pay the ticket if any other
2710        # SFE pin is provided
2711        pin_container = self.app['accesscodes']
2712        pin_container.createBatch(
2713            datetime.utcnow(), 'some_userid', 'SFE', 9.99, 1)
2714        pin = pin_container['SFE-1'].values()[0].representation
2715        sfeseries, sfenumber = pin.split('-')[1:]
2716        # The new SFE-1 pin can be used for starting new session
2717        self.browser.open(self.studycourse_path)
2718        self.browser.getLink('Start new session').click()
2719        self.browser.getControl(name="ac_series").value = sfeseries
2720        self.browser.getControl(name="ac_number").value = sfenumber
2721        self.browser.getControl("Start now").click()
2722        self.assertMatches('...Session started...',
2723                           self.browser.contents)
2724        self.assertTrue(self.student.state == 'school fee paid')
2725        # Student is in new session
2726        self.assertEqual(self.student.current_session, 2005)
2727        self.assertEqual(self.student['studycourse'].current_level, 999)
2728        return
2729
2730    def test_student_accommodation(self):
2731        # Login
2732        self.browser.open(self.login_path)
2733        self.browser.getControl(name="form.login").value = self.student_id
2734        self.browser.getControl(name="form.password").value = 'spwd'
2735        self.browser.getControl("Login").click()
2736
2737        # Students can add online booking fee payment tickets and open the
2738        # callback view (see test_manage_payments)
2739        self.browser.getLink("Payments").click()
2740        self.browser.getLink("Add current session payment ticket").click()
2741        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
2742        self.browser.getControl("Create ticket").click()
2743        ctrl = self.browser.getControl(name='val_id')
2744        value = ctrl.options[0]
2745        self.browser.getLink(value).click()
2746        self.browser.open(self.browser.url + '/fake_approve')
2747        # The new HOS-0 pin has been created
2748        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
2749        pin = self.app['accesscodes']['HOS-0'].keys()[0]
2750        ac = self.app['accesscodes']['HOS-0'][pin]
2751        parts = pin.split('-')[1:]
2752        sfeseries, sfenumber = parts
2753
2754        # Students can use HOS code and book a bed space with it ...
2755        self.browser.open(self.acco_path)
2756        # ... but not if booking period has expired ...
2757        self.app['hostels'].enddate = datetime.now(pytz.utc)
2758        self.browser.getLink("Book accommodation").click()
2759        self.assertMatches('...Outside booking period: ...',
2760                           self.browser.contents)
2761        self.app['hostels'].enddate = datetime.now(pytz.utc) + timedelta(days=10)
2762        # ... or student is not the an allowed state ...
2763        self.browser.getLink("Book accommodation").click()
2764        self.assertMatches('...You are in the wrong...',
2765                           self.browser.contents)
2766        IWorkflowInfo(self.student).fireTransition('admit')
2767        self.browser.getLink("Book accommodation").click()
2768        self.assertMatches('...Activation Code:...',
2769                           self.browser.contents)
2770        # Student can't used faked ACs ...
2771        self.browser.getControl(name="ac_series").value = u'nonsense'
2772        self.browser.getControl(name="ac_number").value = sfenumber
2773        self.browser.getControl("Create bed ticket").click()
2774        self.assertMatches('...Activation code is invalid...',
2775                           self.browser.contents)
2776        # ... or ACs owned by somebody else.
2777        ac.owner = u'Anybody'
2778        self.browser.getControl(name="ac_series").value = sfeseries
2779        self.browser.getControl(name="ac_number").value = sfenumber
2780        self.browser.getControl("Create bed ticket").click()
2781        self.assertMatches('...You are not the owner of this access code...',
2782                           self.browser.contents)
2783        ac.owner = self.student_id
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('...Hall 1, Block A, Room 101, Bed A...',
2788                           self.browser.contents)
2789
2790        # Bed has been allocated
2791        bed = self.app['hostels']['hall-1']['hall-1_A_101_A']
2792        self.assertTrue(bed.owner == self.student_id)
2793
2794        # BedTicketAddPage is now blocked
2795        self.browser.getLink("Book accommodation").click()
2796        self.assertMatches('...You already booked a bed space...',
2797            self.browser.contents)
2798
2799        # The bed ticket displays the data correctly
2800        self.browser.open(self.acco_path + '/2004')
2801        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
2802                           self.browser.contents)
2803        self.assertMatches('...2004/2005...', self.browser.contents)
2804        self.assertMatches('...regular_male_fr...', self.browser.contents)
2805        self.assertMatches('...%s...' % pin, self.browser.contents)
2806
2807        # Students can open the pdf slip
2808        self.browser.open(self.browser.url + '/bed_allocation_slip.pdf')
2809        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2810        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2811
2812        # Students can't relocate themselves
2813        self.assertFalse('Relocate' in self.browser.contents)
2814        relocate_path = self.acco_path + '/2004/relocate'
2815        self.assertRaises(
2816            Unauthorized, self.browser.open, relocate_path)
2817
2818        # Students can't the Remove button and check boxes
2819        self.browser.open(self.acco_path)
2820        self.assertFalse('Remove' in self.browser.contents)
2821        self.assertFalse('val_id' in self.browser.contents)
2822
2823        # Students can pay maintenance fee now
2824        self.browser.open(self.payments_path)
2825        self.browser.open(self.payments_path + '/addop')
2826        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
2827        self.browser.getControl("Create ticket").click()
2828        self.assertMatches('...Payment ticket created...',
2829                           self.browser.contents)
2830        return
2831
2832    def test_change_password_request(self):
2833        self.browser.open('http://localhost/app/changepw')
2834        self.browser.getControl(name="form.identifier").value = '123'
2835        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
2836        self.browser.getControl("Send login credentials").click()
2837        self.assertTrue('An email with' in self.browser.contents)
2838
2839    def test_student_expired_personal_data(self):
2840        # Login
2841        IWorkflowState(self.student).setState('school fee paid')
2842        delta = timedelta(days=180)
2843        self.student.personal_updated = datetime.utcnow() - delta
2844        self.browser.open(self.login_path)
2845        self.browser.getControl(name="form.login").value = self.student_id
2846        self.browser.getControl(name="form.password").value = 'spwd'
2847        self.browser.getControl("Login").click()
2848        self.assertEqual(self.browser.url, self.student_path)
2849        self.assertTrue(
2850            'You logged in' in self.browser.contents)
2851        # Students don't see personal_updated field in edit form
2852        self.browser.open(self.edit_personal_path)
2853        self.assertFalse('Updated' in self.browser.contents)
2854        self.browser.open(self.personal_path)
2855        self.assertTrue('Updated' in self.browser.contents)
2856        self.browser.getLink("Logout").click()
2857        delta = timedelta(days=181)
2858        self.student.personal_updated = datetime.utcnow() - delta
2859        self.browser.open(self.login_path)
2860        self.browser.getControl(name="form.login").value = self.student_id
2861        self.browser.getControl(name="form.password").value = 'spwd'
2862        self.browser.getControl("Login").click()
2863        self.assertEqual(self.browser.url, self.edit_personal_path)
2864        self.assertTrue(
2865            'Your personal data record is outdated.' in self.browser.contents)
2866
2867    def test_setReturningData(self):
2868        utils = getUtility(IStudentsUtils)
2869        self.student['studycourse'].current_level = 600
2870        utils.setReturningData(self.student)
2871        # The new level exceeds the certificates end_level.
2872        # In this case current_level remains unchanged and no error is raised.
2873        self.assertEqual(self.student['studycourse'].current_level, 600)
2874
2875    def test_request_transcript(self):
2876        IWorkflowState(self.student).setState('graduated')
2877        self.browser.open(self.login_path)
2878        self.browser.getControl(name="form.login").value = self.student_id
2879        self.browser.getControl(name="form.password").value = 'spwd'
2880        self.browser.getControl("Login").click()
2881        self.assertMatches(
2882            '...You logged in...', self.browser.contents)
2883        # Create payment ticket
2884        self.browser.open(self.payments_path)
2885        self.browser.open(self.payments_path + '/addop')
2886        self.browser.getControl(name="form.p_category").value = ['transcript']
2887        self.browser.getControl("Create ticket").click()
2888        ctrl = self.browser.getControl(name='val_id')
2889        value = ctrl.options[0]
2890        self.browser.getLink(value).click()
2891        self.assertMatches('...Amount Authorized...',
2892                           self.browser.contents)
2893        self.assertEqual(self.student['payments'][value].amount_auth, 4567.0)
2894        # Student is the payer of the payment ticket.
2895        payer = IPayer(self.student['payments'][value])
2896        self.assertEqual(payer.display_fullname, 'Anna Tester')
2897        self.assertEqual(payer.id, self.student_id)
2898        self.assertEqual(payer.faculty, 'fac1')
2899        self.assertEqual(payer.department, 'dep1')
2900        # We simulate the approval and fetch the pin
2901        self.assertEqual(len(self.app['accesscodes']['TSC-0']),0)
2902        self.browser.open(self.browser.url + '/fake_approve')
2903        self.assertMatches('...Payment approved...',
2904                          self.browser.contents)
2905        pin = self.app['accesscodes']['TSC-0'].keys()[0]
2906        parts = pin.split('-')[1:]
2907        tscseries, tscnumber = parts
2908        # Student can use the pin to send the transcript request
2909        self.browser.open(self.student_path)
2910        self.browser.getLink("Request transcript").click()
2911        self.browser.getControl(name="ac_series").value = tscseries
2912        self.browser.getControl(name="ac_number").value = tscnumber
2913        self.browser.getControl(name="comment").value = 'Comment line 1 \nComment line2'
2914        self.browser.getControl(name="address").value = 'Address line 1 \nAddress line2'
2915        self.browser.getControl("Request transcript now").click()
2916        self.assertMatches('...Transcript processing has been started...',
2917                          self.browser.contents)
2918        self.assertEqual(self.student.state, 'transcript requested')
2919        self.assertMatches(
2920            '... UTC K1000000 wrote:\n\nComment line 1 \n'
2921            'Comment line2\n\nDispatch Address:\nAddress line 1 \n'
2922            'Address line2\n\n', self.student.transcript_comment)
2923        # The comment has been logged
2924        logfile = os.path.join(
2925            self.app['datacenter'].storage, 'logs', 'students.log')
2926        logcontent = open(logfile).read()
2927        self.assertTrue(
2928            'K1000000 - students.browser.StudentTranscriptRequestPage - '
2929            'K1000000 - comment: Comment line 1 <br>Comment line2\n'
2930            in logcontent)
2931
2932class StudentRequestPWTests(StudentsFullSetup):
2933    # Tests for student registration
2934
2935    layer = FunctionalLayer
2936
2937    def test_request_pw(self):
2938        # Student with wrong number can't be found.
2939        self.browser.open('http://localhost/app/requestpw')
2940        self.browser.getControl(name="form.firstname").value = 'Anna'
2941        self.browser.getControl(name="form.number").value = 'anynumber'
2942        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
2943        self.browser.getControl("Send login credentials").click()
2944        self.assertTrue('No student record found.'
2945            in self.browser.contents)
2946        # Anonymous is not informed that firstname verification failed.
2947        # It seems that the record doesn't exist.
2948        self.browser.open('http://localhost/app/requestpw')
2949        self.browser.getControl(name="form.firstname").value = 'Johnny'
2950        self.browser.getControl(name="form.number").value = '123'
2951        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
2952        self.browser.getControl("Send login credentials").click()
2953        self.assertTrue('No student record found.'
2954            in self.browser.contents)
2955        # Even with the correct firstname we can't register if a
2956        # password has been set and used.
2957        self.browser.getControl(name="form.firstname").value = 'Anna'
2958        self.browser.getControl(name="form.number").value = '123'
2959        self.browser.getControl("Send login credentials").click()
2960        self.assertTrue('Your password has already been set and used.'
2961            in self.browser.contents)
2962        self.browser.open('http://localhost/app/requestpw')
2963        self.app['students'][self.student_id].password = None
2964        # The firstname field, used for verification, is not case-sensitive.
2965        self.browser.getControl(name="form.firstname").value = 'aNNa'
2966        self.browser.getControl(name="form.number").value = '123'
2967        self.browser.getControl(name="form.email").value = 'new@yy.zz'
2968        self.browser.getControl("Send login credentials").click()
2969        # Yeah, we succeded ...
2970        self.assertTrue('Your password request was successful.'
2971            in self.browser.contents)
2972        # We can also use the matric_number instead.
2973        self.browser.open('http://localhost/app/requestpw')
2974        self.browser.getControl(name="form.firstname").value = 'aNNa'
2975        self.browser.getControl(name="form.number").value = '234'
2976        self.browser.getControl(name="form.email").value = 'new@yy.zz'
2977        self.browser.getControl("Send login credentials").click()
2978        self.assertTrue('Your password request was successful.'
2979            in self.browser.contents)
2980        # ... and  student can be found in the catalog via the email address
2981        cat = queryUtility(ICatalog, name='students_catalog')
2982        results = list(
2983            cat.searchResults(
2984            email=('new@yy.zz', 'new@yy.zz')))
2985        self.assertEqual(self.student,results[0])
2986        logfile = os.path.join(
2987            self.app['datacenter'].storage, 'logs', 'main.log')
2988        logcontent = open(logfile).read()
2989        self.assertTrue('zope.anybody - students.browser.StudentRequestPasswordPage - '
2990                        '234 (K1000000) - new@yy.zz' in logcontent)
2991        return
2992
2993    def test_student_locked_level_forms(self):
2994
2995        # Add two study levels, one current and one previous
2996        studylevel = createObject(u'waeup.StudentStudyLevel')
2997        studylevel.level = 100
2998        self.student['studycourse'].addStudentStudyLevel(
2999            self.certificate, studylevel)
3000        studylevel = createObject(u'waeup.StudentStudyLevel')
3001        studylevel.level = 200
3002        self.student['studycourse'].addStudentStudyLevel(
3003            self.certificate, studylevel)
3004        IWorkflowState(self.student).setState('school fee paid')
3005        self.student['studycourse'].current_level = 200
3006
3007        self.browser.open(self.login_path)
3008        self.browser.getControl(name="form.login").value = self.student_id
3009        self.browser.getControl(name="form.password").value = 'spwd'
3010        self.browser.getControl("Login").click()
3011
3012        self.browser.open(self.student_path + '/studycourse/200/edit')
3013        self.assertFalse('The requested form is locked' in self.browser.contents)
3014        self.browser.open(self.student_path + '/studycourse/100/edit')
3015        self.assertTrue('The requested form is locked' in self.browser.contents)
3016
3017        self.browser.open(self.student_path + '/studycourse/200/ctadd')
3018        self.assertFalse('The requested form is locked' in self.browser.contents)
3019        self.browser.open(self.student_path + '/studycourse/100/ctadd')
3020        self.assertTrue('The requested form is locked' in self.browser.contents)
3021
3022        IWorkflowState(self.student).setState('courses registered')
3023        self.browser.open(self.student_path + '/studycourse/200/edit')
3024        self.assertTrue('The requested form is locked' in self.browser.contents)
3025        self.browser.open(self.student_path + '/studycourse/200/ctadd')
3026        self.assertTrue('The requested form is locked' in self.browser.contents)
3027
3028
3029class PublicPagesTests(StudentsFullSetup):
3030    # Tests for simple webservices
3031
3032    layer = FunctionalLayer
3033
3034    def test_paymentrequest(self):
3035        payment = createObject('waeup.StudentOnlinePayment')
3036        payment.p_category = u'schoolfee'
3037        payment.p_session = self.student.current_session
3038        payment.p_item = u'My Certificate'
3039        payment.p_id = u'anyid'
3040        self.student['payments']['anykey'] = payment
3041        # Request information about unpaid payment ticket
3042        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
3043        self.assertEqual(self.browser.contents, '-1')
3044        # Request information about paid payment ticket
3045        payment.p_state = u'paid'
3046        notify(grok.ObjectModifiedEvent(payment))
3047        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
3048        self.assertEqual(self.browser.contents,
3049            'FULL_NAME=Anna Tester&FACULTY=fac1&DEPARTMENT=dep1'
3050            '&PAYMENT_ITEM=My Certificate&PAYMENT_CATEGORY=School Fee'
3051            '&ACADEMIC_SESSION=2004/2005&MATRIC_NUMBER=234&REG_NUMBER=123'
3052            '&FEE_AMOUNT=0.0')
3053        self.browser.open('http://localhost/app/paymentrequest?NONSENSE=nonsense')
3054        self.assertEqual(self.browser.contents, '-1')
3055        self.browser.open('http://localhost/app/paymentrequest?P_ID=nonsense')
3056        self.assertEqual(self.browser.contents, '-1')
3057
3058class StudentDataExportTests(StudentsFullSetup, FunctionalAsyncTestCase):
3059    # Tests for StudentsContainer class views and pages
3060
3061    layer = FunctionalLayer
3062
3063    def wait_for_export_job_completed(self):
3064        # helper function waiting until the current export job is completed
3065        manager = getUtility(IJobManager)
3066        job_id = self.app['datacenter'].running_exports[0][0]
3067        job = manager.get(job_id)
3068        wait_for_result(job)
3069        return job_id
3070
3071    def test_faculties_export(self):
3072        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3073        facs_path = 'http://localhost/app/faculties'
3074        self.browser.open(facs_path)
3075        self.browser.getLink("Export student data").click()
3076        self.browser.getControl("Configure new export").click()
3077        self.browser.getControl(name="exporter").value = ['bursary']
3078        self.browser.getControl(name="session").value = ['2004']
3079        self.browser.getControl(name="level").value = ['100']
3080        self.browser.getControl(name="mode").value = ['ug_ft']
3081        self.browser.getControl("Create CSV file").click()
3082
3083        # When the job is finished and we reload the page...
3084        job_id = self.wait_for_export_job_completed()
3085        self.browser.open(facs_path + '/exports')
3086        # ... the csv file can be downloaded ...
3087        self.browser.getLink("Download").click()
3088        self.assertEqual(self.browser.headers['content-type'],
3089            'text/csv; charset=UTF-8')
3090        self.assertTrue(
3091            'filename="WAeUP.Kofa_bursary_%s.csv' % job_id in
3092            self.browser.headers['content-disposition'])
3093        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3094        job_id = self.app['datacenter'].running_exports[0][0]
3095        # ... and discarded
3096        self.browser.open(facs_path + '/exports')
3097        self.browser.getControl("Discard").click()
3098        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3099        # Creation, downloading and discarding is logged
3100        logfile = os.path.join(
3101            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3102        logcontent = open(logfile).read()
3103        self.assertTrue(
3104            'zope.mgr - students.browser.FacultiesExportJobContainerJobConfig '
3105            '- exported: bursary (2004, 100, ug_ft, None, None), job_id=%s'
3106            % job_id in logcontent
3107            )
3108        self.assertTrue(
3109            'zope.mgr - students.browser.ExportJobContainerDownload '
3110            '- downloaded: WAeUP.Kofa_bursary_%s.csv, job_id=%s'
3111            % (job_id, job_id) in logcontent
3112            )
3113        self.assertTrue(
3114            'zope.mgr - students.browser.ExportJobContainerOverview '
3115            '- discarded: job_id=%s' % job_id in logcontent
3116            )
3117
3118    def test_department_export(self):
3119        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3120        dep1_path = 'http://localhost/app/faculties/fac1/dep1'
3121        self.browser.open(dep1_path)
3122        self.browser.getLink("Export student data").click()
3123        self.browser.getControl("Configure new export").click()
3124        self.browser.getControl(name="exporter").value = ['students']
3125        self.browser.getControl(name="session").value = ['2004']
3126        self.browser.getControl(name="level").value = ['100']
3127        self.browser.getControl(name="mode").value = ['ug_ft']
3128        self.browser.getControl("Create CSV file").click()
3129
3130        # When the job is finished and we reload the page...
3131        job_id = self.wait_for_export_job_completed()
3132        self.browser.open(dep1_path + '/exports')
3133        # ... the csv file can be downloaded ...
3134        self.browser.getLink("Download").click()
3135        self.assertEqual(self.browser.headers['content-type'],
3136            'text/csv; charset=UTF-8')
3137        self.assertTrue(
3138            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3139            self.browser.headers['content-disposition'])
3140        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3141        job_id = self.app['datacenter'].running_exports[0][0]
3142        # ... and discarded
3143        self.browser.open(dep1_path + '/exports')
3144        self.browser.getControl("Discard").click()
3145        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3146        # Creation, downloading and discarding is logged
3147        logfile = os.path.join(
3148            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3149        logcontent = open(logfile).read()
3150        self.assertTrue(
3151            'zope.mgr - students.browser.DepartmentExportJobContainerJobConfig '
3152            '- exported: students (2004, 100, ug_ft, dep1, None), job_id=%s'
3153            % job_id in logcontent
3154            )
3155        self.assertTrue(
3156            'zope.mgr - students.browser.ExportJobContainerDownload '
3157            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3158            % (job_id, job_id) in logcontent
3159            )
3160        self.assertTrue(
3161            'zope.mgr - students.browser.ExportJobContainerOverview '
3162            '- discarded: job_id=%s' % job_id in logcontent
3163            )
3164
3165    def test_certificate_export(self):
3166        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3167        cert1_path = 'http://localhost/app/faculties/fac1/dep1/certificates/CERT1'
3168        self.browser.open(cert1_path)
3169        self.browser.getLink("Export student data").click()
3170        self.browser.getControl("Configure new export").click()
3171        self.browser.getControl(name="exporter").value = ['students']
3172        self.browser.getControl(name="session").value = ['2004']
3173        self.browser.getControl(name="level").value = ['100']
3174        self.browser.getControl("Create CSV file").click()
3175
3176        # When the job is finished and we reload the page...
3177        job_id = self.wait_for_export_job_completed()
3178        self.browser.open(cert1_path + '/exports')
3179        # ... the csv file can be downloaded ...
3180        self.browser.getLink("Download").click()
3181        self.assertEqual(self.browser.headers['content-type'],
3182            'text/csv; charset=UTF-8')
3183        self.assertTrue(
3184            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3185            self.browser.headers['content-disposition'])
3186        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3187        job_id = self.app['datacenter'].running_exports[0][0]
3188        # ... and discarded
3189        self.browser.open(cert1_path + '/exports')
3190        self.browser.getControl("Discard").click()
3191        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3192        # Creation, downloading and discarding is logged
3193        logfile = os.path.join(
3194            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3195        logcontent = open(logfile).read()
3196        self.assertTrue(
3197            'zope.mgr - students.browser.CertificateExportJobContainerJobConfig '
3198            '- exported: students (2004, 100, None, None, CERT1), job_id=%s'
3199            % job_id in logcontent
3200            )
3201        self.assertTrue(
3202            'zope.mgr - students.browser.ExportJobContainerDownload '
3203            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3204            % (job_id, job_id) in logcontent
3205            )
3206        self.assertTrue(
3207            'zope.mgr - students.browser.ExportJobContainerOverview '
3208            '- discarded: job_id=%s' % job_id in logcontent
3209            )
3210
3211    def test_course_export_students(self):
3212        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3213        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
3214        self.browser.open(course1_path)
3215        self.browser.getLink("Export student data").click()
3216        self.browser.getControl("Configure new export").click()
3217        self.browser.getControl(name="exporter").value = ['students']
3218        self.browser.getControl(name="session").value = ['2004']
3219        self.browser.getControl(name="level").value = ['100']
3220        self.browser.getControl("Create CSV file").click()
3221
3222        # When the job is finished and we reload the page...
3223        job_id = self.wait_for_export_job_completed()
3224        self.browser.open(course1_path + '/exports')
3225        # ... the csv file can be downloaded ...
3226        self.browser.getLink("Download").click()
3227        self.assertEqual(self.browser.headers['content-type'],
3228            'text/csv; charset=UTF-8')
3229        self.assertTrue(
3230            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3231            self.browser.headers['content-disposition'])
3232        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3233        job_id = self.app['datacenter'].running_exports[0][0]
3234        # ... and discarded
3235        self.browser.open(course1_path + '/exports')
3236        self.browser.getControl("Discard").click()
3237        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3238        # Creation, downloading and discarding is logged
3239        logfile = os.path.join(
3240            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3241        logcontent = open(logfile).read()
3242        self.assertTrue(
3243            'zope.mgr - students.browser.CourseExportJobContainerJobConfig '
3244            '- exported: students (2004, 100, COURSE1), job_id=%s'
3245            % job_id in logcontent
3246            )
3247        self.assertTrue(
3248            'zope.mgr - students.browser.ExportJobContainerDownload '
3249            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3250            % (job_id, job_id) in logcontent
3251            )
3252        self.assertTrue(
3253            'zope.mgr - students.browser.ExportJobContainerOverview '
3254            '- discarded: job_id=%s' % job_id in logcontent
3255            )
3256
3257    def test_course_export_coursetickets(self):
3258        # We add study level 100 to the student's studycourse
3259        studylevel = StudentStudyLevel()
3260        studylevel.level = 100
3261        studylevel.level_session = 2004
3262        self.student['studycourse'].addStudentStudyLevel(
3263            self.certificate,studylevel)
3264        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3265        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
3266        self.browser.open(course1_path)
3267        self.browser.getLink("Export student data").click()
3268        self.browser.getControl("Configure new export").click()
3269        self.browser.getControl(name="exporter").value = ['coursetickets']
3270        self.browser.getControl(name="session").value = ['2004']
3271        self.browser.getControl(name="level").value = ['100']
3272        self.browser.getControl("Create CSV file").click()
3273        # When the job is finished and we reload the page...
3274        job_id = self.wait_for_export_job_completed()
3275        self.browser.open(course1_path + '/exports')
3276        # ... the csv file can be downloaded ...
3277        self.browser.getLink("Download").click()
3278        self.assertEqual(self.browser.headers['content-type'],
3279            'text/csv; charset=UTF-8')
3280        self.assertTrue(
3281            'filename="WAeUP.Kofa_coursetickets_%s.csv' % job_id in
3282            self.browser.headers['content-disposition'])
3283        # ... and contains the course ticket COURSE1
3284        self.assertEqual(self.browser.contents,
3285            'automatic,carry_over,code,credits,dcode,fcode,level,'
3286            'level_session,mandatory,passmark,score,semester,title,'
3287            'student_id,certcode\r\n1,0,COURSE1,10,dep1,fac1,100,2004,1,40,,1,'
3288            'Unnamed Course,K1000000,CERT1\r\n')
3289
3290        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3291        job_id = self.app['datacenter'].running_exports[0][0]
3292        # Thew job can be discarded
3293        self.browser.open(course1_path + '/exports')
3294        self.browser.getControl("Discard").click()
3295        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3296        # Creation, downloading and discarding is logged
3297        logfile = os.path.join(
3298            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3299        logcontent = open(logfile).read()
3300        self.assertTrue(
3301            'zope.mgr - students.browser.CourseExportJobContainerJobConfig '
3302            '- exported: coursetickets (2004, 100, COURSE1), job_id=%s'
3303            % job_id in logcontent
3304            )
3305        self.assertTrue(
3306            'zope.mgr - students.browser.ExportJobContainerDownload '
3307            '- downloaded: WAeUP.Kofa_coursetickets_%s.csv, job_id=%s'
3308            % (job_id, job_id) in logcontent
3309            )
3310        self.assertTrue(
3311            'zope.mgr - students.browser.ExportJobContainerOverview '
3312            '- discarded: job_id=%s' % job_id in logcontent
3313            )
3314
3315    def test_export_departmet_officers(self):
3316        # Create department officer
3317        self.app['users'].addUser('mrdepartment', 'mrdepartmentsecret')
3318        self.app['users']['mrdepartment'].email = 'mrdepartment@foo.ng'
3319        self.app['users']['mrdepartment'].title = 'Carlo Pitter'
3320        # Assign local role
3321        department = self.app['faculties']['fac1']['dep1']
3322        prmlocal = IPrincipalRoleManager(department)
3323        prmlocal.assignRoleToPrincipal('waeup.local.DepartmentOfficer', 'mrdepartment')
3324        # Login as department officer
3325        self.browser.open(self.login_path)
3326        self.browser.getControl(name="form.login").value = 'mrdepartment'
3327        self.browser.getControl(name="form.password").value = 'mrdepartmentsecret'
3328        self.browser.getControl("Login").click()
3329        self.assertMatches('...You logged in...', self.browser.contents)
3330        self.browser.open("http://localhost/app/faculties/fac1/dep1")
3331        self.browser.getLink("Export student data").click()
3332        self.browser.getControl("Configure new export").click()
3333        # Only the paymentsoverview exporter is available for department officers
3334        self.assertFalse('<option value="students">' in self.browser.contents)
3335        self.assertTrue(
3336            '<option value="paymentsoverview">' in self.browser.contents)
3337        self.browser.getControl(name="exporter").value = ['paymentsoverview']
3338        self.browser.getControl(name="session").value = ['2004']
3339        self.browser.getControl(name="level").value = ['100']
3340        self.browser.getControl("Create CSV file").click()
3341        self.assertTrue('Export started' in self.browser.contents)
3342        # Thew job can be discarded
3343        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3344        #job_id = self.app['datacenter'].running_exports[0][0]
3345        job_id = self.wait_for_export_job_completed()
3346        self.browser.open("http://localhost/app/faculties/fac1/dep1/exports")
3347        self.browser.getControl("Discard").click()
3348        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3349
3350    def test_export_bursary_officers(self):
3351        # Create bursary officer
3352        self.app['users'].addUser('mrbursary', 'mrbursarysecret')
3353        self.app['users']['mrbursary'].email = 'mrbursary@foo.ng'
3354        self.app['users']['mrbursary'].title = 'Carlo Pitter'
3355        prmglobal = IPrincipalRoleManager(self.app)
3356        prmglobal.assignRoleToPrincipal('waeup.BursaryOfficer', 'mrbursary')
3357        # Login as bursary officer
3358        self.browser.open(self.login_path)
3359        self.browser.getControl(name="form.login").value = 'mrbursary'
3360        self.browser.getControl(name="form.password").value = 'mrbursarysecret'
3361        self.browser.getControl("Login").click()
3362        self.assertMatches('...You logged in...', self.browser.contents)
3363        self.browser.getLink("Academics").click()
3364        self.browser.getLink("Export student data").click()
3365        self.browser.getControl("Configure new export").click()
3366        # Only the bursary exporter is available for bursary officers
3367        # not only at facultiescontainer level ...
3368        self.assertFalse('<option value="students">' in self.browser.contents)
3369        self.assertTrue('<option value="bursary">' in self.browser.contents)
3370        self.browser.getControl(name="exporter").value = ['bursary']
3371        self.browser.getControl(name="session").value = ['2004']
3372        self.browser.getControl(name="level").value = ['100']
3373        self.browser.getControl("Create CSV file").click()
3374        self.assertTrue('Export started' in self.browser.contents)
3375        # ... but also at other levels
3376        self.browser.open('http://localhost/app/faculties/fac1/dep1')
3377        self.browser.getLink("Export student data").click()
3378        self.browser.getControl("Configure new export").click()
3379        self.assertFalse('<option value="students">' in self.browser.contents)
3380        self.assertTrue('<option value="bursary">' in self.browser.contents)
3381        # Thew job can be discarded
3382        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3383        #job_id = self.app['datacenter'].running_exports[0][0]
3384        job_id = self.wait_for_export_job_completed()
3385        self.browser.open('http://localhost/app/faculties/exports')
3386        self.browser.getControl("Discard").click()
3387        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
Note: See TracBrowser for help on using the repository browser.