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

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

Add login tests.

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