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

Last change on this file since 8555 was 8472, checked in by Henrik Bettermann, 13 years ago

Add property is_postgrad.

Add invariant constraint to ICertificate.

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