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

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

Add clearance_enabled field to ISessionConfiguration. Clearance can now be disabled for certain sessions via this switch.

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