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

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

Let students and officers add course tickets by entering the course code on the StudyLevelEditFormPage? or StudyLevelManageFormPage? respectively. The course ticket add forms can still be used in case course code is unknown.

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