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

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

Add new payment category.

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