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

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

Derive information about passed and failed course.

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