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

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

Add log message to payments.log after student payment approval.

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