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

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

Use keywords not only to filter students but also to restrict access to coursetickets which belong to the course so that lecturers (who gain access to courses) can download only coursetickets of their own course.

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