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

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

Add getOwner method to payments which is needed for the webservice.

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