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

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

Add button and tests.

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