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

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

Protect StudyLevelEditFormPage? and CourseTicketAddFormPage2. Students are not allowed to edit study levels which are not current.

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