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

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

After some experiments with dynamical roles which allow lecturers to edit the score attribute of course tickets through the UI, I decided to discard the idea completely. It's not feasible and very dangerous to enable lecturers to edit course tickets directly: There are too many course tickets, too many lecturers and to many restrictions. The solution will be that lecturers export the course tickets of their courses, fill the score column and re-upload the csv file. This is still not easy to implement because we can't allow lecturers to access the data center directly. Further discussions are necessary ...

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