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

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

Make level_session required and let officers select the right level_session when adding study levels.

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