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

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

Dedicated officers should be able to login as student with a temporary password set by the system. This is the first part of its implementation.

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