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

Last change on this file since 9931 was 9929, checked in by Henrik Bettermann, 12 years ago

Placeholder file is now bigger.

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