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

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

Use public_name in object histories and when approving payments.

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