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

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

Remove school fee from session configuration. School fees are now attributes of the certificates.

Adjust and improve tests.

Fix getPaymentDetails.

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