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

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

Fix docstrings.

In my old ZODB which I used for testing there were orphaned catalog entries. I don't know why. The tests show clearly that the catalog is updated whenever a hostel is removed or cleared. However, to be 100% that nothing goes wrong I added some extra safety belts.

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