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

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

Add regression test for catching ghost dict errors.

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