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

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

I changed my mind. Course Advisers are allowed to view all students in a department or certificate respectively. So they should also be able to select the level.

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