source: main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/tests/test_browser.py @ 9093

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

Add password request page for first-time login withot email address and pwd activation code.

To do: What happens if a wrong email address has been entered. Solution: We need to remember if a student has logged in.

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