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

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

Test if students catalog is updated after transerring student.

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