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

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

Add regression test. The test fails at the moment.

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