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

Last change on this file since 13005 was 13005, checked in by Henrik Bettermann, 9 years ago

Add Payment class attribute created_online to mark payment tickets
which are added online and not by import. This attribute is needed in
custom packages when sending data to payment gateways.

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