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

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

Log the removal of study levels or course tickets.

  • Property svn:keywords set to Id
File size: 116.5 KB
Line 
1## $Id: test_browser.py 9332 2012-10-12 16:40:52Z 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        # Removing levels is properly logged
689        logfile = os.path.join(
690            self.app['datacenter'].storage, 'logs', 'students.log')
691        logcontent = open(logfile).read()
692        self.assertTrue('zope.mgr - students.browser.StudyCourseManageFormPage '
693                        '- K1000000 - removed: 100' in logcontent)
694        # Add level again
695        self.browser.getControl(name="addlevel").value = ['100']
696        self.browser.getControl("Add study level").click()
697
698        # Managers can view and manage course lists
699        self.browser.getLink("100").click()
700        self.assertMatches(
701            '...: Study Level 100 (Year 1)...', self.browser.contents)
702        self.browser.getLink("Manage").click()
703        self.browser.getControl(name="form.level_session").value = ['2002']
704        self.browser.getControl("Save").click()
705        self.browser.getControl("Remove selected").click()
706        self.assertMatches('...No ticket selected...', self.browser.contents)
707        ctrl = self.browser.getControl(name='val_id')
708        ctrl.getControl(value='COURSE1').selected = True
709        self.browser.getControl("Remove selected", index=0).click()
710        self.assertTrue('Successfully removed' in self.browser.contents)
711        # Removing course tickets is properly logged
712        logfile = os.path.join(
713            self.app['datacenter'].storage, 'logs', 'students.log')
714        logcontent = open(logfile).read()
715        self.assertTrue('zope.mgr - students.browser.StudyLevelManageFormPage '
716        '- K1000000 - removed: COURSE1' in logcontent)
717        self.browser.getControl("Add course ticket").click()
718        self.browser.getControl(name="form.course").value = ['COURSE1']
719        self.browser.getControl("Add course ticket").click()
720        self.assertTrue('Successfully added' in self.browser.contents)
721        self.browser.getControl("Add course ticket").click()
722        self.browser.getControl(name="form.course").value = ['COURSE1']
723        self.browser.getControl("Add course ticket").click()
724        self.assertTrue('The ticket exists' in self.browser.contents)
725        self.browser.getControl("Cancel").click()
726        self.browser.getLink("COURSE1").click()
727        self.browser.getLink("Manage").click()
728        self.browser.getControl(name="form.score").value = '10'
729        self.browser.getControl("Save").click()
730        self.assertTrue('Form has been saved' in self.browser.contents)
731        # Carry-over courses will be collected when next level is created
732        self.browser.open(self.student_path + '/studycourse/manage')
733        # Add next level
734        self.browser.getControl(name="addlevel").value = ['200']
735        self.browser.getControl("Add study level").click()
736        self.browser.getLink("200").click()
737        self.assertMatches(
738            '...: Study Level 200 (Year 2)...', self.browser.contents)
739        # COURSE1 has score 0 and thus will become a carry-over course
740        # in level 200
741        self.assertEqual(
742            sorted(self.student['studycourse']['200'].keys()), [u'COURSE1'])
743        self.assertTrue(
744            self.student['studycourse']['200']['COURSE1'].carry_over)
745        return
746
747    def test_manage_workflow(self):
748        # Managers can pass through the whole workflow
749        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
750        student = self.app['students'][self.student_id]
751        self.browser.open(self.trigtrans_path)
752        self.assertTrue(student.clearance_locked)
753        self.browser.getControl(name="transition").value = ['admit']
754        self.browser.getControl("Save").click()
755        self.assertTrue(student.clearance_locked)
756        self.browser.getControl(name="transition").value = ['start_clearance']
757        self.browser.getControl("Save").click()
758        self.assertFalse(student.clearance_locked)
759        self.browser.getControl(name="transition").value = ['request_clearance']
760        self.browser.getControl("Save").click()
761        self.assertTrue(student.clearance_locked)
762        self.browser.getControl(name="transition").value = ['clear']
763        self.browser.getControl("Save").click()
764        # Managers approve payment, they do not pay
765        self.assertFalse('pay_first_school_fee' in self.browser.contents)
766        self.browser.getControl(
767            name="transition").value = ['approve_first_school_fee']
768        self.browser.getControl("Save").click()
769        self.browser.getControl(name="transition").value = ['reset6']
770        self.browser.getControl("Save").click()
771        # In state returning the pay_school_fee transition triggers some
772        # changes of attributes
773        self.browser.getControl(name="transition").value = ['approve_school_fee']
774        self.browser.getControl("Save").click()
775        self.assertEqual(student['studycourse'].current_session, 2005) # +1
776        self.assertEqual(student['studycourse'].current_level, 200) # +100
777        self.assertEqual(student['studycourse'].current_verdict, '0') # 0 = Zero = not set
778        self.assertEqual(student['studycourse'].previous_verdict, 'A')
779        self.browser.getControl(name="transition").value = ['register_courses']
780        self.browser.getControl("Save").click()
781        self.browser.getControl(name="transition").value = ['validate_courses']
782        self.browser.getControl("Save").click()
783        self.browser.getControl(name="transition").value = ['return']
784        self.browser.getControl("Save").click()
785        return
786
787    def test_manage_pg_workflow(self):
788        # Managers can pass through the whole workflow
789        IWorkflowState(self.student).setState('school fee paid')
790        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
791        student = self.app['students'][self.student_id]
792        self.browser.open(self.trigtrans_path)
793        self.assertTrue('<option value="reset6">' in self.browser.contents)
794        self.assertTrue('<option value="register_courses">' in self.browser.contents)
795        self.assertTrue('<option value="reset5">' in self.browser.contents)
796        self.certificate.study_mode = 'pg_ft'
797        self.browser.open(self.trigtrans_path)
798        self.assertFalse('<option value="reset6">' in self.browser.contents)
799        self.assertFalse('<option value="register_courses">' in self.browser.contents)
800        self.assertTrue('<option value="reset5">' in self.browser.contents)
801        return
802
803    def test_manage_import(self):
804        # Managers can import student data files
805        datacenter_path = 'http://localhost/app/datacenter'
806        # Prepare a csv file for students
807        open('students.csv', 'wb').write(
808"""firstname,lastname,reg_number,date_of_birth,matric_number,email,phone,sex,password
809Aaren,Pieri,1,1990-01-02,100000,aa@aa.ng,1234,m,mypwd1
810Claus,Finau,2,1990-01-03,100001,aa@aa.ng,1234,m,mypwd1
811Brit,Berson,3,1990-01-04,100001,aa@aa.ng,1234,m,mypwd1
812""")
813        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
814        self.browser.open(datacenter_path)
815        self.browser.getLink('Upload data').click()
816        filecontents = StringIO(open('students.csv', 'rb').read())
817        filewidget = self.browser.getControl(name='uploadfile:file')
818        filewidget.add_file(filecontents, 'text/plain', 'students.csv')
819        self.browser.getControl(name='SUBMIT').click()
820        self.browser.getLink('Process data').click()
821        button = lookup_submit_value(
822            'select', 'students_zope.mgr.csv', self.browser)
823        button.click()
824        importerselect = self.browser.getControl(name='importer')
825        modeselect = self.browser.getControl(name='mode')
826        importerselect.getControl('Student Processor').selected = True
827        modeselect.getControl(value='create').selected = True
828        self.browser.getControl('Proceed to step 3').click()
829        self.assertTrue('Header fields OK' in self.browser.contents)
830        self.browser.getControl('Perform import').click()
831        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
832        self.assertTrue('Successfully processed 2 rows' in self.browser.contents)
833        self.assertTrue('Batch processing finished' in self.browser.contents)
834        open('studycourses.csv', 'wb').write(
835"""reg_number,matric_number,certificate,current_session,current_level
8361,,CERT1,2008,100
837,100001,CERT1,2008,100
838,100002,CERT1,2008,100
839""")
840        self.browser.open(datacenter_path)
841        self.browser.getLink('Upload data').click()
842        filecontents = StringIO(open('studycourses.csv', 'rb').read())
843        filewidget = self.browser.getControl(name='uploadfile:file')
844        filewidget.add_file(filecontents, 'text/plain', 'studycourses.csv')
845        self.browser.getControl(name='SUBMIT').click()
846        self.browser.getLink('Process data').click()
847        button = lookup_submit_value(
848            'select', 'studycourses_zope.mgr.csv', self.browser)
849        button.click()
850        importerselect = self.browser.getControl(name='importer')
851        modeselect = self.browser.getControl(name='mode')
852        importerselect.getControl(
853            'StudentStudyCourse Processor (update only)').selected = True
854        modeselect.getControl(value='create').selected = True
855        self.browser.getControl('Proceed to step 3').click()
856        self.assertTrue('Update mode only' in self.browser.contents)
857        self.browser.getControl('Proceed to step 3').click()
858        self.assertTrue('Header fields OK' in self.browser.contents)
859        self.browser.getControl('Perform import').click()
860        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
861        self.assertTrue('Successfully processed 2 rows'
862                        in self.browser.contents)
863        # The students are properly indexed and we can
864        # thus find a student in  the department
865        self.browser.open(self.manage_container_path)
866        self.browser.getControl(name="searchtype").value = ['depcode']
867        self.browser.getControl(name="searchterm").value = 'dep1'
868        self.browser.getControl("Search").click()
869        self.assertTrue('Aaren Pieri' in self.browser.contents)
870        # We can search for a new student by name ...
871        self.browser.getControl(name="searchtype").value = ['fullname']
872        self.browser.getControl(name="searchterm").value = 'Claus'
873        self.browser.getControl("Search").click()
874        self.assertTrue('Claus Finau' in self.browser.contents)
875        # ... and check if the imported password has been properly set
876        ctrl = self.browser.getControl(name='entries')
877        value = ctrl.options[0]
878        claus = self.app['students'][value]
879        self.assertTrue(IUserAccount(claus).checkPassword('mypwd1'))
880        return
881
882    def test_handle_clearance_by_co(self):
883        # Create clearance officer
884        self.app['users'].addUser('mrclear', 'mrclearsecret')
885        self.app['users']['mrclear'].email = 'mrclear@foo.ng'
886        self.app['users']['mrclear'].title = 'Carlo Pitter'
887        # Clearance officers need not necessarily to get
888        # the StudentsOfficer site role
889        #prmglobal = IPrincipalRoleManager(self.app)
890        #prmglobal.assignRoleToPrincipal('waeup.StudentsOfficer', 'mrclear')
891        # Assign local ClearanceOfficer role
892        department = self.app['faculties']['fac1']['dep1']
893        prmlocal = IPrincipalRoleManager(department)
894        prmlocal.assignRoleToPrincipal('waeup.local.ClearanceOfficer', 'mrclear')
895        IWorkflowState(self.student).setState('clearance started')
896        # Login as clearance officer
897        self.browser.open(self.login_path)
898        self.browser.getControl(name="form.login").value = 'mrclear'
899        self.browser.getControl(name="form.password").value = 'mrclearsecret'
900        self.browser.getControl("Login").click()
901        self.assertMatches('...You logged in...', self.browser.contents)
902        # CO can see his roles
903        self.browser.getLink("My Roles").click()
904        self.assertMatches(
905            '...<div>Academics Officer (view only)</div>...',
906            self.browser.contents)
907        #self.assertMatches(
908        #    '...<div>Students Officer (view only)</div>...',
909        #    self.browser.contents)
910        # But not his local role ...
911        self.assertFalse('Clearance Officer' in self.browser.contents)
912        # ... because we forgot to notify the department that the local role
913        # has changed
914        notify(LocalRoleSetEvent(
915            department, 'waeup.local.ClearanceOfficer', 'mrclear', granted=True))
916        self.browser.open('http://localhost/app/users/mrclear/my_roles')
917        self.assertTrue('Clearance Officer' in self.browser.contents)
918        self.assertMatches(
919            '...<a href="http://localhost/app/faculties/fac1/dep1">...',
920            self.browser.contents)
921        # CO can view the student ...
922        self.browser.open(self.clearance_path)
923        self.assertEqual(self.browser.headers['Status'], '200 Ok')
924        self.assertEqual(self.browser.url, self.clearance_path)
925        # ... but not other students
926        other_student = Student()
927        other_student.firstname = u'Dep2'
928        other_student.lastname = u'Student'
929        self.app['students'].addStudent(other_student)
930        other_student_path = (
931            'http://localhost/app/students/%s' % other_student.student_id)
932        self.assertRaises(
933            Unauthorized, self.browser.open, other_student_path)
934        # Only in state clearance requested the CO does see the 'Clear' button
935        self.browser.open(self.clearance_path)
936        self.assertFalse('Clear student' in self.browser.contents)
937        IWorkflowInfo(self.student).fireTransition('request_clearance')
938        self.browser.open(self.clearance_path)
939        self.assertTrue('Clear student' in self.browser.contents)
940        self.browser.getLink("Clear student").click()
941        self.assertTrue('Student has been cleared' in self.browser.contents)
942        self.assertTrue('cleared' in self.browser.contents)
943        self.browser.open(self.history_path)
944        self.assertTrue('Cleared by Carlo Pitter' in self.browser.contents)
945        # Hide real name.
946        self.app['users']['mrclear'].public_name = 'My Public Name'
947        self.browser.open(self.clearance_path)
948        self.browser.getLink("Reject clearance").click()
949        self.assertTrue('Clearance has been annulled' in self.browser.contents)
950        urlmessage = 'Clearance+has+been+annulled.'
951        # CO does now see the contact form
952        self.assertEqual(self.browser.url, self.student_path +
953            '/contactstudent?subject=%s' % urlmessage)
954        self.assertTrue('clearance started' in self.browser.contents)
955        self.browser.open(self.history_path)
956        self.assertTrue("Reset to 'clearance started' by My Public Name" in
957            self.browser.contents)
958        IWorkflowInfo(self.student).fireTransition('request_clearance')
959        self.browser.open(self.clearance_path)
960        self.browser.getLink("Reject clearance").click()
961        self.assertTrue('Clearance request has been rejected'
962            in self.browser.contents)
963        self.assertTrue('clearance started' in self.browser.contents)
964        # CO does now also see the contact form and can send a message
965        self.browser.getControl(name="form.subject").value = 'Important subject'
966        self.browser.getControl(name="form.body").value = 'Clearance rejected'
967        self.browser.getControl("Send message now").click()
968        self.assertTrue('Your message has been sent' in self.browser.contents)
969        # The CO can't clear students if not in state
970        # clearance requested
971        self.browser.open(self.student_path + '/clear')
972        self.assertTrue('Student is in wrong state'
973            in self.browser.contents)
974        # The CO can go to his department throug the my_roles page
975        self.browser.open('http://localhost/app/users/mrclear/my_roles')
976        self.browser.getLink("http://localhost/app/faculties/fac1/dep1").click()
977        # and view the list of students
978        self.browser.getLink("Show students").click()
979        self.assertTrue(self.student_id in self.browser.contents)
980
981    def test_handle_courses_by_ca(self):
982        # Create course adviser
983        self.app['users'].addUser('mrsadvise', 'mrsadvisesecret')
984        self.app['users']['mrsadvise'].email = 'mradvise@foo.ng'
985        self.app['users']['mrsadvise'].title = u'Helen Procter'
986        # Assign local CourseAdviser100 role for a certificate
987        cert = self.app['faculties']['fac1']['dep1'].certificates['CERT1']
988        prmlocal = IPrincipalRoleManager(cert)
989        prmlocal.assignRoleToPrincipal('waeup.local.CourseAdviser100', 'mrsadvise')
990        IWorkflowState(self.student).setState('school fee paid')
991        # Login as course adviser
992        self.browser.open(self.login_path)
993        self.browser.getControl(name="form.login").value = 'mrsadvise'
994        self.browser.getControl(name="form.password").value = 'mrsadvisesecret'
995        self.browser.getControl("Login").click()
996        self.assertMatches('...You logged in...', self.browser.contents)
997        # CO can see his roles
998        self.browser.getLink("My Roles").click()
999        self.assertMatches(
1000            '...<div>Academics Officer (view only)</div>...',
1001            self.browser.contents)
1002        # But not his local role ...
1003        self.assertFalse('Course Adviser' in self.browser.contents)
1004        # ... because we forgot to notify the certificate that the local role
1005        # has changed
1006        notify(LocalRoleSetEvent(
1007            cert, 'waeup.local.CourseAdviser100', 'mrsadvise', granted=True))
1008        self.browser.open('http://localhost/app/users/mrsadvise/my_roles')
1009        self.assertTrue('Course Adviser 100L' in self.browser.contents)
1010        self.assertMatches(
1011            '...<a href="http://localhost/app/faculties/fac1/dep1/certificates/CERT1">...',
1012            self.browser.contents)
1013        # CA can view the student ...
1014        self.browser.open(self.student_path)
1015        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1016        self.assertEqual(self.browser.url, self.student_path)
1017        # ... but not other students
1018        other_student = Student()
1019        other_student.firstname = u'Dep2'
1020        other_student.lastname = u'Student'
1021        self.app['students'].addStudent(other_student)
1022        other_student_path = (
1023            'http://localhost/app/students/%s' % other_student.student_id)
1024        self.assertRaises(
1025            Unauthorized, self.browser.open, other_student_path)
1026        # We add study level 110 to the student's studycourse
1027        studylevel = StudentStudyLevel()
1028        studylevel.level = 110
1029        self.student['studycourse'].addStudentStudyLevel(
1030            cert,studylevel)
1031        L110_student_path = self.studycourse_path + '/110'
1032        # Only in state courses registered and only if the current level
1033        # corresponds with the name of the study level object
1034        # the 100L CA does see the 'Validate' button
1035        self.browser.open(L110_student_path)
1036        self.assertFalse('Validate courses' in self.browser.contents)
1037        IWorkflowInfo(self.student).fireTransition('register_courses')
1038        self.browser.open(L110_student_path)
1039        self.assertFalse('Validate courses' in self.browser.contents)
1040        self.student['studycourse'].current_level = 110
1041        self.browser.open(L110_student_path)
1042        self.assertTrue('Validate courses' in self.browser.contents)
1043        # ... but a 100L CA does not see the button on other levels
1044        studylevel2 = StudentStudyLevel()
1045        studylevel2.level = 200
1046        self.student['studycourse'].addStudentStudyLevel(
1047            cert,studylevel2)
1048        L200_student_path = self.studycourse_path + '/200'
1049        self.browser.open(L200_student_path)
1050        self.assertFalse('Validate courses' in self.browser.contents)
1051        self.browser.open(L110_student_path)
1052        self.browser.getLink("Validate courses").click()
1053        self.assertTrue('Course list has been validated' in self.browser.contents)
1054        self.assertTrue('courses validated' in self.browser.contents)
1055        self.assertEqual(self.student['studycourse']['110'].validated_by,
1056            'Helen Procter')
1057        self.assertMatches(
1058            '<YYYY-MM-DD hh:mm:ss>',
1059            self.student['studycourse']['110'].validation_date.strftime(
1060                "%Y-%m-%d %H:%M:%S"))
1061        self.browser.getLink("Reject courses").click()
1062        self.assertTrue('Course list request has been annulled.'
1063            in self.browser.contents)
1064        urlmessage = 'Course+list+request+has+been+annulled.'
1065        self.assertEqual(self.browser.url, self.student_path +
1066            '/contactstudent?subject=%s' % urlmessage)
1067        self.assertTrue('school fee paid' in self.browser.contents)
1068        self.assertTrue(self.student['studycourse']['110'].validated_by is None)
1069        self.assertTrue(self.student['studycourse']['110'].validation_date is None)
1070        IWorkflowInfo(self.student).fireTransition('register_courses')
1071        self.browser.open(L110_student_path)
1072        self.browser.getLink("Reject courses").click()
1073        self.assertTrue('Course list request has been rejected'
1074            in self.browser.contents)
1075        self.assertTrue('school fee paid' in self.browser.contents)
1076        # CA does now see the contact form and can send a message
1077        self.browser.getControl(name="form.subject").value = 'Important subject'
1078        self.browser.getControl(name="form.body").value = 'Course list rejected'
1079        self.browser.getControl("Send message now").click()
1080        self.assertTrue('Your message has been sent' in self.browser.contents)
1081        # The CA can't validate courses if not in state
1082        # courses registered
1083        self.browser.open(L110_student_path + '/validate_courses')
1084        self.assertTrue('Student is in the wrong state'
1085            in self.browser.contents)
1086        # The CA can go to his certificate through the my_roles page
1087        self.browser.open('http://localhost/app/users/mrsadvise/my_roles')
1088        self.browser.getLink(
1089            "http://localhost/app/faculties/fac1/dep1/certificates/CERT1").click()
1090        # and view the list of students
1091        self.browser.getLink("Show students").click()
1092        self.assertTrue(self.student_id in self.browser.contents)
1093
1094    def test_student_change_password(self):
1095        # Students can change the password
1096        self.browser.open(self.login_path)
1097        self.browser.getControl(name="form.login").value = self.student_id
1098        self.browser.getControl(name="form.password").value = 'spwd'
1099        self.browser.getControl("Login").click()
1100        self.assertEqual(self.browser.url, self.student_path)
1101        self.assertTrue('You logged in' in self.browser.contents)
1102        # Change password
1103        self.browser.getLink("Change password").click()
1104        self.browser.getControl(name="change_password").value = 'pw'
1105        self.browser.getControl(
1106            name="change_password_repeat").value = 'pw'
1107        self.browser.getControl("Save").click()
1108        self.assertTrue('Password must have at least' in self.browser.contents)
1109        self.browser.getControl(name="change_password").value = 'new_password'
1110        self.browser.getControl(
1111            name="change_password_repeat").value = 'new_passssword'
1112        self.browser.getControl("Save").click()
1113        self.assertTrue('Passwords do not match' in self.browser.contents)
1114        self.browser.getControl(name="change_password").value = 'new_password'
1115        self.browser.getControl(
1116            name="change_password_repeat").value = 'new_password'
1117        self.browser.getControl("Save").click()
1118        self.assertTrue('Password changed' in self.browser.contents)
1119        # We are still logged in. Changing the password hasn't thrown us out.
1120        self.browser.getLink("Base Data").click()
1121        self.assertEqual(self.browser.url, self.student_path)
1122        # We can logout
1123        self.browser.getLink("Logout").click()
1124        self.assertTrue('You have been logged out' in self.browser.contents)
1125        self.assertEqual(self.browser.url, 'http://localhost/app')
1126        # We can login again with the new password
1127        self.browser.getLink("Login").click()
1128        self.browser.open(self.login_path)
1129        self.browser.getControl(name="form.login").value = self.student_id
1130        self.browser.getControl(name="form.password").value = 'new_password'
1131        self.browser.getControl("Login").click()
1132        self.assertEqual(self.browser.url, self.student_path)
1133        self.assertTrue('You logged in' in self.browser.contents)
1134        return
1135
1136    def test_setpassword(self):
1137        # Set password for first-time access
1138        student = Student()
1139        student.reg_number = u'123456'
1140        student.firstname = u'Klaus'
1141        student.lastname = u'Tester'
1142        self.app['students'].addStudent(student)
1143        setpassword_path = 'http://localhost/app/setpassword'
1144        student_path = 'http://localhost/app/students/%s' % student.student_id
1145        self.browser.open(setpassword_path)
1146        self.browser.getControl(name="ac_series").value = self.existing_pwdseries
1147        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
1148        self.browser.getControl(name="reg_number").value = '223456'
1149        self.browser.getControl("Set").click()
1150        self.assertMatches('...No student found...',
1151                           self.browser.contents)
1152        self.browser.getControl(name="reg_number").value = '123456'
1153        self.browser.getControl(name="ac_number").value = '999999'
1154        self.browser.getControl("Set").click()
1155        self.assertMatches('...Access code is invalid...',
1156                           self.browser.contents)
1157        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
1158        self.browser.getControl("Set").click()
1159        self.assertMatches('...Password has been set. Your Student Id is...',
1160                           self.browser.contents)
1161        self.browser.getControl("Set").click()
1162        self.assertMatches(
1163            '...Password has already been set. Your Student Id is...',
1164            self.browser.contents)
1165        existing_pwdpin = self.pwdpins[1]
1166        parts = existing_pwdpin.split('-')[1:]
1167        existing_pwdseries, existing_pwdnumber = parts
1168        self.browser.getControl(name="ac_series").value = existing_pwdseries
1169        self.browser.getControl(name="ac_number").value = existing_pwdnumber
1170        self.browser.getControl(name="reg_number").value = '123456'
1171        self.browser.getControl("Set").click()
1172        self.assertMatches(
1173            '...You are using the wrong Access Code...',
1174            self.browser.contents)
1175        # The student can login with the new credentials
1176        self.browser.open(self.login_path)
1177        self.browser.getControl(name="form.login").value = student.student_id
1178        self.browser.getControl(
1179            name="form.password").value = self.existing_pwdnumber
1180        self.browser.getControl("Login").click()
1181        self.assertEqual(self.browser.url, student_path)
1182        self.assertTrue('You logged in' in self.browser.contents)
1183        return
1184
1185    def test_student_access(self):
1186        # Student cant login if their password is not set
1187        self.student.password = None
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            'You entered invalid credentials.' in self.browser.contents)
1194        # We set the password again
1195        IUserAccount(
1196            self.app['students'][self.student_id]).setPassword('spwd')
1197        IWorkflowInfo(self.student).fireTransition('admit')
1198        # Students can't login if their account is suspended/deactivated
1199        self.student.suspended = True
1200        self.browser.open(self.login_path)
1201        self.browser.getControl(name="form.login").value = self.student_id
1202        self.browser.getControl(name="form.password").value = 'spwd'
1203        self.browser.getControl("Login").click()
1204        self.assertTrue(
1205            'Your account has been deactivated.' in self.browser.contents)
1206        self.student.suspended = False
1207        self.browser.getControl("Login").click()
1208        self.assertTrue(
1209            'You logged in.' in self.browser.contents)
1210        # Admitted student can upload a passport picture
1211        self.browser.open(self.student_path + '/change_portrait')
1212        ctrl = self.browser.getControl(name='passportuploadedit')
1213        file_obj = open(SAMPLE_IMAGE, 'rb')
1214        file_ctrl = ctrl.mech_control
1215        file_ctrl.add_file(file_obj, filename='my_photo.jpg')
1216        self.browser.getControl(
1217            name='upload_passportuploadedit').click()
1218        self.assertTrue(
1219            '<img align="middle" height="125px" src="passport.jpg" />'
1220            in self.browser.contents)
1221        # Students can open admission letter
1222        self.browser.getLink("Base Data").click()
1223        self.browser.getLink("Download admission letter").click()
1224        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1225        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1226        # Student can view the clearance data
1227        self.browser.open(self.student_path)
1228        self.browser.getLink("Clearance Data").click()
1229        # Student can't open clearance edit form before starting clearance
1230        self.browser.open(self.student_path + '/cedit')
1231        self.assertMatches('...The requested form is locked...',
1232                           self.browser.contents)
1233        self.browser.getLink("Clearance Data").click()
1234        self.browser.getLink("Start clearance").click()
1235        self.student.email = None
1236        # Uups, we forgot to fill the email fields
1237        self.browser.getControl("Start clearance").click()
1238        self.assertMatches('...Not all required fields filled...',
1239                           self.browser.contents)
1240        self.browser.open(self.student_path + '/edit_base')
1241        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
1242        self.browser.getControl("Save").click()
1243        self.browser.open(self.student_path + '/start_clearance')
1244        self.browser.getControl(name="ac_series").value = '3'
1245        self.browser.getControl(name="ac_number").value = '4444444'
1246        self.browser.getControl("Start clearance now").click()
1247        self.assertMatches('...Activation code is invalid...',
1248                           self.browser.contents)
1249        self.browser.getControl(name="ac_series").value = self.existing_clrseries
1250        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
1251        # Owner is Hans Wurst, AC can't be invalidated
1252        self.browser.getControl("Start clearance now").click()
1253        self.assertMatches('...You are not the owner of this access code...',
1254                           self.browser.contents)
1255        # Set the correct owner
1256        self.existing_clrac.owner = self.student_id
1257        # clr_code might be set (and thus returns None) due importing
1258        # an empty clr_code column.
1259        self.student.clr_code = None
1260        self.browser.getControl("Start clearance now").click()
1261        self.assertMatches('...Clearance process has been started...',
1262                           self.browser.contents)
1263        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
1264        self.browser.getControl("Save", index=0).click()
1265        # Student can view the clearance data
1266        self.browser.getLink("Clearance Data").click()
1267        # and go back to the edit form
1268        self.browser.getLink("Edit").click()
1269        # Students can upload documents
1270        ctrl = self.browser.getControl(name='birthcertificateupload')
1271        file_obj = open(SAMPLE_IMAGE, 'rb')
1272        file_ctrl = ctrl.mech_control
1273        file_ctrl.add_file(file_obj, filename='my_birth_certificate.jpg')
1274        self.browser.getControl(
1275            name='upload_birthcertificateupload').click()
1276        self.assertTrue(
1277            '<a target="image" href="birth_certificate">Birth Certificate Scan</a>'
1278            in self.browser.contents)
1279        # Students can open clearance slip
1280        self.browser.getLink("View").click()
1281        self.browser.getLink("Download clearance slip").click()
1282        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1283        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1284        # Students can request clearance
1285        self.browser.open(self.edit_clearance_path)
1286        self.browser.getControl("Save and request clearance").click()
1287        self.browser.getControl(name="ac_series").value = self.existing_clrseries
1288        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
1289        self.browser.getControl("Request clearance now").click()
1290        self.assertMatches('...Clearance has been requested...',
1291                           self.browser.contents)
1292        # Student can't reopen clearance form after requesting clearance
1293        self.browser.open(self.student_path + '/cedit')
1294        self.assertMatches('...The requested form is locked...',
1295                           self.browser.contents)
1296        # Student can't add study level if not in state 'school fee paid'
1297        self.browser.open(self.student_path + '/studycourse/add')
1298        self.assertMatches('...The requested form is locked...',
1299                           self.browser.contents)
1300        # ... and must be transferred first
1301        IWorkflowInfo(self.student).fireTransition('clear')
1302        IWorkflowInfo(self.student).fireTransition('pay_first_school_fee')
1303        # Now students can add the current study level
1304        self.browser.getLink("Study Course").click()
1305        self.browser.getLink("Add course list").click()
1306        self.assertMatches('...Add current level 100 (Year 1)...',
1307                           self.browser.contents)
1308        self.browser.getControl("Create course list now").click()
1309        # A level with one course ticket was created
1310        self.assertEqual(self.student['studycourse']['100'].number_of_tickets, 1)
1311        self.browser.getLink("100").click()
1312        self.browser.getLink("Edit course list").click()
1313        self.browser.getControl("Add course ticket").click()
1314        self.browser.getControl(name="form.course").value = ['COURSE1']
1315        self.browser.getControl("Add course ticket").click()
1316        self.assertMatches('...The ticket exists...',
1317                           self.browser.contents)
1318        self.student['studycourse'].current_level = 200
1319        self.browser.getLink("Study Course").click()
1320        self.browser.getLink("Add course list").click()
1321        self.assertMatches('...Add current level 200 (Year 2)...',
1322                           self.browser.contents)
1323        self.browser.getControl("Create course list now").click()
1324        self.browser.getLink("200").click()
1325        self.browser.getLink("Edit course list").click()
1326        self.browser.getControl("Add course ticket").click()
1327        self.browser.getControl(name="form.course").value = ['COURSE1']
1328        self.browser.getControl("Add course ticket").click()
1329        self.assertMatches('...The ticket exists...',
1330                           self.browser.contents)
1331        # Indeed the ticket exists as carry-over course from level 100
1332        # since its score was 0
1333        self.assertTrue(
1334            self.student['studycourse']['200']['COURSE1'].carry_over is True)
1335        # Students can open the pdf course registration slip
1336        self.browser.open(self.student_path + '/studycourse/200')
1337        self.browser.getLink("Download course registration slip").click()
1338        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1339        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1340        # Students can remove course tickets
1341        self.browser.open(self.student_path + '/studycourse/200/edit')
1342        self.browser.getControl("Remove selected", index=0).click()
1343        self.assertTrue('No ticket selected' in self.browser.contents)
1344        # No ticket can be selected since the carry-over course is a core course
1345        self.assertRaises(
1346            LookupError, self.browser.getControl, name='val_id')
1347        self.student['studycourse']['200']['COURSE1'].mandatory = False
1348        self.browser.open(self.student_path + '/studycourse/200/edit')
1349        # Course list can't be registered if total_credits exceeds max_credits
1350        self.student['studycourse']['200']['COURSE1'].credits = 60
1351        self.browser.getControl("Register course list").click()
1352        self.assertTrue('Maximum credits of 50 exceeded' in self.browser.contents)
1353        # Student can now remove the ticket
1354        ctrl = self.browser.getControl(name='val_id')
1355        ctrl.getControl(value='COURSE1').selected = True
1356        self.browser.getControl("Remove selected", index=0).click()
1357        self.assertTrue('Successfully removed' in self.browser.contents)
1358        # Course list can be registered, even if it's empty
1359        self.browser.getControl("Register course list").click()
1360        self.assertTrue('Course list has been registered' in self.browser.contents)
1361        self.assertEqual(self.student.state, 'courses registered')
1362        return
1363
1364    def test_postgraduate_student_access(self):
1365        self.certificate.study_mode = 'pg_ft'
1366        self.certificate.start_level = 999
1367        self.certificate.end_level = 999
1368        self.student['studycourse'].current_level = 999
1369        IWorkflowState(self.student).setState('school fee paid')
1370        self.browser.open(self.login_path)
1371        self.browser.getControl(name="form.login").value = self.student_id
1372        self.browser.getControl(name="form.password").value = 'spwd'
1373        self.browser.getControl("Login").click()
1374        self.assertTrue(
1375            'You logged in.' in self.browser.contents)
1376        # Now students can add the current study level
1377        self.browser.getLink("Study Course").click()
1378        self.browser.getLink("Add course list").click()
1379        self.assertMatches('...Add current level Postgraduate Level...',
1380                           self.browser.contents)
1381        self.browser.getControl("Create course list now").click()
1382        # A level with one course ticket was created
1383        self.assertEqual(self.student['studycourse']['999'].number_of_tickets, 0)
1384        self.browser.getLink("999").click()
1385        self.browser.getLink("Edit course list").click()
1386        self.browser.getControl("Add course ticket").click()
1387        self.browser.getControl(name="form.course").value = ['COURSE1']
1388        self.browser.getControl("Add course ticket").click()
1389        self.assertMatches('...Successfully added COURSE1...',
1390                           self.browser.contents)
1391        # Postgraduate students can't register course lists
1392        self.browser.getControl("Register course list").click()
1393        self.assertTrue("your course list can't bee registered"
1394            in self.browser.contents)
1395        self.assertEqual(self.student.state, 'school fee paid')
1396        return
1397
1398    def test_student_clearance_wo_clrcode(self):
1399        IWorkflowState(self.student).setState('clearance started')
1400        self.browser.open(self.login_path)
1401        self.browser.getControl(name="form.login").value = self.student_id
1402        self.browser.getControl(name="form.password").value = 'spwd'
1403        self.browser.getControl("Login").click()
1404        self.student.clearance_locked = False
1405        self.browser.open(self.edit_clearance_path)
1406        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
1407        self.browser.getControl("Save and request clearance").click()
1408        self.assertMatches('...Clearance has been requested...',
1409                           self.browser.contents)
1410
1411    def test_manage_payments(self):
1412        # Managers can add online school fee payment tickets
1413        # if certain requirements are met
1414        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1415        self.browser.open(self.payments_path)
1416        IWorkflowState(self.student).setState('cleared')
1417        self.browser.getControl("Add online payment ticket").click()
1418        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1419        self.browser.getControl("Create ticket").click()
1420        self.assertMatches('...ticket created...',
1421                           self.browser.contents)
1422        ctrl = self.browser.getControl(name='val_id')
1423        value = ctrl.options[0]
1424        self.browser.getLink(value).click()
1425        self.assertMatches('...Amount Authorized...',
1426                           self.browser.contents)
1427        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
1428        payment_url = self.browser.url
1429
1430        # The pdf payment slip can't yet be opened
1431        #self.browser.open(payment_url + '/payment_slip.pdf')
1432        #self.assertMatches('...Ticket not yet paid...',
1433        #                   self.browser.contents)
1434
1435        # The same payment (with same p_item, p_session and p_category)
1436        # can be initialized a second time if the former ticket is not yet paid.
1437        self.browser.open(self.payments_path)
1438        self.browser.getControl("Add online payment ticket").click()
1439        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1440        self.browser.getControl("Create ticket").click()
1441        self.assertMatches('...Payment ticket created...',
1442                           self.browser.contents)
1443
1444        # Managers can approve the payment
1445        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
1446        self.browser.open(payment_url)
1447        self.browser.getLink("Approve payment").click()
1448        self.assertMatches('...Payment approved...',
1449                          self.browser.contents)
1450
1451        # The authorized amount has been stored in the access code
1452        self.assertEqual(
1453            self.app['accesscodes']['SFE-0'].values()[0].cost,40000.0)
1454
1455        # Payments can't be approved twice
1456        self.browser.open(payment_url + '/approve')
1457        self.assertMatches('...This ticket has already been paid...',
1458                          self.browser.contents)
1459
1460        # Now the first ticket is paid and no more ticket of same type
1461        # (with same p_item, p_session and p_category) can be added
1462        self.browser.open(self.payments_path)
1463        self.browser.getControl("Add online payment ticket").click()
1464        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1465        self.browser.getControl("Create ticket").click()
1466        self.assertMatches(
1467            '...This type of payment has already been made...',
1468            self.browser.contents)
1469
1470        # Managers can open the pdf payment slip
1471        self.browser.open(payment_url)
1472        self.browser.getLink("Download payment slip").click()
1473        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1474        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1475
1476        # Managers can remove online school fee payment tickets
1477        self.browser.open(self.payments_path)
1478        self.browser.getControl("Remove selected").click()
1479        self.assertMatches('...No payment selected...', self.browser.contents)
1480        ctrl = self.browser.getControl(name='val_id')
1481        value = ctrl.options[0]
1482        ctrl.getControl(value=value).selected = True
1483        self.browser.getControl("Remove selected", index=0).click()
1484        self.assertTrue('Successfully removed' in self.browser.contents)
1485
1486        # Managers can add online clearance payment tickets
1487        self.browser.open(self.payments_path + '/addop')
1488        self.browser.getControl(name="form.p_category").value = ['clearance']
1489        self.browser.getControl("Create ticket").click()
1490        self.assertMatches('...ticket created...',
1491                           self.browser.contents)
1492
1493        # Managers can approve the payment
1494        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
1495        ctrl = self.browser.getControl(name='val_id')
1496        value = ctrl.options[1] # The clearance payment is the second in the table
1497        self.browser.getLink(value).click()
1498        self.browser.open(self.browser.url + '/approve')
1499        self.assertMatches('...Payment approved...',
1500                          self.browser.contents)
1501        expected = '''...
1502        <td>
1503          <span>Paid</span>
1504        </td>...'''
1505        self.assertMatches(expected,self.browser.contents)
1506        # The new CLR-0 pin has been created
1507        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
1508        pin = self.app['accesscodes']['CLR-0'].keys()[0]
1509        ac = self.app['accesscodes']['CLR-0'][pin]
1510        self.assertEqual(ac.owner, self.student_id)
1511        self.assertEqual(ac.cost, 3456.0)
1512        return
1513
1514    def test_student_payments(self):
1515        # Login
1516        self.browser.open(self.login_path)
1517        self.browser.getControl(name="form.login").value = self.student_id
1518        self.browser.getControl(name="form.password").value = 'spwd'
1519        self.browser.getControl("Login").click()
1520
1521        # Students can add online clearance payment tickets
1522        self.browser.open(self.payments_path + '/addop')
1523        self.browser.getControl(name="form.p_category").value = ['clearance']
1524        self.browser.getControl("Create ticket").click()
1525        self.assertMatches('...ticket created...',
1526                           self.browser.contents)
1527
1528        # Students can't approve the payment
1529        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
1530        ctrl = self.browser.getControl(name='val_id')
1531        value = ctrl.options[0]
1532        self.browser.getLink(value).click()
1533        payment_url = self.browser.url
1534        self.assertRaises(
1535            Unauthorized, self.browser.open, payment_url + '/approve')
1536        # In the base package they can 'use' a fake approval view
1537        self.browser.open(payment_url + '/fake_approve')
1538        self.assertMatches('...Payment approved...',
1539                          self.browser.contents)
1540        expected = '''...
1541        <td>
1542          <span>Paid</span>
1543        </td>...'''
1544        expected = '''...
1545        <td>
1546          <span>Paid</span>
1547        </td>...'''
1548        self.assertMatches(expected,self.browser.contents)
1549        payment_id = self.student['payments'].keys()[0]
1550        payment = self.student['payments'][payment_id]
1551        self.assertEqual(payment.p_state, 'paid')
1552        self.assertEqual(payment.r_amount_approved, 3456.0)
1553        self.assertEqual(payment.r_code, 'AP')
1554        self.assertEqual(payment.r_desc, u'Payment approved by Anna Tester')
1555        # The new CLR-0 pin has been created
1556        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
1557        pin = self.app['accesscodes']['CLR-0'].keys()[0]
1558        ac = self.app['accesscodes']['CLR-0'][pin]
1559        self.assertEqual(ac.owner, self.student_id)
1560        self.assertEqual(ac.cost, 3456.0)
1561
1562        # Students can open the pdf payment slip
1563        self.browser.open(payment_url + '/payment_slip.pdf')
1564        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1565        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1566
1567        # The new CLR-0 pin can be used for starting clearance
1568        # but they have to upload a passport picture first
1569        # which is only possible in state admitted
1570        self.browser.open(self.student_path + '/change_portrait')
1571        self.assertMatches('...form is locked...',
1572                          self.browser.contents)
1573        IWorkflowInfo(self.student).fireTransition('admit')
1574        self.browser.open(self.student_path + '/change_portrait')
1575        image = open(SAMPLE_IMAGE, 'rb')
1576        ctrl = self.browser.getControl(name='passportuploadedit')
1577        file_ctrl = ctrl.mech_control
1578        file_ctrl.add_file(image, filename='my_photo.jpg')
1579        self.browser.getControl(
1580            name='upload_passportuploadedit').click()
1581        self.browser.open(self.student_path + '/start_clearance')
1582        parts = pin.split('-')[1:]
1583        clrseries, clrnumber = parts
1584        self.browser.getControl(name="ac_series").value = clrseries
1585        self.browser.getControl(name="ac_number").value = clrnumber
1586        self.browser.getControl("Start clearance now").click()
1587        self.assertMatches('...Clearance process has been started...',
1588                           self.browser.contents)
1589
1590        # Students can add online school fee payment tickets.
1591        IWorkflowState(self.student).setState('returning')
1592        self.browser.open(self.payments_path)
1593        self.browser.getControl("Add online payment ticket").click()
1594        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1595        self.browser.getControl("Create ticket").click()
1596        self.assertMatches('...ticket created...',
1597                           self.browser.contents)
1598        ctrl = self.browser.getControl(name='val_id')
1599        value = ctrl.options[0]
1600        self.browser.getLink(value).click()
1601        self.assertMatches('...Amount Authorized...',
1602                           self.browser.contents)
1603        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
1604        # Payment session and will be calculated as defined
1605        # in w.k.students.utils because we set changed the state
1606        # to returning
1607        self.assertEqual(self.student['payments'][value].p_session, 2005)
1608        self.assertEqual(self.student['payments'][value].p_level, 200)
1609
1610        # Student is the payee of the payment ticket.
1611        webservice = IPaymentWebservice(self.student['payments'][value])
1612        self.assertEqual(webservice.display_fullname, 'Anna Tester')
1613        self.assertEqual(webservice.id, self.student_id)
1614        self.assertEqual(webservice.faculty, 'fac1')
1615        self.assertEqual(webservice.department, 'dep1')
1616
1617        # We simulate the approval
1618        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
1619        self.browser.open(self.browser.url + '/fake_approve')
1620        self.assertMatches('...Payment approved...',
1621                          self.browser.contents)
1622
1623        # Students can remove only online payment tickets which have
1624        # not received a valid callback
1625        self.browser.open(self.payments_path)
1626        self.assertRaises(
1627            LookupError, self.browser.getControl, name='val_id')
1628        self.browser.open(self.payments_path + '/addop')
1629        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1630        self.browser.getControl("Create ticket").click()
1631        self.browser.open(self.payments_path)
1632        ctrl = self.browser.getControl(name='val_id')
1633        value = ctrl.options[0]
1634        ctrl.getControl(value=value).selected = True
1635        self.browser.getControl("Remove selected", index=0).click()
1636        self.assertTrue('Successfully removed' in self.browser.contents)
1637
1638        # The new SFE-0 pin can be used for starting new session
1639        self.browser.open(self.studycourse_path)
1640        self.browser.getLink('Start new session').click()
1641        pin = self.app['accesscodes']['SFE-0'].keys()[0]
1642        parts = pin.split('-')[1:]
1643        sfeseries, sfenumber = parts
1644        self.browser.getControl(name="ac_series").value = sfeseries
1645        self.browser.getControl(name="ac_number").value = sfenumber
1646        self.browser.getControl("Start now").click()
1647        self.assertMatches('...Session started...',
1648                           self.browser.contents)
1649        self.assertTrue(self.student.state == 'school fee paid')
1650        return
1651
1652    def test_student_previous_payments(self):
1653        configuration = createObject('waeup.SessionConfiguration')
1654        configuration.academic_session = 2000
1655        configuration.clearance_fee = 3456.0
1656        configuration.booking_fee = 123.4
1657        self.app['configuration'].addSessionConfiguration(configuration)
1658        configuration2 = createObject('waeup.SessionConfiguration')
1659        configuration2.academic_session = 2003
1660        configuration2.clearance_fee = 3456.0
1661        configuration2.booking_fee = 123.4
1662        self.app['configuration'].addSessionConfiguration(configuration2)
1663
1664        self.student['studycourse'].entry_session = 2002
1665
1666        # Login
1667        self.browser.open(self.login_path)
1668        self.browser.getControl(name="form.login").value = self.student_id
1669        self.browser.getControl(name="form.password").value = 'spwd'
1670        self.browser.getControl("Login").click()
1671
1672        # Students can add previous school fee payment tickets in any state.
1673        IWorkflowState(self.student).setState('courses registered')
1674        self.browser.open(self.payments_path)
1675        self.browser.getControl("Add online payment ticket").click()
1676        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1677        self.browser.getControl("Create ticket").click()
1678
1679        # Amount cannot be determined since the state is not
1680        # 'cleared' or 'returning'
1681        self.assertMatches('...Amount could not be determined...',
1682                           self.browser.contents)
1683        self.assertMatches('...Would you like to pay for a previous session?...',
1684                           self.browser.contents)
1685
1686        # Previous session payment form is provided
1687        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1688        self.browser.getControl(name="form.p_session").value = ['2000']
1689        self.browser.getControl(name="form.p_level").value = ['300']
1690        self.browser.getControl("Create ticket").click()
1691        self.assertMatches('...The previous session must not fall below...',
1692                           self.browser.contents)
1693        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1694        self.browser.getControl(name="form.p_session").value = ['2004']
1695        self.browser.getControl(name="form.p_level").value = ['300']
1696        self.browser.getControl("Create ticket").click()
1697        self.assertMatches('...This is not a previous session...',
1698                           self.browser.contents)
1699        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1700        self.browser.getControl(name="form.p_session").value = ['2003']
1701        self.browser.getControl(name="form.p_level").value = ['300']
1702        self.browser.getControl("Create ticket").click()
1703        self.assertMatches('...ticket created...',
1704                           self.browser.contents)
1705        ctrl = self.browser.getControl(name='val_id')
1706        value = ctrl.options[0]
1707        self.browser.getLink(value).click()
1708        self.assertMatches('...Amount Authorized...',
1709                           self.browser.contents)
1710        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
1711
1712        # Payment session is properly set
1713        self.assertEqual(self.student['payments'][value].p_session, 2003)
1714        self.assertEqual(self.student['payments'][value].p_level, 300)
1715
1716        # We simulate the approval
1717        self.browser.open(self.browser.url + '/fake_approve')
1718        self.assertMatches('...Payment approved...',
1719                          self.browser.contents)
1720
1721        # No AC has been created
1722        self.assertEqual(len(self.app['accesscodes']['SFE-0'].keys()), 0)
1723        self.assertTrue(self.student['payments'][value].ac is None)
1724
1725        # Current payment flag is set False
1726        self.assertFalse(self.student['payments'][value].p_current)
1727        return
1728
1729    def test_postgraduate_student_payments(self):
1730        self.certificate.study_mode = 'pg_ft'
1731        self.certificate.start_level = 999
1732        self.certificate.end_level = 999
1733        self.student['studycourse'].current_level = 999
1734        # Login
1735        self.browser.open(self.login_path)
1736        self.browser.getControl(name="form.login").value = self.student_id
1737        self.browser.getControl(name="form.password").value = 'spwd'
1738        self.browser.getControl("Login").click()
1739        # Students can add online school fee payment tickets.
1740        IWorkflowState(self.student).setState('cleared')
1741        self.browser.open(self.payments_path)
1742        self.browser.getControl("Add online payment ticket").click()
1743        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1744        self.browser.getControl("Create ticket").click()
1745        self.assertMatches('...ticket created...',
1746                           self.browser.contents)
1747        ctrl = self.browser.getControl(name='val_id')
1748        value = ctrl.options[0]
1749        self.browser.getLink(value).click()
1750        self.assertMatches('...Amount Authorized...',
1751                           self.browser.contents)
1752        # Payment session and level are current ones.
1753        # Postgrads have to pay school_fee_1.
1754        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
1755        self.assertEqual(self.student['payments'][value].p_session, 2004)
1756        self.assertEqual(self.student['payments'][value].p_level, 999)
1757
1758        # We simulate the approval
1759        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
1760        self.browser.open(self.browser.url + '/fake_approve')
1761        self.assertMatches('...Payment approved...',
1762                          self.browser.contents)
1763
1764        # The new SFE-0 pin can be used for starting session
1765        self.browser.open(self.studycourse_path)
1766        self.browser.getLink('Start new session').click()
1767        pin = self.app['accesscodes']['SFE-0'].keys()[0]
1768        parts = pin.split('-')[1:]
1769        sfeseries, sfenumber = parts
1770        self.browser.getControl(name="ac_series").value = sfeseries
1771        self.browser.getControl(name="ac_number").value = sfenumber
1772        self.browser.getControl("Start now").click()
1773        self.assertMatches('...Session started...',
1774                           self.browser.contents)
1775        self.assertTrue(self.student.state == 'school fee paid')
1776
1777        # Postgrad students do not need to register courses the
1778        # can just pay for the next session.
1779        self.browser.open(self.payments_path)
1780        # Remove first payment to be sure that we access the right ticket
1781        del self.student['payments'][value]
1782        self.browser.getControl("Add online payment ticket").click()
1783        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1784        self.browser.getControl("Create ticket").click()
1785        ctrl = self.browser.getControl(name='val_id')
1786        value = ctrl.options[0]
1787        self.browser.getLink(value).click()
1788        # Payment session has increased by one, payment level remains the same.
1789        # Returning Postgraduates have to pay school_fee_2.
1790        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
1791        self.assertEqual(self.student['payments'][value].p_session, 2005)
1792        self.assertEqual(self.student['payments'][value].p_level, 999)
1793
1794        # Student is still in old session
1795        self.assertEqual(self.student.current_session, 2004)
1796
1797        # We do not need to pay the ticket if any other
1798        # SFE pin is provided
1799        pin_container = self.app['accesscodes']
1800        pin_container.createBatch(
1801            datetime.utcnow(), 'some_userid', 'SFE', 9.99, 1)
1802        pin = pin_container['SFE-1'].values()[0].representation
1803        sfeseries, sfenumber = pin.split('-')[1:]
1804        # The new SFE-1 pin can be used for starting new session
1805        self.browser.open(self.studycourse_path)
1806        self.browser.getLink('Start new session').click()
1807        self.browser.getControl(name="ac_series").value = sfeseries
1808        self.browser.getControl(name="ac_number").value = sfenumber
1809        self.browser.getControl("Start now").click()
1810        self.assertMatches('...Session started...',
1811                           self.browser.contents)
1812        self.assertTrue(self.student.state == 'school fee paid')
1813        # Student is in new session
1814        self.assertEqual(self.student.current_session, 2005)
1815        self.assertEqual(self.student['studycourse'].current_level, 999)
1816        return
1817
1818    def test_manage_accommodation(self):
1819        # Managers can add online booking fee payment tickets and open the
1820        # callback view (see test_manage_payments)
1821        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1822        self.browser.open(self.payments_path)
1823        self.browser.getControl("Add online payment ticket").click()
1824        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1825        # If student is not in accommodation session, payment cannot be processed
1826        self.app['hostels'].accommodation_session = 2011
1827        self.browser.getControl("Create ticket").click()
1828        self.assertMatches('...Your current session does not match...',
1829                           self.browser.contents)
1830        self.app['hostels'].accommodation_session = 2004
1831        self.browser.getControl("Add online payment ticket").click()
1832        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1833        self.browser.getControl("Create ticket").click()
1834        ctrl = self.browser.getControl(name='val_id')
1835        value = ctrl.options[0]
1836        self.browser.getLink(value).click()
1837        self.browser.open(self.browser.url + '/approve')
1838        # The new HOS-0 pin has been created
1839        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
1840        pin = self.app['accesscodes']['HOS-0'].keys()[0]
1841        ac = self.app['accesscodes']['HOS-0'][pin]
1842        self.assertEqual(ac.owner, self.student_id)
1843        parts = pin.split('-')[1:]
1844        sfeseries, sfenumber = parts
1845        # Managers can use HOS code and book a bed space with it
1846        self.browser.open(self.acco_path)
1847        self.browser.getLink("Book accommodation").click()
1848        self.assertMatches('...You are in the wrong...',
1849                           self.browser.contents)
1850        IWorkflowInfo(self.student).fireTransition('admit')
1851        # An existing HOS code can only be used if students
1852        # are in accommodation session
1853        self.student['studycourse'].current_session = 2003
1854        self.browser.getLink("Book accommodation").click()
1855        self.assertMatches('...Your current session does not match...',
1856                           self.browser.contents)
1857        self.student['studycourse'].current_session = 2004
1858        # All requirements are met and ticket can be created
1859        self.browser.getLink("Book accommodation").click()
1860        self.assertMatches('...Activation Code:...',
1861                           self.browser.contents)
1862        self.browser.getControl(name="ac_series").value = sfeseries
1863        self.browser.getControl(name="ac_number").value = sfenumber
1864        self.browser.getControl("Create bed ticket").click()
1865        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1866                           self.browser.contents)
1867        # Bed has been allocated
1868        bed1 = self.app['hostels']['hall-1']['hall-1_A_101_A']
1869        self.assertTrue(bed1.owner == self.student_id)
1870        # BedTicketAddPage is now blocked
1871        self.browser.getLink("Book accommodation").click()
1872        self.assertMatches('...You already booked a bed space...',
1873            self.browser.contents)
1874        # The bed ticket displays the data correctly
1875        self.browser.open(self.acco_path + '/2004')
1876        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1877                           self.browser.contents)
1878        self.assertMatches('...2004/2005...', self.browser.contents)
1879        self.assertMatches('...regular_male_fr...', self.browser.contents)
1880        self.assertMatches('...%s...' % pin, self.browser.contents)
1881        # Managers can relocate students if the student's bed_type has changed
1882        self.browser.getLink("Relocate student").click()
1883        self.assertMatches(
1884            "...Student can't be relocated...", self.browser.contents)
1885        self.student.sex = u'f'
1886        self.browser.getLink("Relocate student").click()
1887        self.assertMatches(
1888            "...Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
1889        self.assertTrue(bed1.owner == NOT_OCCUPIED)
1890        bed2 = self.app['hostels']['hall-1']['hall-1_A_101_B']
1891        self.assertTrue(bed2.owner == self.student_id)
1892        self.assertTrue(self.student['accommodation'][
1893            '2004'].bed_type == u'regular_female_fr')
1894        # The payment object still shows the original payment item
1895        payment_id = self.student['payments'].keys()[0]
1896        payment = self.student['payments'][payment_id]
1897        self.assertTrue(payment.p_item == u'regular_male_fr')
1898        # Managers can relocate students if the bed's bed_type has changed
1899        bed1.bed_type = u'regular_female_fr'
1900        bed2.bed_type = u'regular_male_fr'
1901        notify(grok.ObjectModifiedEvent(bed1))
1902        notify(grok.ObjectModifiedEvent(bed2))
1903        self.browser.getLink("Relocate student").click()
1904        self.assertMatches(
1905            "...Student relocated...", self.browser.contents)
1906        self.assertMatches(
1907            "... Hall 1, Block A, Room 101, Bed A...", self.browser.contents)
1908        self.assertMatches(bed1.owner, self.student_id)
1909        self.assertMatches(bed2.owner, NOT_OCCUPIED)
1910        # Managers can't relocate students if bed is reserved
1911        self.student.sex = u'm'
1912        bed1.bed_type = u'regular_female_reserved'
1913        notify(grok.ObjectModifiedEvent(bed1))
1914        self.browser.getLink("Relocate student").click()
1915        self.assertMatches(
1916            "...Students in reserved beds can't be relocated...",
1917            self.browser.contents)
1918        # Managers can relocate students if booking has been cancelled but
1919        # other bed space has been manually allocated after cancellation
1920        old_owner = bed1.releaseBed()
1921        self.assertMatches(old_owner, self.student_id)
1922        bed2.owner = self.student_id
1923        self.browser.open(self.acco_path + '/2004')
1924        self.assertMatches(
1925            "...booking cancelled...", self.browser.contents)
1926        self.browser.getLink("Relocate student").click()
1927        # We didn't informed the catalog therefore the new owner is not found
1928        self.assertMatches(
1929            "...There is no free bed in your category regular_male_fr...",
1930            self.browser.contents)
1931        # Now we fire the event properly
1932        notify(grok.ObjectModifiedEvent(bed2))
1933        self.browser.getLink("Relocate student").click()
1934        self.assertMatches(
1935            "...Student relocated...", self.browser.contents)
1936        self.assertMatches(
1937            "... Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
1938          # Managers can delete bed tickets
1939        self.browser.open(self.acco_path)
1940        ctrl = self.browser.getControl(name='val_id')
1941        value = ctrl.options[0]
1942        ctrl.getControl(value=value).selected = True
1943        self.browser.getControl("Remove selected", index=0).click()
1944        self.assertMatches('...Successfully removed...', self.browser.contents)
1945        # The bed has been properly released by the event handler
1946        self.assertMatches(bed1.owner, NOT_OCCUPIED)
1947        self.assertMatches(bed2.owner, NOT_OCCUPIED)
1948        return
1949
1950    def test_student_accommodation(self):
1951        # Login
1952        self.browser.open(self.login_path)
1953        self.browser.getControl(name="form.login").value = self.student_id
1954        self.browser.getControl(name="form.password").value = 'spwd'
1955        self.browser.getControl("Login").click()
1956
1957        # Students can add online booking fee payment tickets and open the
1958        # callback view (see test_manage_payments)
1959        self.browser.getLink("Payments").click()
1960        self.browser.getControl("Add online payment ticket").click()
1961        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1962        self.browser.getControl("Create ticket").click()
1963        ctrl = self.browser.getControl(name='val_id')
1964        value = ctrl.options[0]
1965        self.browser.getLink(value).click()
1966        self.browser.open(self.browser.url + '/fake_approve')
1967        # The new HOS-0 pin has been created
1968        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
1969        pin = self.app['accesscodes']['HOS-0'].keys()[0]
1970        ac = self.app['accesscodes']['HOS-0'][pin]
1971        parts = pin.split('-')[1:]
1972        sfeseries, sfenumber = parts
1973
1974        # Students can use HOS code and book a bed space with it ...
1975        self.browser.open(self.acco_path)
1976        # ... but not if booking period has expired ...
1977        self.app['hostels'].enddate = datetime.now(pytz.utc)
1978        self.browser.getLink("Book accommodation").click()
1979        self.assertMatches('...Outside booking period: ...',
1980                           self.browser.contents)
1981        self.app['hostels'].enddate = datetime.now(pytz.utc) + timedelta(days=10)
1982        # ... or student is not the an allowed state ...
1983        self.browser.getLink("Book accommodation").click()
1984        self.assertMatches('...You are in the wrong...',
1985                           self.browser.contents)
1986        IWorkflowInfo(self.student).fireTransition('admit')
1987        self.browser.getLink("Book accommodation").click()
1988        self.assertMatches('...Activation Code:...',
1989                           self.browser.contents)
1990        # Student can't used faked ACs ...
1991        self.browser.getControl(name="ac_series").value = u'nonsense'
1992        self.browser.getControl(name="ac_number").value = sfenumber
1993        self.browser.getControl("Create bed ticket").click()
1994        self.assertMatches('...Activation code is invalid...',
1995                           self.browser.contents)
1996        # ... or ACs owned by somebody else.
1997        ac.owner = u'Anybody'
1998        self.browser.getControl(name="ac_series").value = sfeseries
1999        self.browser.getControl(name="ac_number").value = sfenumber
2000        self.browser.getControl("Create bed ticket").click()
2001        self.assertMatches('...You are not the owner of this access code...',
2002                           self.browser.contents)
2003        ac.owner = self.student_id
2004        self.browser.getControl(name="ac_series").value = sfeseries
2005        self.browser.getControl(name="ac_number").value = sfenumber
2006        self.browser.getControl("Create bed ticket").click()
2007        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
2008                           self.browser.contents)
2009
2010        # Bed has been allocated
2011        bed = self.app['hostels']['hall-1']['hall-1_A_101_A']
2012        self.assertTrue(bed.owner == self.student_id)
2013
2014        # BedTicketAddPage is now blocked
2015        self.browser.getLink("Book accommodation").click()
2016        self.assertMatches('...You already booked a bed space...',
2017            self.browser.contents)
2018
2019        # The bed ticket displays the data correctly
2020        self.browser.open(self.acco_path + '/2004')
2021        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
2022                           self.browser.contents)
2023        self.assertMatches('...2004/2005...', self.browser.contents)
2024        self.assertMatches('...regular_male_fr...', self.browser.contents)
2025        self.assertMatches('...%s...' % pin, self.browser.contents)
2026
2027        # Students can open the pdf slip
2028        self.browser.open(self.browser.url + '/bed_allocation.pdf')
2029        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2030        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2031
2032        # Students can't relocate themselves
2033        self.assertFalse('Relocate' in self.browser.contents)
2034        relocate_path = self.acco_path + '/2004/relocate'
2035        self.assertRaises(
2036            Unauthorized, self.browser.open, relocate_path)
2037
2038        # Students can't the Remove button and check boxes
2039        self.browser.open(self.acco_path)
2040        self.assertFalse('Remove' in self.browser.contents)
2041        self.assertFalse('val_id' in self.browser.contents)
2042        return
2043
2044    def test_change_password_request(self):
2045        self.browser.open('http://localhost/app/changepw')
2046        self.browser.getControl(name="form.identifier").value = '123'
2047        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
2048        self.browser.getControl("Send login credentials").click()
2049        self.assertTrue('An email with' in self.browser.contents)
2050
2051    def test_change_current_mode(self):
2052        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
2053        self.browser.open(self.clearance_path)
2054        self.assertFalse('Employer' in self.browser.contents)
2055        self.browser.open(self.manage_clearance_path)
2056        self.assertFalse('Employer' in self.browser.contents)
2057        self.student.clearance_locked = False
2058        self.browser.open(self.edit_clearance_path)
2059        self.assertFalse('Employer' in self.browser.contents)
2060        # Now we change the study mode of the certificate and a different
2061        # interface is used by clearance views.
2062        self.certificate.study_mode = 'pg_ft'
2063        # Invariants are not being checked here?!
2064        self.certificate.end_level = 100
2065        self.browser.open(self.clearance_path)
2066        self.assertTrue('Employer' in self.browser.contents)
2067        self.browser.open(self.manage_clearance_path)
2068        self.assertTrue('Employer' in self.browser.contents)
2069        self.browser.open(self.edit_clearance_path)
2070        self.assertTrue('Employer' in self.browser.contents)
2071
2072    def test_activate_deactivate_buttons(self):
2073        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
2074        self.browser.open(self.student_path)
2075        self.browser.getLink("Deactivate").click()
2076        self.assertTrue(
2077            'Student account has been deactivated.' in self.browser.contents)
2078        self.assertTrue(
2079            'Base Data (account deactivated)' in self.browser.contents)
2080        self.assertTrue(self.student.suspended)
2081        self.browser.getLink("Activate").click()
2082        self.assertTrue(
2083            'Student account has been activated.' in self.browser.contents)
2084        self.assertFalse(
2085            'Base Data (account deactivated)' in self.browser.contents)
2086        self.assertFalse(self.student.suspended)
2087        # History messages have been added ...
2088        self.browser.getLink("History").click()
2089        self.assertTrue(
2090            'Student account deactivated by Manager<br />' in self.browser.contents)
2091        self.assertTrue(
2092            'Student account activated by Manager<br />' in self.browser.contents)
2093        # ... and actions have been logged.
2094        logfile = os.path.join(
2095            self.app['datacenter'].storage, 'logs', 'students.log')
2096        logcontent = open(logfile).read()
2097        self.assertTrue('zope.mgr - students.browser.StudentDeactivatePage - '
2098                        'K1000000 - account deactivated' in logcontent)
2099        self.assertTrue('zope.mgr - students.browser.StudentActivatePage - '
2100                        'K1000000 - account activated' in logcontent)
2101
2102    def test_student_locked_level_forms(self):
2103
2104        # Add two study levels, one current and one previous
2105        studylevel = createObject(u'waeup.StudentStudyLevel')
2106        studylevel.level = 100
2107        self.student['studycourse'].addStudentStudyLevel(
2108            self.certificate, studylevel)
2109        studylevel = createObject(u'waeup.StudentStudyLevel')
2110        studylevel.level = 200
2111        self.student['studycourse'].addStudentStudyLevel(
2112            self.certificate, studylevel)
2113        IWorkflowState(self.student).setState('school fee paid')
2114        self.student['studycourse'].current_level = 200
2115
2116        self.browser.open(self.login_path)
2117        self.browser.getControl(name="form.login").value = self.student_id
2118        self.browser.getControl(name="form.password").value = 'spwd'
2119        self.browser.getControl("Login").click()
2120
2121        self.browser.open(self.student_path + '/studycourse/200/edit')
2122        self.assertFalse('The requested form is locked' in self.browser.contents)
2123        self.browser.open(self.student_path + '/studycourse/100/edit')
2124        self.assertTrue('The requested form is locked' in self.browser.contents)
2125
2126        self.browser.open(self.student_path + '/studycourse/200/ctadd')
2127        self.assertFalse('The requested form is locked' in self.browser.contents)
2128        self.browser.open(self.student_path + '/studycourse/100/ctadd')
2129        self.assertTrue('The requested form is locked' in self.browser.contents)
2130
2131        IWorkflowState(self.student).setState('courses registered')
2132        self.browser.open(self.student_path + '/studycourse/200/edit')
2133        self.assertTrue('The requested form is locked' in self.browser.contents)
2134        self.browser.open(self.student_path + '/studycourse/200/ctadd')
2135        self.assertTrue('The requested form is locked' in self.browser.contents)
2136
2137
2138    def test_manage_student_transfer(self):
2139        # Add second certificate
2140        self.certificate2 = createObject('waeup.Certificate')
2141        self.certificate2.code = u'CERT2'
2142        self.certificate2.study_mode = 'ug_ft'
2143        self.certificate2.start_level = 999
2144        self.certificate2.end_level = 999
2145        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
2146            self.certificate2)
2147
2148        # Add study level to old study course
2149        studylevel = createObject(u'waeup.StudentStudyLevel')
2150        studylevel.level = 200
2151        self.student['studycourse'].addStudentStudyLevel(
2152            self.certificate, studylevel)
2153        studylevel = createObject(u'waeup.StudentStudyLevel')
2154        studylevel.level = 999
2155        self.student['studycourse'].addStudentStudyLevel(
2156            self.certificate, studylevel)
2157
2158        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
2159        self.browser.open(self.student_path)
2160        self.browser.getLink("Transfer").click()
2161        self.browser.getControl(name="form.certificate").value = ['CERT2']
2162        self.browser.getControl(name="form.current_session").value = ['2011']
2163        self.browser.getControl(name="form.current_level").value = ['200']
2164        self.browser.getControl("Transfer").click()
2165        self.assertTrue(
2166            'Current level does not match certificate levels'
2167            in self.browser.contents)
2168        self.browser.getControl(name="form.current_level").value = ['999']
2169        self.browser.getControl("Transfer").click()
2170        self.assertTrue('Successfully transferred' in self.browser.contents)
2171        # The catalog has been updated
2172        cat = queryUtility(ICatalog, name='students_catalog')
2173        results = list(
2174            cat.searchResults(
2175            certcode=('CERT2', 'CERT2')))
2176        self.assertTrue(results[0] is self.student)
2177        results = list(
2178            cat.searchResults(
2179            current_session=(2011, 2011)))
2180        self.assertTrue(results[0] is self.student)
2181        # Add study level to new study course
2182        studylevel = createObject(u'waeup.StudentStudyLevel')
2183        studylevel.level = 999
2184        self.student['studycourse'].addStudentStudyLevel(
2185            self.certificate, studylevel)
2186
2187        # Edit and add pages are locked for old study courses
2188        self.browser.open(self.student_path + '/studycourse/manage')
2189        self.assertFalse('The requested form is locked' in self.browser.contents)
2190        self.browser.open(self.student_path + '/studycourse_1/manage')
2191        self.assertTrue('The requested form is locked' in self.browser.contents)
2192
2193        self.browser.open(self.student_path + '/studycourse/start_session')
2194        self.assertFalse('The requested form is locked' in self.browser.contents)
2195        self.browser.open(self.student_path + '/studycourse_1/start_session')
2196        self.assertTrue('The requested form is locked' in self.browser.contents)
2197
2198        IWorkflowState(self.student).setState('school fee paid')
2199        self.browser.open(self.student_path + '/studycourse/add')
2200        self.assertFalse('The requested form is locked' in self.browser.contents)
2201        self.browser.open(self.student_path + '/studycourse_1/add')
2202        self.assertTrue('The requested form is locked' in self.browser.contents)
2203
2204        self.browser.open(self.student_path + '/studycourse/999/manage')
2205        self.assertFalse('The requested form is locked' in self.browser.contents)
2206        self.browser.open(self.student_path + '/studycourse_1/999/manage')
2207        self.assertTrue('The requested form is locked' in self.browser.contents)
2208
2209        self.browser.open(self.student_path + '/studycourse/999/validate_courses')
2210        self.assertFalse('The requested form is locked' in self.browser.contents)
2211        self.browser.open(self.student_path + '/studycourse_1/999/validate_courses')
2212        self.assertTrue('The requested form is locked' in self.browser.contents)
2213
2214        self.browser.open(self.student_path + '/studycourse/999/reject_courses')
2215        self.assertFalse('The requested form is locked' in self.browser.contents)
2216        self.browser.open(self.student_path + '/studycourse_1/999/reject_courses')
2217        self.assertTrue('The requested form is locked' in self.browser.contents)
2218
2219        self.browser.open(self.student_path + '/studycourse/999/add')
2220        self.assertFalse('The requested form is locked' in self.browser.contents)
2221        self.browser.open(self.student_path + '/studycourse_1/999/add')
2222        self.assertTrue('The requested form is locked' in self.browser.contents)
2223
2224        self.browser.open(self.student_path + '/studycourse/999/edit')
2225        self.assertFalse('The requested form is locked' in self.browser.contents)
2226        self.browser.open(self.student_path + '/studycourse_1/999/edit')
2227        self.assertTrue('The requested form is locked' in self.browser.contents)
2228
2229class StudentRequestPWTests(StudentsFullSetup):
2230    # Tests for student registration
2231
2232    layer = FunctionalLayer
2233
2234    def test_request_pw(self):
2235        # Student with wrong number can't be found.
2236        self.browser.open('http://localhost/app/requestpw')
2237        self.browser.getControl(name="form.firstname").value = 'Anna'
2238        self.browser.getControl(name="form.number").value = 'anynumber'
2239        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
2240        self.browser.getControl("Send login credentials").click()
2241        self.assertTrue('No student record found.'
2242            in self.browser.contents)
2243        # Anonymous is not informed that firstname verification failed.
2244        # It seems that the record doesn't exist.
2245        self.browser.open('http://localhost/app/requestpw')
2246        self.browser.getControl(name="form.firstname").value = 'Johnny'
2247        self.browser.getControl(name="form.number").value = '123'
2248        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
2249        self.browser.getControl("Send login credentials").click()
2250        self.assertTrue('No student record found.'
2251            in self.browser.contents)
2252        # Even with the correct firstname we can't register if a
2253        # password has been set and used.
2254        self.browser.getControl(name="form.firstname").value = 'Anna'
2255        self.browser.getControl(name="form.number").value = '123'
2256        self.browser.getControl("Send login credentials").click()
2257        self.assertTrue('Your password has already been set and used.'
2258            in self.browser.contents)
2259        self.browser.open('http://localhost/app/requestpw')
2260        self.app['students'][self.student_id].password = None
2261        # The firstname field, used for verification, is not case-sensitive.
2262        self.browser.getControl(name="form.firstname").value = 'aNNa'
2263        self.browser.getControl(name="form.number").value = '123'
2264        self.browser.getControl(name="form.email").value = 'new@yy.zz'
2265        self.browser.getControl("Send login credentials").click()
2266        # Yeah, we succeded ...
2267        self.assertTrue('Your password request was successful.'
2268            in self.browser.contents)
2269        # We can also use the matric_number instead.
2270        self.browser.open('http://localhost/app/requestpw')
2271        self.browser.getControl(name="form.firstname").value = 'aNNa'
2272        self.browser.getControl(name="form.number").value = '234'
2273        self.browser.getControl(name="form.email").value = 'new@yy.zz'
2274        self.browser.getControl("Send login credentials").click()
2275        self.assertTrue('Your password request was successful.'
2276            in self.browser.contents)
2277        # ... and  student can be found in the catalog via the email address
2278        cat = queryUtility(ICatalog, name='students_catalog')
2279        results = list(
2280            cat.searchResults(
2281            email=('new@yy.zz', 'new@yy.zz')))
2282        self.assertEqual(self.student,results[0])
2283        logfile = os.path.join(
2284            self.app['datacenter'].storage, 'logs', 'main.log')
2285        logcontent = open(logfile).read()
2286        self.assertTrue('zope.anybody - students.browser.StudentRequestPasswordPage - '
2287                        '234 (K1000000) - new@yy.zz' in logcontent)
2288        return
Note: See TracBrowser for help on using the repository browser.