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

Last change on this file since 12847 was 12847, checked in by Henrik Bettermann, 10 years ago

Update security documentation.

  • Property svn:keywords set to Id
File size: 191.8 KB
Line 
1## $Id: test_browser.py 12847 2015-04-03 17:45:48Z 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
24import base64
25from datetime import datetime, timedelta, date
26from StringIO import StringIO
27import os
28import grok
29from zc.async.testing import wait_for_result
30from zope.event import notify
31from zope.component import createObject, queryUtility, getUtility
32from zope.component.hooks import setSite, clearSite
33from zope.catalog.interfaces import ICatalog
34from zope.security.interfaces import Unauthorized
35from zope.securitypolicy.interfaces import IPrincipalRoleManager
36from zope.testbrowser.testing import Browser
37from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
38from waeup.kofa.testing import FunctionalLayer, FunctionalTestCase
39from waeup.kofa.app import University
40from waeup.kofa.payments.interfaces import IPayer
41from waeup.kofa.students.interfaces import IStudentsUtils
42from waeup.kofa.students.student import Student
43from waeup.kofa.students.studylevel import StudentStudyLevel
44from waeup.kofa.university.faculty import Faculty
45from waeup.kofa.university.department import Department
46from waeup.kofa.interfaces import IUserAccount, IJobManager
47from waeup.kofa.authentication import LocalRoleSetEvent
48from waeup.kofa.hostels.hostel import Hostel, Bed, NOT_OCCUPIED
49from waeup.kofa.tests.test_async import FunctionalAsyncTestCase
50from waeup.kofa.browser.tests.test_pdf import samples_dir
51
52PH_LEN = 15911  # Length of placeholder file
53
54SAMPLE_IMAGE = os.path.join(os.path.dirname(__file__), 'test_image.jpg')
55SAMPLE_IMAGE_BMP = os.path.join(os.path.dirname(__file__), 'test_image.bmp')
56
57def lookup_submit_value(name, value, browser):
58    """Find a button with a certain value."""
59    for num in range(0, 100):
60        try:
61            button = browser.getControl(name=name, index=num)
62            if button.value.endswith(value):
63                return button
64        except IndexError:
65            break
66    return None
67
68class StudentsFullSetup(FunctionalTestCase):
69    # A test case that only contains a setup and teardown
70    #
71    # Complete setup for students handlings is rather complex and
72    # requires lots of things created before we can start. This is a
73    # setup that does all this, creates a university, creates PINs,
74    # etc.  so that we do not have to bother with that in different
75    # test cases.
76
77    layer = FunctionalLayer
78
79    def setUp(self):
80        super(StudentsFullSetup, self).setUp()
81
82        # Setup a sample site for each test
83        app = University()
84        self.dc_root = tempfile.mkdtemp()
85        app['datacenter'].setStoragePath(self.dc_root)
86
87        # Prepopulate the ZODB...
88        self.getRootFolder()['app'] = app
89        # we add the site immediately after creation to the
90        # ZODB. Catalogs and other local utilities are not setup
91        # before that step.
92        self.app = self.getRootFolder()['app']
93        # Set site here. Some of the following setup code might need
94        # to access grok.getSite() and should get our new app then
95        setSite(app)
96
97        # Add student with subobjects
98        student = createObject('waeup.Student')
99        student.firstname = u'Anna'
100        student.lastname = u'Tester'
101        student.reg_number = u'123'
102        student.matric_number = u'234'
103        student.sex = u'm'
104        student.email = 'aa@aa.ng'
105        student.phone = u'1234'
106        student.date_of_birth = date(1981, 2, 4)
107        self.app['students'].addStudent(student)
108        self.student_id = student.student_id
109        self.student = self.app['students'][self.student_id]
110
111        # Set password
112        IUserAccount(
113            self.app['students'][self.student_id]).setPassword('spwd')
114
115        self.login_path = 'http://localhost/app/login'
116        self.container_path = 'http://localhost/app/students'
117        self.manage_container_path = self.container_path + '/@@manage'
118        self.add_student_path = self.container_path + '/addstudent'
119        self.student_path = self.container_path + '/' + self.student_id
120        self.manage_student_path = self.student_path + '/manage_base'
121        self.trigtrans_path = self.student_path + '/trigtrans'
122        self.clearance_path = self.student_path + '/view_clearance'
123        self.personal_path = self.student_path + '/view_personal'
124        self.edit_clearance_path = self.student_path + '/cedit'
125        self.manage_clearance_path = self.student_path + '/manage_clearance'
126        self.edit_personal_path = self.student_path + '/edit_personal'
127        self.manage_personal_path = self.student_path + '/manage_personal'
128        self.studycourse_path = self.student_path + '/studycourse'
129        self.payments_path = self.student_path + '/payments'
130        self.acco_path = self.student_path + '/accommodation'
131        self.history_path = self.student_path + '/history'
132
133        # Create 5 access codes with prefix'PWD'
134        pin_container = self.app['accesscodes']
135        pin_container.createBatch(
136            datetime.utcnow(), 'some_userid', 'PWD', 9.99, 5)
137        pins = pin_container['PWD-1'].values()
138        self.pwdpins = [x.representation for x in pins]
139        self.existing_pwdpin = self.pwdpins[0]
140        parts = self.existing_pwdpin.split('-')[1:]
141        self.existing_pwdseries, self.existing_pwdnumber = parts
142        # Create 5 access codes with prefix 'CLR'
143        pin_container.createBatch(
144            datetime.now(), 'some_userid', 'CLR', 9.99, 5)
145        pins = pin_container['CLR-1'].values()
146        pins[0].owner = u'Hans Wurst'
147        self.existing_clrac = pins[0]
148        self.existing_clrpin = pins[0].representation
149        parts = self.existing_clrpin.split('-')[1:]
150        self.existing_clrseries, self.existing_clrnumber = parts
151        # Create 2 access codes with prefix 'HOS'
152        pin_container.createBatch(
153            datetime.now(), 'some_userid', 'HOS', 9.99, 2)
154        pins = pin_container['HOS-1'].values()
155        self.existing_hosac = pins[0]
156        self.existing_hospin = pins[0].representation
157        parts = self.existing_hospin.split('-')[1:]
158        self.existing_hosseries, self.existing_hosnumber = parts
159
160        # Populate university
161        self.certificate = createObject('waeup.Certificate')
162        self.certificate.code = u'CERT1'
163        self.certificate.application_category = 'basic'
164        self.certificate.study_mode = 'ug_ft'
165        self.certificate.start_level = 100
166        self.certificate.end_level = 500
167        self.certificate.school_fee_1 = 40000.0
168        self.certificate.school_fee_2 = 20000.0
169        self.app['faculties']['fac1'] = Faculty(code=u'fac1')
170        self.app['faculties']['fac1']['dep1'] = Department(code=u'dep1')
171        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
172            self.certificate)
173        self.course = createObject('waeup.Course')
174        self.course.code = 'COURSE1'
175        self.course.semester = 1
176        self.course.credits = 10
177        self.course.passmark = 40
178        self.app['faculties']['fac1']['dep1'].courses.addCourse(
179            self.course)
180        self.app['faculties']['fac1']['dep1'].certificates['CERT1'].addCertCourse(
181            self.course, level=100)
182
183        # Configure university and hostels
184        self.app['hostels'].accommodation_states = ['admitted']
185        self.app['hostels'].accommodation_session = 2004
186        delta = timedelta(days=10)
187        self.app['hostels'].startdate = datetime.now(pytz.utc) - delta
188        self.app['hostels'].enddate = datetime.now(pytz.utc) + delta
189        self.app['configuration'].carry_over = True
190        configuration = createObject('waeup.SessionConfiguration')
191        configuration.academic_session = 2004
192        configuration.clearance_fee = 3456.0
193        configuration.transcript_fee = 4567.0
194        configuration.booking_fee = 123.4
195        configuration.maint_fee = 987.0
196        self.app['configuration'].addSessionConfiguration(configuration)
197
198        # Create a hostel with two beds
199        hostel = Hostel()
200        hostel.hostel_id = u'hall-1'
201        hostel.hostel_name = u'Hall 1'
202        hostel.maint_fee = 876.0
203        self.app['hostels'].addHostel(hostel)
204        bed = Bed()
205        bed.bed_id = u'hall-1_A_101_A'
206        bed.bed_number = 1
207        bed.owner = NOT_OCCUPIED
208        bed.bed_type = u'regular_male_fr'
209        self.app['hostels'][hostel.hostel_id].addBed(bed)
210        bed = Bed()
211        bed.bed_id = u'hall-1_A_101_B'
212        bed.bed_number = 2
213        bed.owner = NOT_OCCUPIED
214        bed.bed_type = u'regular_female_fr'
215        self.app['hostels'][hostel.hostel_id].addBed(bed)
216
217        # Set study course attributes of test student
218        self.student['studycourse'].certificate = self.certificate
219        self.student['studycourse'].current_session = 2004
220        self.student['studycourse'].entry_session = 2004
221        self.student['studycourse'].current_verdict = 'A'
222        self.student['studycourse'].current_level = 100
223        # Update the catalog
224        notify(grok.ObjectModifiedEvent(self.student))
225
226        # Put the prepopulated site into test ZODB and prepare test
227        # browser
228        self.browser = Browser()
229        self.browser.handleErrors = False
230
231    def tearDown(self):
232        super(StudentsFullSetup, self).tearDown()
233        clearSite()
234        shutil.rmtree(self.dc_root)
235
236
237
238class StudentsContainerUITests(StudentsFullSetup):
239    # Tests for StudentsContainer class views and pages
240
241    layer = FunctionalLayer
242
243    def test_anonymous_access(self):
244        # Anonymous users can't access students containers
245        self.assertRaises(
246            Unauthorized, self.browser.open, self.container_path)
247        self.assertRaises(
248            Unauthorized, self.browser.open, self.manage_container_path)
249        return
250
251    def test_manage_access(self):
252        # Managers can access the view page of students
253        # containers and can perform actions
254        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
255        self.browser.open(self.container_path)
256        self.assertEqual(self.browser.headers['Status'], '200 Ok')
257        self.assertEqual(self.browser.url, self.container_path)
258        self.browser.getLink("Manage student section").click()
259        self.assertEqual(self.browser.headers['Status'], '200 Ok')
260        self.assertEqual(self.browser.url, self.manage_container_path)
261        return
262
263    def test_add_search_delete_students(self):
264        # Managers can add search and remove students
265        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
266        self.browser.open(self.manage_container_path)
267        self.browser.getLink("Add student").click()
268        self.assertEqual(self.browser.headers['Status'], '200 Ok')
269        self.assertEqual(self.browser.url, self.add_student_path)
270        self.browser.getControl(name="form.firstname").value = 'Bob'
271        self.browser.getControl(name="form.lastname").value = 'Tester'
272        self.browser.getControl(name="form.reg_number").value = '123'
273        self.browser.getControl("Create student record").click()
274        self.assertTrue('Registration number exists already'
275            in self.browser.contents)
276        self.browser.getControl(name="form.reg_number").value = '1234'
277        self.browser.getControl("Create student record").click()
278        self.assertTrue('Student record created' in self.browser.contents)
279
280        # Registration and matric numbers must be unique
281        self.browser.getLink("Manage").click()
282        self.browser.getControl(name="form.reg_number").value = '123'
283        self.browser.getControl("Save").click()
284        self.assertMatches('...Registration number exists...',
285                           self.browser.contents)
286        self.browser.getControl(name="form.reg_number").value = '789'
287        self.browser.getControl(name="form.matric_number").value = '234'
288        self.browser.getControl("Save").click()
289        self.assertMatches('...Matriculation number exists...',
290                           self.browser.contents)
291
292        # We can find a student with a certain student_id
293        self.browser.open(self.container_path)
294        self.browser.getControl("Find student(s)").click()
295        self.assertTrue('Empty search string' in self.browser.contents)
296        self.browser.getControl(name="searchtype").value = ['student_id']
297        self.browser.getControl(name="searchterm").value = self.student_id
298        self.browser.getControl("Find student(s)").click()
299        self.assertTrue('Anna Tester' in self.browser.contents)
300
301        # We can find a student in a certain session
302        self.browser.open(self.container_path)
303        self.browser.getControl(name="searchtype").value = ['current_session']
304        self.browser.getControl(name="searchterm").value = '2004'
305        self.browser.getControl("Find student(s)").click()
306        self.assertTrue('Anna Tester' in self.browser.contents)
307        # Session fileds require integer values
308        self.browser.open(self.container_path)
309        self.browser.getControl(name="searchtype").value = ['current_session']
310        self.browser.getControl(name="searchterm").value = '2004/2005'
311        self.browser.getControl("Find student(s)").click()
312        self.assertTrue('Only year dates allowed' in self.browser.contents)
313        self.browser.open(self.manage_container_path)
314        self.browser.getControl(name="searchtype").value = ['current_session']
315        self.browser.getControl(name="searchterm").value = '2004/2005'
316        self.browser.getControl("Find student(s)").click()
317        self.assertTrue('Only year dates allowed' in self.browser.contents)
318
319        # We can find a student in a certain study_mode
320        self.browser.open(self.container_path)
321        self.browser.getControl(name="searchtype").value = ['current_mode']
322        self.browser.getControl(name="searchterm").value = 'ug_ft'
323        self.browser.getControl("Find student(s)").click()
324        self.assertTrue('Anna Tester' in self.browser.contents)
325
326        # We can find a student in a certain department
327        self.browser.open(self.container_path)
328        self.browser.getControl(name="searchtype").value = ['depcode']
329        self.browser.getControl(name="searchterm").value = 'dep1'
330        self.browser.getControl("Find student(s)").click()
331        self.assertTrue('Anna Tester' in self.browser.contents)
332
333        # We can find a student by searching for all kind of name parts
334        self.browser.open(self.manage_container_path)
335        self.browser.getControl("Find student(s)").click()
336        self.assertTrue('Empty search string' in self.browser.contents)
337        self.browser.getControl(name="searchtype").value = ['fullname']
338        self.browser.getControl(name="searchterm").value = 'Anna Tester'
339        self.browser.getControl("Find student(s)").click()
340        self.assertTrue('Anna Tester' in self.browser.contents)
341        self.browser.open(self.manage_container_path)
342        self.browser.getControl(name="searchtype").value = ['fullname']
343        self.browser.getControl(name="searchterm").value = 'Anna'
344        self.browser.getControl("Find student(s)").click()
345        self.assertTrue('Anna Tester' in self.browser.contents)
346        self.browser.open(self.manage_container_path)
347        self.browser.getControl(name="searchtype").value = ['fullname']
348        self.browser.getControl(name="searchterm").value = 'Tester'
349        self.browser.getControl("Find student(s)").click()
350        self.assertTrue('Anna Tester' in self.browser.contents)
351        self.browser.open(self.manage_container_path)
352        self.browser.getControl(name="searchtype").value = ['fullname']
353        self.browser.getControl(name="searchterm").value = 'An'
354        self.browser.getControl("Find student(s)").click()
355        self.assertFalse('Anna Tester' in self.browser.contents)
356        self.browser.open(self.manage_container_path)
357        self.browser.getControl(name="searchtype").value = ['fullname']
358        self.browser.getControl(name="searchterm").value = 'An*'
359        self.browser.getControl("Find student(s)").click()
360        self.assertTrue('Anna Tester' in self.browser.contents)
361        self.browser.open(self.manage_container_path)
362        self.browser.getControl(name="searchtype").value = ['fullname']
363        self.browser.getControl(name="searchterm").value = 'tester'
364        self.browser.getControl("Find student(s)").click()
365        self.assertTrue('Anna Tester' in self.browser.contents)
366        self.browser.open(self.manage_container_path)
367        self.browser.getControl(name="searchtype").value = ['fullname']
368        self.browser.getControl(name="searchterm").value = 'Tester Ana'
369        self.browser.getControl("Find student(s)").click()
370        self.assertFalse('Anna Tester' in self.browser.contents)
371        self.browser.open(self.manage_container_path)
372        self.browser.getControl(name="searchtype").value = ['fullname']
373        self.browser.getControl(name="searchterm").value = 'Tester Anna'
374        self.browser.getControl("Find student(s)").click()
375        self.assertTrue('Anna Tester' in self.browser.contents)
376        # The old searchterm will be used again
377        self.browser.getControl("Find student(s)").click()
378        self.assertTrue('Anna Tester' in self.browser.contents)
379
380        # We can find suspended students
381        self.student.suspended = True
382        notify(grok.ObjectModifiedEvent(self.student))
383        self.browser.open(self.manage_container_path)
384        self.browser.getControl(name="searchtype").value = ['suspended']
385        self.browser.getControl("Find student(s)").click()
386        self.assertTrue('Anna Tester' in self.browser.contents)
387        self.browser.open(self.container_path)
388        self.browser.getControl(name="searchtype").value = ['suspended']
389        self.browser.getControl("Find student(s)").click()
390        self.assertTrue('Anna Tester' in self.browser.contents)
391
392        # The catalog is informed when studycourse objects have been
393        # edited
394        self.browser.open(self.studycourse_path + '/manage')
395        self.browser.getControl(name="form.current_session").value = ['2010']
396        self.browser.getControl(name="form.entry_session").value = ['2010']
397        self.browser.getControl(name="form.entry_mode").value = ['ug_ft']
398        self.browser.getControl("Save").click()
399
400        # We can find the student in the new session
401        self.browser.open(self.manage_container_path)
402        self.browser.getControl(name="searchtype").value = ['current_session']
403        self.browser.getControl(name="searchterm").value = '2010'
404        self.browser.getControl("Find student(s)").click()
405        self.assertTrue('Anna Tester' in self.browser.contents)
406
407        ctrl = self.browser.getControl(name='entries')
408        ctrl.getControl(value=self.student_id).selected = True
409        self.browser.getControl("Remove selected", index=0).click()
410        self.assertTrue('Successfully removed' in self.browser.contents)
411        self.browser.getControl(name="searchtype").value = ['student_id']
412        self.browser.getControl(name="searchterm").value = self.student_id
413        self.browser.getControl("Find student(s)").click()
414        self.assertTrue('No student found' in self.browser.contents)
415
416        self.browser.open(self.container_path)
417        self.browser.getControl(name="searchtype").value = ['student_id']
418        self.browser.getControl(name="searchterm").value = self.student_id
419        self.browser.getControl("Find student(s)").click()
420        self.assertTrue('No student found' in self.browser.contents)
421        return
422
423class OfficerUITests(StudentsFullSetup):
424    # Tests for Student class views and pages
425
426    def test_student_properties(self):
427        self.student['studycourse'].current_level = 100
428        self.assertEqual(self.student.current_level, 100)
429        self.student['studycourse'].current_session = 2011
430        self.assertEqual(self.student.current_session, 2011)
431        self.student['studycourse'].current_verdict = 'A'
432        self.assertEqual(self.student.current_verdict, 'A')
433        return
434
435    def test_studylevelmanagepage(self):
436        studylevel = StudentStudyLevel()
437        studylevel.level = 100
438        cert = self.app['faculties']['fac1']['dep1'].certificates['CERT1']
439        self.student['studycourse'].addStudentStudyLevel(
440            cert,studylevel)
441        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
442        self.browser.open(self.studycourse_path + '/100/manage')
443        self.assertEqual(self.browser.url, self.studycourse_path + '/100/manage')
444        self.assertEqual(self.browser.headers['Status'], '200 Ok')
445
446    def test_basic_auth(self):
447        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
448        self.browser.open('http://localhost/app')
449        self.browser.getLink("Logout").click()
450        self.assertTrue('You have been logged out' in self.browser.contents)
451        # But we are still logged in since we've used basic authentication here.
452        # Wikipedia says: Existing browsers retain authentication information
453        # until the tab or browser is closed or the user clears the history.
454        # HTTP does not provide a method for a server to direct clients to
455        # discard these cached credentials. This means that there is no
456        # effective way for a server to "log out" the user without closing
457        # the browser. This is a significant defect that requires browser
458        # manufacturers to support a "logout" user interface element ...
459        self.assertTrue('Manager' in self.browser.contents)
460
461    def test_basic_auth_base64(self):
462        auth_token = base64.b64encode('mgr:mgrpw')
463        self.browser.addHeader('Authorization', 'Basic %s' % auth_token)
464        self.browser.open(self.manage_container_path)
465        self.assertEqual(self.browser.headers['Status'], '200 Ok')
466
467    def test_manage_access(self):
468        # Managers can access the pages of students
469        # and can perform actions
470        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
471        self.browser.open(self.student_path)
472        self.assertEqual(self.browser.headers['Status'], '200 Ok')
473        self.assertEqual(self.browser.url, self.student_path)
474        self.browser.getLink("Trigger").click()
475        self.assertEqual(self.browser.headers['Status'], '200 Ok')
476        # Managers can trigger transitions
477        self.browser.getControl(name="transition").value = ['admit']
478        self.browser.getControl("Save").click()
479        # Managers can edit base
480        self.browser.open(self.student_path)
481        self.browser.getLink("Manage").click()
482        self.assertEqual(self.browser.url, self.manage_student_path)
483        self.assertEqual(self.browser.headers['Status'], '200 Ok')
484        self.browser.getControl(name="form.firstname").value = 'John'
485        self.browser.getControl(name="form.lastname").value = 'Tester'
486        self.browser.getControl(name="form.reg_number").value = '345'
487        self.browser.getControl(name="password").value = 'secret'
488        self.browser.getControl(name="control_password").value = 'secret'
489        self.browser.getControl("Save").click()
490        self.assertMatches('...Form has been saved...',
491                           self.browser.contents)
492        self.browser.open(self.student_path)
493        self.browser.getLink("Clearance Data").click()
494        self.assertEqual(self.browser.headers['Status'], '200 Ok')
495        self.assertEqual(self.browser.url, self.clearance_path)
496        self.browser.getLink("Manage").click()
497        self.assertEqual(self.browser.headers['Status'], '200 Ok')
498        self.assertEqual(self.browser.url, self.manage_clearance_path)
499        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
500        self.browser.getControl("Save").click()
501        self.assertMatches('...Form has been saved...',
502                           self.browser.contents)
503
504        self.browser.open(self.student_path)
505        self.browser.getLink("Personal Data").click()
506        self.assertEqual(self.browser.headers['Status'], '200 Ok')
507        self.assertEqual(self.browser.url, self.personal_path)
508        self.browser.getLink("Manage").click()
509        self.assertEqual(self.browser.headers['Status'], '200 Ok')
510        self.assertEqual(self.browser.url, self.manage_personal_path)
511        self.browser.open(self.personal_path)
512        self.assertTrue('Updated' in self.browser.contents)
513        self.browser.getLink("Edit").click()
514        self.assertEqual(self.browser.headers['Status'], '200 Ok')
515        self.assertEqual(self.browser.url, self.edit_personal_path)
516        self.browser.getControl("Save").click()
517        # perm_address is required in IStudentPersonalEdit
518        self.assertMatches('...Required input is missing...',
519                           self.browser.contents)
520        self.browser.getControl(name="form.perm_address").value = 'My address!'
521        self.browser.getControl("Save").click()
522        self.assertMatches('...Form has been saved...',
523                           self.browser.contents)
524
525        # Managers can browse all subobjects
526        self.browser.open(self.student_path)
527        self.browser.getLink("Payments").click()
528        self.assertEqual(self.browser.headers['Status'], '200 Ok')
529        self.assertEqual(self.browser.url, self.payments_path)
530        self.browser.open(self.student_path)
531        self.browser.getLink("Accommodation").click()
532        self.assertEqual(self.browser.headers['Status'], '200 Ok')
533        self.assertEqual(self.browser.url, self.acco_path)
534        self.browser.open(self.student_path)
535        self.browser.getLink("History").click()
536        self.assertEqual(self.browser.headers['Status'], '200 Ok')
537        self.assertEqual(self.browser.url, self.history_path)
538        self.assertMatches('...Admitted by Manager...',
539                           self.browser.contents)
540        # Only the Application Slip does not exist
541        self.assertFalse('Application Slip' in self.browser.contents)
542        return
543
544    def test_manage_contact_student(self):
545        # Managers can contact student
546        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
547        self.student.email = None
548        self.browser.open(self.student_path)
549        self.browser.getLink("Send email").click()
550        self.browser.getControl(name="form.subject").value = 'Important subject'
551        self.browser.getControl(name="form.body").value = 'Hello!'
552        self.browser.getControl("Send message now").click()
553        self.assertTrue('An smtp server error occurred' in self.browser.contents)
554        self.student.email = 'xx@yy.zz'
555        self.browser.getControl("Send message now").click()
556        self.assertTrue('Your message has been sent' in self.browser.contents)
557        return
558
559    def test_manage_remove_department(self):
560        # Lazy student is studying CERT1
561        lazystudent = Student()
562        lazystudent.firstname = u'Lazy'
563        lazystudent.lastname = u'Student'
564        self.app['students'].addStudent(lazystudent)
565        student_id = lazystudent.student_id
566        student_path = self.container_path + '/' + student_id
567        lazystudent['studycourse'].certificate = self.certificate
568        notify(grok.ObjectModifiedEvent(lazystudent))
569        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
570        self.browser.open(student_path + '/studycourse')
571        self.assertTrue('CERT1' in self.browser.contents)
572        # After some years the department is removed
573        del self.app['faculties']['fac1']['dep1']
574        # So CERT1 does no longer exist and lazy student's
575        # certificate reference is removed too
576        self.browser.open(student_path + '/studycourse')
577        self.assertEqual(self.browser.headers['Status'], '200 Ok')
578        self.assertEqual(self.browser.url, student_path + '/studycourse')
579        self.assertFalse('CERT1' in self.browser.contents)
580        self.assertMatches('...<div>--</div>...',
581                           self.browser.contents)
582
583    def test_manage_upload_file(self):
584        # Managers can upload a file via the StudentClearanceManageFormPage
585        # The image is stored even if form has errors
586        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
587        self.browser.open(self.manage_clearance_path)
588        # No birth certificate has been uploaded yet
589        # Browsing the link shows a placerholder image
590        self.browser.open('birth_certificate')
591        self.assertEqual(
592            self.browser.headers['content-type'], 'image/jpeg')
593        self.assertEqual(len(self.browser.contents), PH_LEN)
594        # Create a pseudo image file and select it to be uploaded in form
595        # as birth certificate
596        self.browser.open(self.manage_clearance_path)
597        image = open(SAMPLE_IMAGE, 'rb')
598        ctrl = self.browser.getControl(name='birthcertificateupload')
599        file_ctrl = ctrl.mech_control
600        file_ctrl.add_file(image, filename='my_birth_certificate.jpg')
601        # The Save action does not upload files
602        self.browser.getControl("Save").click() # submit form
603        self.assertFalse(
604            '<a target="image" href="birth_certificate">'
605            in self.browser.contents)
606        # ... but the correct upload submit button does
607        image = open(SAMPLE_IMAGE)
608        ctrl = self.browser.getControl(name='birthcertificateupload')
609        file_ctrl = ctrl.mech_control
610        file_ctrl.add_file(image, filename='my_birth_certificate.jpg')
611        self.browser.getControl(
612            name='upload_birthcertificateupload').click()
613        # There is a correct <img> link included
614        self.assertTrue(
615            'href="http://localhost/app/students/K1000000/birth_certificate"'
616            in self.browser.contents)
617        # Browsing the link shows a real image
618        self.browser.open('birth_certificate')
619        self.assertEqual(
620            self.browser.headers['content-type'], 'image/jpeg')
621        self.assertEqual(len(self.browser.contents), 2787)
622        # We can't reupload a file. The existing file must be deleted first.
623        self.browser.open(self.manage_clearance_path)
624        self.assertFalse(
625            'upload_birthcertificateupload' in self.browser.contents)
626        # File must be deleted first
627        self.browser.getControl(name='delete_birthcertificateupload').click()
628        self.assertTrue(
629            'birth_certificate deleted' in self.browser.contents)
630        # Uploading a file which is bigger than 150k will raise an error
631        big_image = StringIO(open(SAMPLE_IMAGE, 'rb').read() * 75)
632        ctrl = self.browser.getControl(name='birthcertificateupload')
633        file_ctrl = ctrl.mech_control
634        file_ctrl.add_file(big_image, filename='my_birth_certificate.jpg')
635        self.browser.getControl(
636            name='upload_birthcertificateupload').click()
637        self.assertTrue(
638            'Uploaded file is too big' in self.browser.contents)
639        # we do not rely on filename extensions given by uploaders
640        image = open(SAMPLE_IMAGE, 'rb') # a jpg-file
641        ctrl = self.browser.getControl(name='birthcertificateupload')
642        file_ctrl = ctrl.mech_control
643        # tell uploaded file is bmp
644        file_ctrl.add_file(image, filename='my_birth_certificate.bmp')
645        self.browser.getControl(
646            name='upload_birthcertificateupload').click()
647        self.assertTrue(
648            # jpg file was recognized
649            'File birth_certificate.jpg uploaded.' in self.browser.contents)
650        # Delete file again
651        self.browser.getControl(name='delete_birthcertificateupload').click()
652        self.assertTrue(
653            'birth_certificate deleted' in self.browser.contents)
654        # File names must meet several conditions
655        bmp_image = open(SAMPLE_IMAGE_BMP, 'rb')
656        ctrl = self.browser.getControl(name='birthcertificateupload')
657        file_ctrl = ctrl.mech_control
658        file_ctrl.add_file(bmp_image, filename='my_birth_certificate.bmp')
659        self.browser.getControl(
660            name='upload_birthcertificateupload').click()
661        self.assertTrue('Only the following extensions are allowed'
662            in self.browser.contents)
663
664        # Managers can upload a file via the StudentBaseManageFormPage
665        self.browser.open(self.manage_student_path)
666        image = open(SAMPLE_IMAGE_BMP, 'rb')
667        ctrl = self.browser.getControl(name='passportuploadmanage')
668        file_ctrl = ctrl.mech_control
669        file_ctrl.add_file(image, filename='my_photo.bmp')
670        self.browser.getControl(
671            name='upload_passportuploadmanage').click()
672        self.assertTrue('jpg file format expected'
673            in self.browser.contents)
674        ctrl = self.browser.getControl(name='passportuploadmanage')
675        file_ctrl = ctrl.mech_control
676        image = open(SAMPLE_IMAGE, 'rb')
677        file_ctrl.add_file(image, filename='my_photo.jpg')
678        self.browser.getControl(
679            name='upload_passportuploadmanage').click()
680        self.assertTrue(
681            'src="http://localhost/app/students/K1000000/passport.jpg"'
682            in self.browser.contents)
683        # We remove the passport file again
684        self.browser.open(self.manage_student_path)
685        self.browser.getControl('Delete').click()
686        self.browser.open(self.student_path + '/clearance_slip.pdf')
687        self.assertEqual(self.browser.headers['Status'], '200 Ok')
688        self.assertEqual(self.browser.headers['Content-Type'],
689                         'application/pdf')
690        # We want to see the signature fields.
691        IWorkflowState(self.student).setState('cleared')
692        self.browser.open(self.student_path + '/clearance_slip.pdf')
693        self.assertEqual(self.browser.headers['Status'], '200 Ok')
694        self.assertEqual(self.browser.headers['Content-Type'],
695                         'application/pdf')
696        path = os.path.join(samples_dir(), 'clearance_slip.pdf')
697        open(path, 'wb').write(self.browser.contents)
698        print "Sample PDF clearance_slip.pdf written to %s" % path
699
700    def test_manage_course_lists(self):
701        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
702        self.browser.open(self.student_path)
703        self.browser.getLink("Study Course").click()
704        self.assertEqual(self.browser.headers['Status'], '200 Ok')
705        self.assertEqual(self.browser.url, self.studycourse_path)
706        self.assertTrue('Undergraduate Full-Time' in self.browser.contents)
707        self.browser.getLink("Manage").click()
708        self.assertTrue('Manage study course' in self.browser.contents)
709        # Before we can select a level, the certificate must
710        # be selected and saved
711        self.browser.getControl(name="form.certificate").value = ['CERT1']
712        self.browser.getControl(name="form.current_session").value = ['2004']
713        self.browser.getControl(name="form.current_verdict").value = ['A']
714        self.browser.getControl(name="form.entry_mode").value = ['ug_ft']
715        self.browser.getControl("Save").click()
716        # Now we can save also the current level which depends on start and end
717        # level of the certificate
718        self.browser.getControl(name="form.current_level").value = ['100']
719        self.browser.getControl("Save").click()
720        # Managers can add and remove any study level (course list)
721        self.browser.getControl(name="addlevel").value = ['100']
722        self.browser.getControl("Add study level").click()
723        self.assertMatches(
724            '...You must select a session...', self.browser.contents)
725        self.browser.getControl(name="addlevel").value = ['100']
726        self.browser.getControl(name="level_session").value = ['2004']
727        self.browser.getControl("Add study level").click()
728        self.assertMatches('...<span>100</span>...', self.browser.contents)
729        self.assertEqual(self.student['studycourse']['100'].level, 100)
730        self.assertEqual(self.student['studycourse']['100'].level_session, 2004)
731        self.browser.getControl(name="addlevel").value = ['100']
732        self.browser.getControl(name="level_session").value = ['2004']
733        self.browser.getControl("Add study level").click()
734        self.assertMatches('...This level exists...', self.browser.contents)
735        self.browser.getControl("Remove selected").click()
736        self.assertMatches(
737            '...No study level selected...', self.browser.contents)
738        self.browser.getControl(name="val_id").value = ['100']
739        self.browser.getControl(name="level_session").value = ['2004']
740        self.browser.getControl("Remove selected").click()
741        self.assertMatches('...Successfully removed...', self.browser.contents)
742        # Removing levels is properly logged
743        logfile = os.path.join(
744            self.app['datacenter'].storage, 'logs', 'students.log')
745        logcontent = open(logfile).read()
746        self.assertTrue('zope.mgr - students.browser.StudyCourseManageFormPage '
747                        '- K1000000 - removed: 100' in logcontent)
748        # Add level again
749        self.browser.getControl(name="addlevel").value = ['100']
750        self.browser.getControl(name="level_session").value = ['2004']
751        self.browser.getControl("Add study level").click()
752
753        # Managers can view and manage course lists
754        self.browser.getLink("100").click()
755        self.assertMatches(
756            '...: Study Level 100 (Year 1)...', self.browser.contents)
757        self.browser.getLink("Manage").click()
758        self.browser.getControl(name="form.level_session").value = ['2002']
759        self.browser.getControl("Save").click()
760        self.browser.getControl("Remove selected").click()
761        self.assertMatches('...No ticket selected...', self.browser.contents)
762        ctrl = self.browser.getControl(name='val_id')
763        ctrl.getControl(value='COURSE1').selected = True
764        self.browser.getControl("Remove selected", index=0).click()
765        self.assertTrue('Successfully removed' in self.browser.contents)
766        # Removing course tickets is properly logged
767        logfile = os.path.join(
768            self.app['datacenter'].storage, 'logs', 'students.log')
769        logcontent = open(logfile).read()
770        self.assertTrue('zope.mgr - students.browser.StudyLevelManageFormPage '
771        '- K1000000 - removed: COURSE1 at 100' in logcontent)
772        self.browser.getLink("here").click()
773        self.browser.getControl(name="form.course").value = ['COURSE1']
774        self.course.credits = 100
775        self.browser.getControl("Add course ticket").click()
776        self.assertMatches(
777            '...Total credits exceed 50...', self.browser.contents)
778        self.course.credits = 10
779        self.browser.getControl("Add course ticket").click()
780        self.assertTrue('Successfully added' in self.browser.contents)
781        # We can do the same by adding the course on the manage page directly
782        del self.student['studycourse']['100']['COURSE1']
783        self.browser.getControl(name="course").value = 'COURSE1'
784        self.browser.getControl("Add course ticket").click()
785        self.assertTrue('Successfully added' in self.browser.contents)
786        self.browser.getLink("here").click()
787        self.browser.getControl(name="form.course").value = ['COURSE1']
788        self.browser.getControl("Add course ticket").click()
789        self.assertTrue('The ticket exists' in self.browser.contents)
790        self.browser.getControl("Cancel").click()
791        self.browser.getLink("COURSE1").click()
792        self.browser.getLink("Manage").click()
793        self.browser.getControl("Save").click()
794        self.assertTrue('Form has been saved' in self.browser.contents)
795        # Grade and weight have been determined
796        self.browser.open(self.studycourse_path + '/100/COURSE1')
797        self.assertFalse('Grade' in self.browser.contents)
798        self.assertFalse('Weight' in self.browser.contents)
799        self.student['studycourse']['100']['COURSE1'].score = 55
800        self.browser.open(self.studycourse_path + '/100/COURSE1')
801        self.assertTrue('Grade' in self.browser.contents)
802        self.assertTrue('Weight' in self.browser.contents)
803        self.assertEqual(self.student['studycourse']['100']['COURSE1'].grade, 'C')
804        self.assertEqual(self.student['studycourse']['100']['COURSE1'].weight, 3)
805        # We add another ticket to check if GPA will be correctly calculated
806        # (and rounded)
807        courseticket = createObject('waeup.CourseTicket')
808        courseticket.code = 'ANYCODE'
809        courseticket.title = u'Any TITLE'
810        courseticket.credits = 13
811        courseticket.score = 66
812        courseticket.semester = 1
813        courseticket.dcode = u'ANYDCODE'
814        courseticket.fcode = u'ANYFCODE'
815        self.student['studycourse']['100']['COURSE2'] = courseticket
816        self.browser.open(self.student_path + '/studycourse/100')
817        # total credits
818        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[1], 23)
819        # weigheted credits = 3 * 10 + 4 * 13
820        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[2], 82.0)
821        # sgpa = 82 / 23
822        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[0], 3.565)
823        # Carry-over courses will be collected when next level is created
824        self.browser.open(self.student_path + '/studycourse/manage')
825        # Add next level
826        self.student['studycourse']['100']['COURSE1'].score = 10
827        self.browser.getControl(name="addlevel").value = ['200']
828        self.browser.getControl(name="level_session").value = ['2005']
829        self.browser.getControl("Add study level").click()
830        self.browser.getLink("200").click()
831        self.assertMatches(
832            '...: Study Level 200 (Year 2)...', self.browser.contents)
833        # Since COURSE1 has score 10 it becomes a carry-over course
834        # in level 200
835        self.assertEqual(
836            sorted(self.student['studycourse']['200'].keys()), [u'COURSE1'])
837        self.assertTrue(
838            self.student['studycourse']['200']['COURSE1'].carry_over)
839        # Passed and failed courses have been counted
840        self.assertEqual(
841            self.student['studycourse']['100'].passed_params,
842            (1, 1, 13, 10, ['COURSE1']))
843        self.assertEqual(
844            self.student['studycourse']['200'].passed_params,
845            (0, 0, 0, 0, []))
846        # And also cumulative params can be calculated. Meanwhile we have the
847        # following courses: COURSE1 and COURSE2 in level 100 and
848        # COURSE1 as carry-over course in level 200.
849        self.assertEqual(
850            self.student['studycourse']['100'].cumulative_params,
851            (2.261, 23, 52.0, 23, 13))
852        # COURSE1 in level 200 is not taken into consideration
853        # when calculating the gpa.
854        self.assertEqual(
855            self.student['studycourse']['200'].cumulative_params,
856            (2.261, 23, 52.0, 33, 13))
857        return
858
859    def test_gpa_calculation_with_carryover(self):
860        studylevel = createObject(u'waeup.StudentStudyLevel')
861        studylevel.level = 100
862        studylevel.level_session = 2005
863        self.student['studycourse'].entry_mode = 'ug_ft'
864        self.student['studycourse'].addStudentStudyLevel(
865            self.certificate, studylevel)
866        # First course has been added automatically.
867        # Set score above passmark.
868        studylevel['COURSE1'].score = studylevel['COURSE1'].passmark + 1
869        # GPA is 1.
870        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[0], 1.0)
871        # Set score below passmark.
872        studylevel['COURSE1'].score = studylevel['COURSE1'].passmark - 1
873        # GPA is still 0.
874        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[0], 0.0)
875        studylevel2 = createObject(u'waeup.StudentStudyLevel')
876        studylevel2.level = 200
877        studylevel2.level_session = 2006
878        self.student['studycourse'].addStudentStudyLevel(
879            self.certificate, studylevel2)
880        # Carry-over course has been autonatically added.
881        studylevel2['COURSE1'].score = 66
882        # The score of the carry-over course is now used for calculation of the
883        # GPA at level 100 ...
884        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[0], 4.0)
885        # ... but not at level 200
886        self.assertEqual(self.student['studycourse']['200'].gpa_params_rectified[0], 0.0)
887        return
888
889    def test_manage_payments(self):
890        # Managers can add online school fee payment tickets
891        # if certain requirements are met
892        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
893        self.browser.open(self.payments_path)
894        IWorkflowState(self.student).setState('cleared')
895        self.browser.getLink("Add current session payment ticket").click()
896        self.browser.getControl(name="form.p_category").value = ['schoolfee']
897        self.browser.getControl("Create ticket").click()
898        self.assertMatches('...ticket created...',
899                           self.browser.contents)
900        ctrl = self.browser.getControl(name='val_id')
901        value = ctrl.options[0]
902        self.browser.getLink(value).click()
903        self.assertMatches('...Amount Authorized...',
904                           self.browser.contents)
905        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
906        payment_url = self.browser.url
907        logfile = os.path.join(
908            self.app['datacenter'].storage, 'logs', 'students.log')
909        logcontent = open(logfile).read()
910        self.assertTrue(
911            ' zope.mgr - students.browser.OnlinePaymentAddFormPage - '
912            'K1000000 - added: %s' % value
913            in logcontent)
914        # The pdf payment slip can't yet be opened
915        #self.browser.open(payment_url + '/payment_slip.pdf')
916        #self.assertMatches('...Ticket not yet paid...',
917        #                   self.browser.contents)
918
919        # The same payment (with same p_item, p_session and p_category)
920        # can be initialized a second time if the former ticket is not yet paid.
921        self.browser.open(self.payments_path)
922        self.browser.getLink("Add current session payment ticket").click()
923        self.browser.getControl(name="form.p_category").value = ['schoolfee']
924        self.browser.getControl("Create ticket").click()
925        self.assertMatches('...Payment ticket created...',
926                           self.browser.contents)
927
928        # The ticket can be found in the payments_catalog
929        cat = queryUtility(ICatalog, name='payments_catalog')
930        results = list(cat.searchResults(p_state=('unpaid', 'unpaid')))
931        self.assertTrue(len(results), 1)
932        self.assertTrue(results[0] is self.student['payments'][value])
933
934        # Managers can approve the payment
935        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
936        self.browser.open(payment_url)
937        self.browser.getLink("Approve payment").click()
938        self.assertMatches('...Payment approved...',
939                          self.browser.contents)
940        # Approval is logged in students.log ...
941        logcontent = open(logfile).read()
942        self.assertTrue(
943            'zope.mgr - students.browser.OnlinePaymentApprovePage '
944            '- K1000000 - schoolfee payment approved'
945            in logcontent)
946        # ... and in payments.log
947        logfile = os.path.join(
948            self.app['datacenter'].storage, 'logs', 'payments.log')
949        logcontent = open(logfile).read()
950        self.assertTrue(
951            '"zope.mgr",K1000000,%s,schoolfee,40000.0,AP,,,,,,\n' % value
952            in logcontent)
953
954        # The authorized amount has been stored in the access code
955        self.assertEqual(
956            self.app['accesscodes']['SFE-0'].values()[0].cost,40000.0)
957
958        # The catalog has been updated
959        results = list(cat.searchResults(p_state=('unpaid', 'unpaid')))
960        self.assertTrue(len(results), 0)
961        results = list(cat.searchResults(p_state=('paid', 'paid')))
962        self.assertTrue(len(results), 1)
963        self.assertTrue(results[0] is self.student['payments'][value])
964
965        # Payments can't be approved twice
966        self.browser.open(payment_url + '/approve')
967        self.assertMatches('...This ticket has already been paid...',
968                          self.browser.contents)
969
970        # Now the first ticket is paid and no more ticket of same type
971        # (with same p_item, p_session and p_category) can be added
972        self.browser.open(self.payments_path)
973        self.browser.getLink("Add current session payment ticket").click()
974        self.browser.getControl(name="form.p_category").value = ['schoolfee']
975        self.browser.getControl("Create ticket").click()
976        self.assertMatches(
977            '...This type of payment has already been made...',
978            self.browser.contents)
979
980        # Managers can open the pdf payment slip
981        self.browser.open(payment_url)
982        self.browser.getLink("Download payment slip").click()
983        self.assertEqual(self.browser.headers['Status'], '200 Ok')
984        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
985
986        # Managers can remove online school fee payment tickets
987        self.browser.open(self.payments_path)
988        self.browser.getControl("Remove selected").click()
989        self.assertMatches('...No payment selected...', self.browser.contents)
990        ctrl = self.browser.getControl(name='val_id')
991        value = ctrl.options[0]
992        ctrl.getControl(value=value).selected = True
993        self.browser.getControl("Remove selected", index=0).click()
994        self.assertTrue('Successfully removed' in self.browser.contents)
995
996        # Managers can add online clearance payment tickets
997        self.browser.open(self.payments_path + '/addop')
998        self.browser.getControl(name="form.p_category").value = ['clearance']
999        self.browser.getControl("Create ticket").click()
1000        self.assertMatches('...ticket created...',
1001                           self.browser.contents)
1002
1003        # Managers can approve the payment
1004        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
1005        ctrl = self.browser.getControl(name='val_id')
1006        value = ctrl.options[1] # The clearance payment is the second in the table
1007        self.browser.getLink(value).click()
1008        self.browser.open(self.browser.url + '/approve')
1009        self.assertMatches('...Payment approved...',
1010                          self.browser.contents)
1011        expected = '''...
1012        <td>
1013          <span>Paid</span>
1014        </td>...'''
1015        self.assertMatches(expected,self.browser.contents)
1016        # The new CLR-0 pin has been created
1017        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
1018        pin = self.app['accesscodes']['CLR-0'].keys()[0]
1019        ac = self.app['accesscodes']['CLR-0'][pin]
1020        self.assertEqual(ac.owner, self.student_id)
1021        self.assertEqual(ac.cost, 3456.0)
1022
1023        # Managers can add online transcript payment tickets
1024        self.browser.open(self.payments_path + '/addop')
1025        self.browser.getControl(name="form.p_category").value = ['transcript']
1026        self.browser.getControl("Create ticket").click()
1027        self.assertMatches('...ticket created...',
1028                           self.browser.contents)
1029
1030        # Managers can approve the payment
1031        self.assertEqual(len(self.app['accesscodes']['TSC-0']),0)
1032        ctrl = self.browser.getControl(name='val_id')
1033        value = ctrl.options[2] # The clearance payment is the third in the table
1034        self.browser.getLink(value).click()
1035        self.browser.open(self.browser.url + '/approve')
1036        self.assertMatches('...Payment approved...',
1037                          self.browser.contents)
1038        expected = '''...
1039        <td>
1040          <span>Paid</span>
1041        </td>...'''
1042        self.assertMatches(expected,self.browser.contents)
1043        # The new CLR-0 pin has been created
1044        self.assertEqual(len(self.app['accesscodes']['TSC-0']),1)
1045        pin = self.app['accesscodes']['TSC-0'].keys()[0]
1046        ac = self.app['accesscodes']['TSC-0'][pin]
1047        self.assertEqual(ac.owner, self.student_id)
1048        self.assertEqual(ac.cost, 4567.0)
1049        return
1050
1051    def test_payment_disabled(self):
1052        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1053        self.browser.open(self.payments_path)
1054        IWorkflowState(self.student).setState('cleared')
1055        self.browser.getLink("Add current session payment ticket").click()
1056        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1057        self.browser.getControl("Create ticket").click()
1058        self.assertMatches('...ticket created...',
1059                           self.browser.contents)
1060        self.app['configuration']['2004'].payment_disabled = ['sf_all']
1061        self.browser.getLink("Add current session payment ticket").click()
1062        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1063        self.browser.getControl("Create ticket").click()
1064        self.assertMatches('...Payment temporarily disabled...',
1065                           self.browser.contents)
1066        return
1067
1068    def test_manage_balance_payments(self):
1069
1070        # Login
1071        #self.browser.open(self.login_path)
1072        #self.browser.getControl(name="form.login").value = self.student_id
1073        #self.browser.getControl(name="form.password").value = 'spwd'
1074        #self.browser.getControl("Login").click()
1075
1076        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1077        self.browser.open(self.payments_path)
1078
1079        # Managers can add previous school fee payment tickets in any state.
1080        IWorkflowState(self.student).setState('courses registered')
1081        self.browser.open(self.payments_path)
1082        self.browser.getLink("Add balance payment ticket").click()
1083
1084        # Previous session payment form is provided
1085        self.assertEqual(self.student.current_session, 2004)
1086        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1087        self.browser.getControl(name="form.balance_session").value = ['2004']
1088        self.browser.getControl(name="form.balance_level").value = ['300']
1089        self.browser.getControl(name="form.balance_amount").value = '-567.8'
1090        self.browser.getControl("Create ticket").click()
1091        self.assertMatches('...Amount must be greater than 0...',
1092                           self.browser.contents)
1093        self.browser.getControl(name="form.balance_amount").value = '0'
1094        self.browser.getControl("Create ticket").click()
1095        self.assertMatches('...Amount must be greater than 0...',
1096                           self.browser.contents)
1097        self.browser.getControl(name="form.balance_amount").value = '567.8'
1098        self.browser.getControl("Create ticket").click()
1099        self.assertMatches('...ticket created...',
1100                           self.browser.contents)
1101        ctrl = self.browser.getControl(name='val_id')
1102        value = ctrl.options[0]
1103        self.browser.getLink(value).click()
1104        self.assertMatches('...Amount Authorized...',
1105                           self.browser.contents)
1106        self.assertEqual(self.student['payments'][value].amount_auth, 567.8)
1107        # Payment attributes are properly set
1108        self.assertEqual(self.student['payments'][value].p_session, 2004)
1109        self.assertEqual(self.student['payments'][value].p_level, 300)
1110        self.assertEqual(self.student['payments'][value].p_item, u'Balance')
1111        self.assertEqual(self.student['payments'][value].p_category, 'schoolfee')
1112        # Adding payment tickets is logged.
1113        logfile = os.path.join(
1114            self.app['datacenter'].storage, 'logs', 'students.log')
1115        logcontent = open(logfile).read()
1116        self.assertTrue('zope.mgr - students.browser.BalancePaymentAddFormPage '
1117                        '- K1000000 - added: %s' % value in logcontent)
1118
1119    def test_manage_accommodation(self):
1120        logfile = os.path.join(
1121            self.app['datacenter'].storage, 'logs', 'students.log')
1122        # Managers can add online booking fee payment tickets and open the
1123        # callback view (see test_manage_payments)
1124        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1125        self.browser.open(self.payments_path)
1126        self.browser.getLink("Add current session payment ticket").click()
1127        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1128        # If student is not in accommodation session, payment cannot be processed
1129        self.app['hostels'].accommodation_session = 2011
1130        self.browser.getControl("Create ticket").click()
1131        self.assertMatches('...Your current session does not match...',
1132                           self.browser.contents)
1133        self.app['hostels'].accommodation_session = 2004
1134        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1135        self.browser.getControl("Create ticket").click()
1136        ctrl = self.browser.getControl(name='val_id')
1137        value = ctrl.options[0]
1138        self.browser.getLink(value).click()
1139        self.browser.open(self.browser.url + '/approve')
1140        # The new HOS-0 pin has been created
1141        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
1142        pin = self.app['accesscodes']['HOS-0'].keys()[0]
1143        ac = self.app['accesscodes']['HOS-0'][pin]
1144        self.assertEqual(ac.owner, self.student_id)
1145        parts = pin.split('-')[1:]
1146        sfeseries, sfenumber = parts
1147        # Managers can use HOS code and book a bed space with it
1148        self.browser.open(self.acco_path)
1149        self.browser.getLink("Book accommodation").click()
1150        self.assertMatches('...You are in the wrong...',
1151                           self.browser.contents)
1152        IWorkflowInfo(self.student).fireTransition('admit')
1153        # An existing HOS code can only be used if students
1154        # are in accommodation session
1155        self.student['studycourse'].current_session = 2003
1156        self.browser.getLink("Book accommodation").click()
1157        self.assertMatches('...Your current session does not match...',
1158                           self.browser.contents)
1159        self.student['studycourse'].current_session = 2004
1160        # All requirements are met and ticket can be created
1161        self.browser.getLink("Book accommodation").click()
1162        self.assertMatches('...Activation Code:...',
1163                           self.browser.contents)
1164        self.browser.getControl(name="ac_series").value = sfeseries
1165        self.browser.getControl(name="ac_number").value = sfenumber
1166        self.browser.getControl("Create bed ticket").click()
1167        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1168                           self.browser.contents)
1169        # Bed has been allocated
1170        bed1 = self.app['hostels']['hall-1']['hall-1_A_101_A']
1171        self.assertTrue(bed1.owner == self.student_id)
1172        # BedTicketAddPage is now blocked
1173        self.browser.getLink("Book accommodation").click()
1174        self.assertMatches('...You already booked a bed space...',
1175            self.browser.contents)
1176        # The bed ticket displays the data correctly
1177        self.browser.open(self.acco_path + '/2004')
1178        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1179                           self.browser.contents)
1180        self.assertMatches('...2004/2005...', self.browser.contents)
1181        self.assertMatches('...regular_male_fr...', self.browser.contents)
1182        self.assertMatches('...%s...' % pin, self.browser.contents)
1183        # Booking is properly logged
1184        logcontent = open(logfile).read()
1185        self.assertTrue('zope.mgr - students.browser.BedTicketAddPage '
1186            '- K1000000 - booked: hall-1_A_101_A' in logcontent)
1187        # Managers can relocate students if the student's bed_type has changed
1188        self.browser.getLink("Relocate student").click()
1189        self.assertMatches(
1190            "...Student can't be relocated...", self.browser.contents)
1191        self.student.sex = u'f'
1192        self.browser.getLink("Relocate student").click()
1193        self.assertMatches(
1194            "...Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
1195        self.assertTrue(bed1.owner == NOT_OCCUPIED)
1196        bed2 = self.app['hostels']['hall-1']['hall-1_A_101_B']
1197        self.assertTrue(bed2.owner == self.student_id)
1198        self.assertTrue(self.student['accommodation'][
1199            '2004'].bed_type == u'regular_female_fr')
1200        # Relocation is properly logged
1201        logcontent = open(logfile).read()
1202        self.assertTrue('zope.mgr - students.browser.BedTicketRelocationPage '
1203            '- K1000000 - relocated: hall-1_A_101_B' in logcontent)
1204        # The payment object still shows the original payment item
1205        payment_id = self.student['payments'].keys()[0]
1206        payment = self.student['payments'][payment_id]
1207        self.assertTrue(payment.p_item == u'regular_male_fr')
1208        # Managers can relocate students if the bed's bed_type has changed
1209        bed1.bed_type = u'regular_female_fr'
1210        bed2.bed_type = u'regular_male_fr'
1211        notify(grok.ObjectModifiedEvent(bed1))
1212        notify(grok.ObjectModifiedEvent(bed2))
1213        self.browser.getLink("Relocate student").click()
1214        self.assertMatches(
1215            "...Student relocated...", self.browser.contents)
1216        self.assertMatches(
1217            "... Hall 1, Block A, Room 101, Bed A...", self.browser.contents)
1218        self.assertMatches(bed1.owner, self.student_id)
1219        self.assertMatches(bed2.owner, NOT_OCCUPIED)
1220        # Managers can't relocate students if bed is reserved
1221        self.student.sex = u'm'
1222        bed1.bed_type = u'regular_female_reserved'
1223        notify(grok.ObjectModifiedEvent(bed1))
1224        self.browser.getLink("Relocate student").click()
1225        self.assertMatches(
1226            "...Students in reserved beds can't be relocated...",
1227            self.browser.contents)
1228        # Managers can relocate students if booking has been cancelled but
1229        # other bed space has been manually allocated after cancellation
1230        old_owner = bed1.releaseBed()
1231        self.assertMatches(old_owner, self.student_id)
1232        bed2.owner = self.student_id
1233        self.browser.open(self.acco_path + '/2004')
1234        self.assertMatches(
1235            "...booking cancelled...", self.browser.contents)
1236        self.browser.getLink("Relocate student").click()
1237        # We didn't informed the catalog therefore the new owner is not found
1238        self.assertMatches(
1239            "...There is no free bed in your category regular_male_fr...",
1240            self.browser.contents)
1241        # Now we fire the event properly
1242        notify(grok.ObjectModifiedEvent(bed2))
1243        self.browser.getLink("Relocate student").click()
1244        self.assertMatches(
1245            "...Student relocated...", self.browser.contents)
1246        self.assertMatches(
1247            "... Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
1248          # Managers can delete bed tickets
1249        self.browser.open(self.acco_path)
1250        ctrl = self.browser.getControl(name='val_id')
1251        value = ctrl.options[0]
1252        ctrl.getControl(value=value).selected = True
1253        self.browser.getControl("Remove selected", index=0).click()
1254        self.assertMatches('...Successfully removed...', self.browser.contents)
1255        # The bed has been properly released by the event handler
1256        self.assertMatches(bed1.owner, NOT_OCCUPIED)
1257        self.assertMatches(bed2.owner, NOT_OCCUPIED)
1258        return
1259
1260    def test_manage_workflow(self):
1261        # Managers can pass through the whole workflow
1262        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1263        student = self.app['students'][self.student_id]
1264        self.browser.open(self.trigtrans_path)
1265        self.assertTrue(student.clearance_locked)
1266        self.browser.getControl(name="transition").value = ['admit']
1267        self.browser.getControl("Save").click()
1268        self.assertTrue(student.clearance_locked)
1269        self.browser.getControl(name="transition").value = ['start_clearance']
1270        self.browser.getControl("Save").click()
1271        self.assertFalse(student.clearance_locked)
1272        self.browser.getControl(name="transition").value = ['request_clearance']
1273        self.browser.getControl("Save").click()
1274        self.assertTrue(student.clearance_locked)
1275        self.browser.getControl(name="transition").value = ['clear']
1276        self.browser.getControl("Save").click()
1277        # Managers approve payment, they do not pay
1278        self.assertFalse('pay_first_school_fee' in self.browser.contents)
1279        self.browser.getControl(
1280            name="transition").value = ['approve_first_school_fee']
1281        self.browser.getControl("Save").click()
1282        self.browser.getControl(name="transition").value = ['reset6']
1283        self.browser.getControl("Save").click()
1284        # In state returning the pay_school_fee transition triggers some
1285        # changes of attributes
1286        self.browser.getControl(name="transition").value = ['approve_school_fee']
1287        self.browser.getControl("Save").click()
1288        self.assertEqual(student['studycourse'].current_session, 2005) # +1
1289        self.assertEqual(student['studycourse'].current_level, 200) # +100
1290        self.assertEqual(student['studycourse'].current_verdict, '0') # 0 = Zero = not set
1291        self.assertEqual(student['studycourse'].previous_verdict, 'A')
1292        self.browser.getControl(name="transition").value = ['register_courses']
1293        self.browser.getControl("Save").click()
1294        self.browser.getControl(name="transition").value = ['validate_courses']
1295        self.browser.getControl("Save").click()
1296        self.browser.getControl(name="transition").value = ['return']
1297        self.browser.getControl("Save").click()
1298        return
1299
1300    def test_manage_pg_workflow(self):
1301        # Managers can pass through the whole workflow
1302        IWorkflowState(self.student).setState('school fee paid')
1303        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1304        student = self.app['students'][self.student_id]
1305        self.browser.open(self.trigtrans_path)
1306        self.assertTrue('<option value="reset6">' in self.browser.contents)
1307        self.assertTrue('<option value="register_courses">' in self.browser.contents)
1308        self.assertTrue('<option value="reset5">' in self.browser.contents)
1309        self.certificate.study_mode = 'pg_ft'
1310        self.browser.open(self.trigtrans_path)
1311        self.assertFalse('<option value="reset6">' in self.browser.contents)
1312        self.assertFalse('<option value="register_courses">' in self.browser.contents)
1313        self.assertTrue('<option value="reset5">' in self.browser.contents)
1314        return
1315
1316    def test_manage_import(self):
1317        # Managers can import student data files
1318        datacenter_path = 'http://localhost/app/datacenter'
1319        # Prepare a csv file for students
1320        open('students.csv', 'wb').write(
1321"""firstname,lastname,reg_number,date_of_birth,matric_number,email,phone,sex,password
1322Aaren,Pieri,1,1990-01-02,100000,aa@aa.ng,1234,m,mypwd1
1323Claus,Finau,2,1990-01-03,100001,aa@aa.ng,1234,m,mypwd1
1324Brit,Berson,3,1990-01-04,100001,aa@aa.ng,1234,m,mypwd1
1325""")
1326        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1327        self.browser.open(datacenter_path)
1328        self.browser.getLink('Upload data').click()
1329        filecontents = StringIO(open('students.csv', 'rb').read())
1330        filewidget = self.browser.getControl(name='uploadfile:file')
1331        filewidget.add_file(filecontents, 'text/plain', 'students.csv')
1332        self.browser.getControl(name='SUBMIT').click()
1333        self.browser.getLink('Process data').click()
1334        button = lookup_submit_value(
1335            'select', 'students_zope.mgr.csv', self.browser)
1336        button.click()
1337        importerselect = self.browser.getControl(name='importer')
1338        modeselect = self.browser.getControl(name='mode')
1339        importerselect.getControl('Student Processor').selected = True
1340        modeselect.getControl(value='create').selected = True
1341        self.browser.getControl('Proceed to step 3').click()
1342        self.assertTrue('Header fields OK' in self.browser.contents)
1343        self.browser.getControl('Perform import').click()
1344        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
1345        self.assertTrue('Successfully processed 2 rows' in self.browser.contents)
1346        self.assertTrue('Batch processing finished' in self.browser.contents)
1347        open('studycourses.csv', 'wb').write(
1348"""reg_number,matric_number,certificate,current_session,current_level
13491,,CERT1,2008,100
1350,100001,CERT1,2008,100
1351,100002,CERT1,2008,100
1352""")
1353        self.browser.open(datacenter_path)
1354        self.browser.getLink('Upload data').click()
1355        filecontents = StringIO(open('studycourses.csv', 'rb').read())
1356        filewidget = self.browser.getControl(name='uploadfile:file')
1357        filewidget.add_file(filecontents, 'text/plain', 'studycourses.csv')
1358        self.browser.getControl(name='SUBMIT').click()
1359        self.browser.getLink('Process data').click()
1360        button = lookup_submit_value(
1361            'select', 'studycourses_zope.mgr.csv', self.browser)
1362        button.click()
1363        importerselect = self.browser.getControl(name='importer')
1364        modeselect = self.browser.getControl(name='mode')
1365        importerselect.getControl(
1366            'StudentStudyCourse Processor (update only)').selected = True
1367        modeselect.getControl(value='create').selected = True
1368        self.browser.getControl('Proceed to step 3').click()
1369        self.assertTrue('Update mode only' in self.browser.contents)
1370        self.browser.getControl('Proceed to step 3').click()
1371        self.assertTrue('Header fields OK' in self.browser.contents)
1372        self.browser.getControl('Perform import').click()
1373        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
1374        self.assertTrue('Successfully processed 2 rows'
1375                        in self.browser.contents)
1376        # The students are properly indexed and we can
1377        # thus find a student in  the department
1378        self.browser.open(self.manage_container_path)
1379        self.browser.getControl(name="searchtype").value = ['depcode']
1380        self.browser.getControl(name="searchterm").value = 'dep1'
1381        self.browser.getControl("Find student(s)").click()
1382        self.assertTrue('Aaren Pieri' in self.browser.contents)
1383        # We can search for a new student by name ...
1384        self.browser.getControl(name="searchtype").value = ['fullname']
1385        self.browser.getControl(name="searchterm").value = 'Claus'
1386        self.browser.getControl("Find student(s)").click()
1387        self.assertTrue('Claus Finau' in self.browser.contents)
1388        # ... and check if the imported password has been properly set
1389        ctrl = self.browser.getControl(name='entries')
1390        value = ctrl.options[0]
1391        claus = self.app['students'][value]
1392        self.assertTrue(IUserAccount(claus).checkPassword('mypwd1'))
1393        return
1394
1395    def init_clearance_officer(self):
1396        # Create clearance officer
1397        self.app['users'].addUser('mrclear', 'mrclearsecret')
1398        self.app['users']['mrclear'].email = 'mrclear@foo.ng'
1399        self.app['users']['mrclear'].title = 'Carlo Pitter'
1400        # Clearance officers need not necessarily to get
1401        # the StudentsOfficer site role
1402        #prmglobal = IPrincipalRoleManager(self.app)
1403        #prmglobal.assignRoleToPrincipal('waeup.StudentsOfficer', 'mrclear')
1404        # Assign local ClearanceOfficer role
1405        self.department = self.app['faculties']['fac1']['dep1']
1406        prmlocal = IPrincipalRoleManager(self.department)
1407        prmlocal.assignRoleToPrincipal('waeup.local.ClearanceOfficer', 'mrclear')
1408        IWorkflowState(self.student).setState('clearance started')
1409        # Add another student for testing
1410        other_student = Student()
1411        other_student.firstname = u'Dep2'
1412        other_student.lastname = u'Student'
1413        self.app['students'].addStudent(other_student)
1414        self.other_student_path = (
1415            'http://localhost/app/students/%s' % other_student.student_id)
1416        # Login as clearance officer
1417        self.browser.open(self.login_path)
1418        self.browser.getControl(name="form.login").value = 'mrclear'
1419        self.browser.getControl(name="form.password").value = 'mrclearsecret'
1420        self.browser.getControl("Login").click()
1421
1422    def test_handle_clearance_by_co(self):
1423        self.init_clearance_officer()
1424        self.assertMatches('...You logged in...', self.browser.contents)
1425        # CO is landing on index page
1426        self.assertEqual(self.browser.url, 'http://localhost/app/index')
1427        # CO can see his roles
1428        self.browser.getLink("My Roles").click()
1429        self.assertMatches(
1430            '...<div>Academics Officer (view only)</div>...',
1431            self.browser.contents)
1432        #self.assertMatches(
1433        #    '...<div>Students Officer (view only)</div>...',
1434        #    self.browser.contents)
1435        # But not his local role ...
1436        self.assertFalse('Clearance Officer' in self.browser.contents)
1437        # ... because we forgot to notify the department that the local role
1438        # has changed
1439        notify(LocalRoleSetEvent(
1440            self.department, 'waeup.local.ClearanceOfficer', 'mrclear', granted=True))
1441        self.browser.open('http://localhost/app/users/mrclear/my_roles')
1442        self.assertTrue('Clearance Officer' in self.browser.contents)
1443        self.assertMatches(
1444            '...<a href="http://localhost/app/faculties/fac1/dep1">...',
1445            self.browser.contents)
1446        # CO can view the student ...
1447        self.browser.open(self.clearance_path)
1448        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1449        self.assertEqual(self.browser.url, self.clearance_path)
1450        # ... but not other students
1451        self.assertRaises(
1452            Unauthorized, self.browser.open, self.other_student_path)
1453        # Clearance is disabled for this session
1454        self.browser.open(self.clearance_path)
1455        self.assertFalse('Clear student' in self.browser.contents)
1456        self.browser.open(self.student_path + '/clear')
1457        self.assertTrue('Clearance is disabled for this session'
1458            in self.browser.contents)
1459        self.app['configuration']['2004'].clearance_enabled = True
1460        # Only in state clearance requested the CO does see the 'Clear' button
1461        self.browser.open(self.clearance_path)
1462        self.assertFalse('Clear student' in self.browser.contents)
1463        self.browser.open(self.student_path + '/clear')
1464        self.assertTrue('Student is in wrong state.'
1465            in self.browser.contents)
1466        IWorkflowInfo(self.student).fireTransition('request_clearance')
1467        self.browser.open(self.clearance_path)
1468        self.assertTrue('Clear student' in self.browser.contents)
1469        self.browser.getLink("Clear student").click()
1470        self.assertTrue('Student has been cleared' in self.browser.contents)
1471        self.assertTrue('cleared' in self.browser.contents)
1472        self.browser.open(self.history_path)
1473        self.assertTrue('Cleared by Carlo Pitter' in self.browser.contents)
1474        # Hide real name.
1475        self.app['users']['mrclear'].public_name = 'My Public Name'
1476        self.browser.open(self.clearance_path)
1477        self.browser.getLink("Reject clearance").click()
1478        self.assertEqual(
1479            self.browser.url, self.student_path + '/reject_clearance')
1480        # Type comment why
1481        self.browser.getControl(name="form.officer_comment").value = """Dear Student,
1482You did not fill properly.
1483"""
1484        self.browser.getControl("Save comment").click()
1485        self.assertTrue('Clearance has been annulled' in self.browser.contents)
1486        url = ('http://localhost/app/students/K1000000/'
1487              'contactstudent?body=Dear+Student%2C%0AYou+did+not+fill+properly.'
1488              '%0A&subject=Clearance+has+been+annulled.')
1489        # CO does now see the prefilled contact form and can send a message
1490        self.assertEqual(self.browser.url, url)
1491        self.assertTrue('clearance started' in self.browser.contents)
1492        self.assertTrue('name="form.subject" size="20" type="text" '
1493            'value="Clearance has been annulled."'
1494            in self.browser.contents)
1495        self.assertTrue('name="form.body" rows="10" >Dear Student,'
1496            in self.browser.contents)
1497        self.browser.getControl("Send message now").click()
1498        self.assertTrue('Your message has been sent' in self.browser.contents)
1499        # The comment has been stored ...
1500        self.assertEqual(self.student.officer_comment,
1501            u'Dear Student,\nYou did not fill properly.\n')
1502        # ... and logged
1503        logfile = os.path.join(
1504            self.app['datacenter'].storage, 'logs', 'students.log')
1505        logcontent = open(logfile).read()
1506        self.assertTrue(
1507            'INFO - mrclear - students.browser.StudentRejectClearancePage - '
1508            'K1000000 - comment: Dear Student,<br>You did not fill '
1509            'properly.<br>\n' in logcontent)
1510        self.browser.open(self.history_path)
1511        self.assertTrue("Reset to 'clearance started' by My Public Name" in
1512            self.browser.contents)
1513        IWorkflowInfo(self.student).fireTransition('request_clearance')
1514        self.browser.open(self.clearance_path)
1515        self.browser.getLink("Reject clearance").click()
1516        self.browser.getControl("Save comment").click()
1517        self.assertTrue('Clearance request has been rejected'
1518            in self.browser.contents)
1519        self.assertTrue('clearance started' in self.browser.contents)
1520        # The CO can't clear students if not in state
1521        # clearance requested
1522        self.browser.open(self.student_path + '/clear')
1523        self.assertTrue('Student is in wrong state'
1524            in self.browser.contents)
1525        # The CO can go to his department throug the my_roles page ...
1526        self.browser.open('http://localhost/app/users/mrclear/my_roles')
1527        self.browser.getLink("http://localhost/app/faculties/fac1/dep1").click()
1528        # ... and view the list of students
1529        self.browser.getLink("Show students").click()
1530        self.browser.getControl(name="session").value = ['2004']
1531        self.browser.getControl(name="level").value = ['200']
1532        self.browser.getControl("Show").click()
1533        self.assertFalse(self.student_id in self.browser.contents)
1534        self.browser.getControl(name="session").value = ['2004']
1535        self.browser.getControl(name="level").value = ['100']
1536        self.browser.getControl("Show").click()
1537        self.assertTrue(self.student_id in self.browser.contents)
1538        # The comment is indicated by 'yes'
1539        self.assertTrue('<td><span>yes</span></td>' in self.browser.contents)
1540        # Check if the enquiries form is not pre-filled with officer_comment
1541        # (regression test)
1542        self.browser.getLink("Logout").click()
1543        self.browser.open('http://localhost/app/enquiries')
1544        self.assertFalse(
1545            'You did not fill properly'
1546            in self.browser.contents)
1547        # When a student is cleared the comment is automatically deleted
1548        IWorkflowInfo(self.student).fireTransition('request_clearance')
1549        IWorkflowInfo(self.student).fireTransition('clear')
1550        self.assertEqual(self.student.officer_comment, None)
1551        return
1552
1553    def test_handle_mass_clearance_by_co(self):
1554        self.init_clearance_officer()
1555        # Additional setups according to test above
1556        notify(LocalRoleSetEvent(
1557            self.department, 'waeup.local.ClearanceOfficer', 'mrclear', granted=True))
1558        self.app['configuration']['2004'].clearance_enabled = True
1559        IWorkflowState(self.student).setState('clearance requested')
1560        # Update the catalog
1561        notify(grok.ObjectModifiedEvent(self.student))
1562        # The CO can go to the department and clear all students in department
1563        self.browser.open('http://localhost/app/faculties/fac1/dep1')
1564        self.browser.getLink("Clear all students").click()
1565        self.assertTrue('1 students have been cleared' in self.browser.contents)
1566        self.browser.open(self.history_path)
1567        self.assertTrue('Cleared by Carlo Pitter' in self.browser.contents)
1568        logfile = os.path.join(
1569            self.app['datacenter'].storage, 'logs', 'students.log')
1570        logcontent = open(logfile).read()
1571        self.assertTrue(
1572            'INFO - mrclear - K1000000 - Cleared' in logcontent)
1573        self.browser.open('http://localhost/app/faculties/fac1/dep1')
1574        self.browser.getLink("Clear all students").click()
1575        self.assertTrue('0 students have been cleared' in self.browser.contents)
1576        return
1577
1578    def test_handle_courses_by_ca(self):
1579        # Create course adviser
1580        self.app['users'].addUser('mrsadvise', 'mrsadvisesecret')
1581        self.app['users']['mrsadvise'].email = 'mradvise@foo.ng'
1582        self.app['users']['mrsadvise'].title = u'Helen Procter'
1583        # Assign local CourseAdviser100 role for a certificate
1584        cert = self.app['faculties']['fac1']['dep1'].certificates['CERT1']
1585        prmlocal = IPrincipalRoleManager(cert)
1586        prmlocal.assignRoleToPrincipal('waeup.local.CourseAdviser100', 'mrsadvise')
1587        IWorkflowState(self.student).setState('school fee paid')
1588        # Login as course adviser
1589        self.browser.open(self.login_path)
1590        self.browser.getControl(name="form.login").value = 'mrsadvise'
1591        self.browser.getControl(name="form.password").value = 'mrsadvisesecret'
1592        self.browser.getControl("Login").click()
1593        self.assertMatches('...You logged in...', self.browser.contents)
1594        # CO can see his roles
1595        self.browser.getLink("My Roles").click()
1596        self.assertMatches(
1597            '...<div>Academics Officer (view only)</div>...',
1598            self.browser.contents)
1599        # But not his local role ...
1600        self.assertFalse('Course Adviser' in self.browser.contents)
1601        # ... because we forgot to notify the certificate that the local role
1602        # has changed
1603        notify(LocalRoleSetEvent(
1604            cert, 'waeup.local.CourseAdviser100', 'mrsadvise', granted=True))
1605        self.browser.open('http://localhost/app/users/mrsadvise/my_roles')
1606        self.assertTrue('Course Adviser 100L' in self.browser.contents)
1607        self.assertMatches(
1608            '...<a href="http://localhost/app/faculties/fac1/dep1/certificates/CERT1">...',
1609            self.browser.contents)
1610        # CA can view the student ...
1611        self.browser.open(self.student_path)
1612        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1613        self.assertEqual(self.browser.url, self.student_path)
1614        # ... but not other students
1615        other_student = Student()
1616        other_student.firstname = u'Dep2'
1617        other_student.lastname = u'Student'
1618        self.app['students'].addStudent(other_student)
1619        other_student_path = (
1620            'http://localhost/app/students/%s' % other_student.student_id)
1621        self.assertRaises(
1622            Unauthorized, self.browser.open, other_student_path)
1623        # We add study level 110 to the student's studycourse
1624        studylevel = StudentStudyLevel()
1625        studylevel.level = 110
1626        self.student['studycourse'].addStudentStudyLevel(
1627            cert,studylevel)
1628        L110_student_path = self.studycourse_path + '/110'
1629        # The CA can neither see the Validate nor the Edit button
1630        self.browser.open(L110_student_path)
1631        self.assertFalse('Validate courses' in self.browser.contents)
1632        self.assertFalse('Edit' in self.browser.contents)
1633        IWorkflowInfo(self.student).fireTransition('register_courses')
1634        self.browser.open(L110_student_path)
1635        self.assertFalse('Validate courses' in self.browser.contents)
1636        self.assertFalse('Edit' in self.browser.contents)
1637        # Only in state courses registered and only if the current level
1638        # corresponds with the name of the study level object
1639        # the 100L CA does see the 'Validate' button but not
1640        # the edit button
1641        self.student['studycourse'].current_level = 110
1642        self.browser.open(L110_student_path)
1643        self.assertFalse('Edit' in self.browser.contents)
1644        self.assertTrue('Validate courses' in self.browser.contents)
1645        # But a 100L CA does not see the button at other levels
1646        studylevel2 = StudentStudyLevel()
1647        studylevel2.level = 200
1648        self.student['studycourse'].addStudentStudyLevel(
1649            cert,studylevel2)
1650        L200_student_path = self.studycourse_path + '/200'
1651        self.browser.open(L200_student_path)
1652        self.assertFalse('Edit' in self.browser.contents)
1653        self.assertFalse('Validate courses' in self.browser.contents)
1654        self.browser.open(L110_student_path)
1655        self.browser.getLink("Validate courses").click()
1656        self.assertTrue('Course list has been validated' in self.browser.contents)
1657        self.assertTrue('courses validated' in self.browser.contents)
1658        self.assertEqual(self.student['studycourse']['110'].validated_by,
1659            'Helen Procter')
1660        self.assertMatches(
1661            '<YYYY-MM-DD hh:mm:ss>',
1662            self.student['studycourse']['110'].validation_date.strftime(
1663                "%Y-%m-%d %H:%M:%S"))
1664        self.browser.getLink("Reject courses").click()
1665        self.assertTrue('Course list request has been annulled.'
1666            in self.browser.contents)
1667        urlmessage = 'Course+list+request+has+been+annulled.'
1668        self.assertEqual(self.browser.url, self.student_path +
1669            '/contactstudent?subject=%s' % urlmessage)
1670        self.assertTrue('school fee paid' in self.browser.contents)
1671        self.assertTrue(self.student['studycourse']['110'].validated_by is None)
1672        self.assertTrue(self.student['studycourse']['110'].validation_date is None)
1673        IWorkflowInfo(self.student).fireTransition('register_courses')
1674        self.browser.open(L110_student_path)
1675        self.browser.getLink("Reject courses").click()
1676        self.assertTrue('Course list request has been rejected'
1677            in self.browser.contents)
1678        self.assertTrue('school fee paid' in self.browser.contents)
1679        # CA does now see the contact form and can send a message
1680        self.browser.getControl(name="form.subject").value = 'Important subject'
1681        self.browser.getControl(name="form.body").value = 'Course list rejected'
1682        self.browser.getControl("Send message now").click()
1683        self.assertTrue('Your message has been sent' in self.browser.contents)
1684        # The CA does now see the Edit button and can edit
1685        # current study level
1686        self.browser.open(L110_student_path)
1687        self.browser.getLink("Edit").click()
1688        self.assertTrue('Edit course list of 100 (Year 1) on 1st probation'
1689            in self.browser.contents)
1690        # The CA can't validate courses if not in state
1691        # courses registered
1692        self.browser.open(L110_student_path + '/validate_courses')
1693        self.assertTrue('Student is in the wrong state'
1694            in self.browser.contents)
1695        # The CA can go to his certificate through the my_roles page
1696        self.browser.open('http://localhost/app/users/mrsadvise/my_roles')
1697        self.browser.getLink(
1698            "http://localhost/app/faculties/fac1/dep1/certificates/CERT1").click()
1699        # and view the list of students
1700        self.browser.getLink("Show students").click()
1701        self.browser.getControl(name="session").value = ['2004']
1702        self.browser.getControl(name="level").value = ['100']
1703        self.browser.getControl("Show").click()
1704        self.assertTrue(self.student_id in self.browser.contents)
1705
1706    def test_handle_courses_by_lecturer(self):
1707        # Create course lecturer
1708        self.app['users'].addUser('mrslecturer', 'mrslecturersecret')
1709        self.app['users']['mrslecturer'].email = 'mrslecturer@foo.ng'
1710        self.app['users']['mrslecturer'].title = u'Mercedes Benz'
1711        # Assign local Lecturer role for a certificate
1712        course = self.app['faculties']['fac1']['dep1'].courses['COURSE1']
1713        prmlocal = IPrincipalRoleManager(course)
1714        prmlocal.assignRoleToPrincipal('waeup.local.Lecturer', 'mrslecturer')
1715        # Login as lecturer
1716        self.browser.open(self.login_path)
1717        self.browser.getControl(name="form.login").value = 'mrslecturer'
1718        self.browser.getControl(name="form.password").value = 'mrslecturersecret'
1719        self.browser.getControl("Login").click()
1720        self.assertMatches('...You logged in...', self.browser.contents)
1721        # CO can see her roles
1722        self.browser.getLink("My Roles").click()
1723        self.assertMatches(
1724            '...<div>Academics Officer (view only)</div>...',
1725            self.browser.contents)
1726        # But not her local role ...
1727        self.assertFalse('Lecturer' in self.browser.contents)
1728        # ... because we forgot to notify the course that the local role
1729        # has changed
1730        notify(LocalRoleSetEvent(
1731            course, 'waeup.local.Lecturer', 'mrslecturer', granted=True))
1732        self.browser.open('http://localhost/app/users/mrslecturer/my_roles')
1733        self.assertTrue('Lecturer' in self.browser.contents)
1734        self.assertMatches(
1735            '...<a href="http://localhost/app/faculties/fac1/dep1/courses/COURSE1">...',
1736            self.browser.contents)
1737        # The lecturer can go to her course
1738        self.browser.getLink(
1739            "http://localhost/app/faculties/fac1/dep1/courses/COURSE1").click()
1740        # and view the list of students
1741        self.browser.getLink("Show students").click()
1742        self.browser.getControl(name="session").value = ['2004']
1743        self.browser.getControl(name="level").value = ['100']
1744        self.browser.getControl("Show").click()
1745        self.assertTrue('No student found.' in self.browser.contents)
1746        # No student in course so far
1747        self.assertFalse(self.student_id in self.browser.contents)
1748        studylevel = createObject(u'waeup.StudentStudyLevel')
1749        studylevel.level = 100
1750        studylevel.level_session = 2004
1751        self.student['studycourse'].addStudentStudyLevel(
1752            self.certificate, studylevel)
1753        # Now the student has registered the course and can
1754        # be seen by the lecturer.
1755        self.browser.open("http://localhost/app/faculties/fac1/dep1/courses/COURSE1/students")
1756        self.browser.getControl(name="session").value = ['2004']
1757        self.browser.getControl(name="level").value = ['100']
1758        self.browser.getControl("Show").click()
1759        self.assertTrue(self.student_id in self.browser.contents)
1760        # The course ticket can be linked with the course.
1761        self.assertEqual(
1762            self.student['studycourse']['100']['COURSE1'].course,
1763            self.course)
1764        # Lecturer can neither access the student ...
1765        self.assertRaises(
1766            Unauthorized, self.browser.open, self.student_path)
1767        # ... nor the respective course ticket since
1768        # editing course tickets by lecturers is not feasible.
1769        self.assertTrue('COURSE1' in self.student['studycourse']['100'].keys())
1770        course_ticket_path = self.student_path + '/studycourse/100/COURSE1'
1771        self.assertRaises(
1772            Unauthorized, self.browser.open, course_ticket_path)
1773        # Course results can be batch edited via the edit_courses view
1774        self.app['faculties']['fac1']['dep1'].score_editing_disabled = True
1775        self.browser.open("http://localhost/app/faculties/fac1/dep1/courses/COURSE1")
1776        self.browser.getLink("Update scores").click()
1777        self.assertTrue('Score editing disabled' in self.browser.contents)
1778        self.app['faculties']['fac1']['dep1'].score_editing_disabled = False
1779        self.browser.getLink("Update scores").click()
1780        self.assertTrue('Current academic session not set' in self.browser.contents)
1781        self.app['configuration'].current_academic_session = 2004
1782        self.browser.getLink("Update scores").click()
1783        self.assertFalse(
1784            '<input type="text" name="scores" class="span1" />'
1785            in self.browser.contents)
1786        IWorkflowState(self.student).setState('courses validated')
1787        # Student must be in state 'courses validated'
1788        self.browser.open(
1789            "http://localhost/app/faculties/fac1/dep1/courses/COURSE1/edit_scores")
1790        self.assertTrue(
1791            '<input type="text" name="scores" class="span1" />'
1792            in self.browser.contents)
1793        self.browser.getControl(name="scores", index=0).value = '55'
1794        self.browser.getControl("Update scores").click()
1795        # New score has been set
1796        self.assertEqual(
1797            self.student['studycourse']['100']['COURSE1'].score, 55)
1798        # Score editing has been logged
1799        logfile = os.path.join(
1800            self.app['datacenter'].storage, 'logs', 'students.log')
1801        logcontent = open(logfile).read()
1802        self.assertTrue('mrslecturer - students.browser.EditScoresPage - '
1803                        'K1000000 100/COURSE1 score updated (55)' in logcontent)
1804        # Non-integer scores won't be accepted
1805        self.browser.open(
1806            "http://localhost/app/faculties/fac1/dep1/courses/COURSE1/edit_scores")
1807        self.assertTrue('value="55" />' in self.browser.contents)
1808        self.browser.getControl(name="scores", index=0).value = 'abc'
1809        self.browser.getControl("Update scores").click()
1810        self.assertTrue('Error: Score(s) of Anna Tester have not be updated'
1811            in self.browser.contents)
1812        # Scores can be removed
1813        self.browser.open(
1814            "http://localhost/app/faculties/fac1/dep1/courses/COURSE1/edit_scores")
1815        self.browser.getControl(name="scores", index=0).value = ''
1816        self.browser.getControl("Update scores").click()
1817        self.assertEqual(
1818            self.student['studycourse']['100']['COURSE1'].score, None)
1819        logcontent = open(logfile).read()
1820        self.assertTrue('mrslecturer - students.browser.EditScoresPage - '
1821                        'K1000000 100/COURSE1 score updated (None)' in logcontent)
1822
1823    def test_change_current_mode(self):
1824        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1825        self.browser.open(self.clearance_path)
1826        self.assertFalse('Employer' in self.browser.contents)
1827        self.browser.open(self.manage_clearance_path)
1828        self.assertFalse('Employer' in self.browser.contents)
1829        self.student.clearance_locked = False
1830        self.browser.open(self.edit_clearance_path)
1831        self.assertFalse('Employer' in self.browser.contents)
1832        # Now we change the study mode of the certificate and a different
1833        # interface is used by clearance views.
1834        self.certificate.study_mode = 'pg_ft'
1835        # Invariants are not being checked here?!
1836        self.certificate.end_level = 100
1837        self.browser.open(self.clearance_path)
1838        self.assertTrue('Employer' in self.browser.contents)
1839        self.browser.open(self.manage_clearance_path)
1840        self.assertTrue('Employer' in self.browser.contents)
1841        self.browser.open(self.edit_clearance_path)
1842        self.assertTrue('Employer' in self.browser.contents)
1843
1844    def test_find_students_in_faculties(self):
1845        # Create local students manager in faculty
1846        self.app['users'].addUser('mrmanager', 'mrmanagersecret')
1847        self.app['users']['mrmanager'].email = 'mrmanager@foo.ng'
1848        self.app['users']['mrmanager'].title = u'Volk Wagen'
1849        # Assign LocalStudentsManager role for faculty
1850        fac = self.app['faculties']['fac1']
1851        prmlocal = IPrincipalRoleManager(fac)
1852        prmlocal.assignRoleToPrincipal(
1853            'waeup.local.LocalStudentsManager', 'mrmanager')
1854        notify(LocalRoleSetEvent(
1855            fac, 'waeup.local.LocalStudentsManager', 'mrmanager',
1856            granted=True))
1857        # Login as manager
1858        self.browser.open(self.login_path)
1859        self.browser.getControl(name="form.login").value = 'mrmanager'
1860        self.browser.getControl(name="form.password").value = 'mrmanagersecret'
1861        self.browser.getControl("Login").click()
1862        self.assertMatches('...You logged in...', self.browser.contents)
1863        # Manager can see his roles
1864        self.browser.getLink("My Roles").click()
1865        self.assertMatches(
1866            '...<span>Students Manager</span>...',
1867            self.browser.contents)
1868        # The manager can go to his faculty
1869        self.browser.getLink(
1870            "http://localhost/app/faculties/fac1").click()
1871        # and find students
1872        self.browser.getLink("Find students").click()
1873        self.browser.getControl("Find student").click()
1874        self.assertTrue('Empty search string' in self.browser.contents)
1875        self.browser.getControl(name="searchtype").value = ['student_id']
1876        self.browser.getControl(name="searchterm").value = self.student_id
1877        self.browser.getControl("Find student").click()
1878        self.assertTrue('Anna Tester' in self.browser.contents)
1879
1880    def test_activate_deactivate_buttons(self):
1881        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1882        self.browser.open(self.student_path)
1883        self.browser.getLink("Deactivate").click()
1884        self.assertTrue(
1885            'Student account has been deactivated.' in self.browser.contents)
1886        self.assertTrue(
1887            'Base Data (account deactivated)' in self.browser.contents)
1888        self.assertTrue(self.student.suspended)
1889        self.browser.getLink("Activate").click()
1890        self.assertTrue(
1891            'Student account has been activated.' in self.browser.contents)
1892        self.assertFalse(
1893            'Base Data (account deactivated)' in self.browser.contents)
1894        self.assertFalse(self.student.suspended)
1895        # History messages have been added ...
1896        self.browser.getLink("History").click()
1897        self.assertTrue(
1898            'Student account deactivated by Manager<br />' in self.browser.contents)
1899        self.assertTrue(
1900            'Student account activated by Manager<br />' in self.browser.contents)
1901        # ... and actions have been logged.
1902        logfile = os.path.join(
1903            self.app['datacenter'].storage, 'logs', 'students.log')
1904        logcontent = open(logfile).read()
1905        self.assertTrue('zope.mgr - students.browser.StudentDeactivatePage - '
1906                        'K1000000 - account deactivated' in logcontent)
1907        self.assertTrue('zope.mgr - students.browser.StudentActivatePage - '
1908                        'K1000000 - account activated' in logcontent)
1909
1910    def test_manage_student_transfer(self):
1911        # Add second certificate
1912        self.certificate2 = createObject('waeup.Certificate')
1913        self.certificate2.code = u'CERT2'
1914        self.certificate2.study_mode = 'ug_ft'
1915        self.certificate2.start_level = 999
1916        self.certificate2.end_level = 999
1917        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
1918            self.certificate2)
1919
1920        # Add study level to old study course
1921        studylevel = createObject(u'waeup.StudentStudyLevel')
1922        studylevel.level = 200
1923        self.student['studycourse'].addStudentStudyLevel(
1924            self.certificate, studylevel)
1925        studylevel = createObject(u'waeup.StudentStudyLevel')
1926        studylevel.level = 999
1927        self.student['studycourse'].addStudentStudyLevel(
1928            self.certificate, studylevel)
1929
1930        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1931        self.browser.open(self.student_path)
1932        self.browser.getLink("Transfer").click()
1933        self.browser.getControl(name="form.certificate").value = ['CERT2']
1934        self.browser.getControl(name="form.current_session").value = ['2011']
1935        self.browser.getControl(name="form.current_level").value = ['200']
1936        self.browser.getControl("Transfer").click()
1937        self.assertTrue(
1938            'Current level does not match certificate levels'
1939            in self.browser.contents)
1940        self.browser.getControl(name="form.current_level").value = ['999']
1941        self.browser.getControl("Transfer").click()
1942        self.assertTrue('Successfully transferred' in self.browser.contents)
1943        # The catalog has been updated
1944        cat = queryUtility(ICatalog, name='students_catalog')
1945        results = list(
1946            cat.searchResults(
1947            certcode=('CERT2', 'CERT2')))
1948        self.assertTrue(results[0] is self.student)
1949        results = list(
1950            cat.searchResults(
1951            current_session=(2011, 2011)))
1952        self.assertTrue(results[0] is self.student)
1953        # Add study level to new study course
1954        studylevel = createObject(u'waeup.StudentStudyLevel')
1955        studylevel.level = 999
1956        self.student['studycourse'].addStudentStudyLevel(
1957            self.certificate, studylevel)
1958
1959        # Edit and add pages are locked for old study courses
1960        self.browser.open(self.student_path + '/studycourse/manage')
1961        self.assertFalse('The requested form is locked' in self.browser.contents)
1962        self.browser.open(self.student_path + '/studycourse_1/manage')
1963        self.assertTrue('The requested form is locked' in self.browser.contents)
1964
1965        self.browser.open(self.student_path + '/studycourse/start_session')
1966        self.assertFalse('The requested form is locked' in self.browser.contents)
1967        self.browser.open(self.student_path + '/studycourse_1/start_session')
1968        self.assertTrue('The requested form is locked' in self.browser.contents)
1969
1970        IWorkflowState(self.student).setState('school fee paid')
1971        self.browser.open(self.student_path + '/studycourse/add')
1972        self.assertFalse('The requested form is locked' in self.browser.contents)
1973        self.browser.open(self.student_path + '/studycourse_1/add')
1974        self.assertTrue('The requested form is locked' in self.browser.contents)
1975
1976        self.browser.open(self.student_path + '/studycourse/999/manage')
1977        self.assertFalse('The requested form is locked' in self.browser.contents)
1978        self.browser.open(self.student_path + '/studycourse_1/999/manage')
1979        self.assertTrue('The requested form is locked' in self.browser.contents)
1980
1981        self.browser.open(self.student_path + '/studycourse/999/validate_courses')
1982        self.assertFalse('The requested form is locked' in self.browser.contents)
1983        self.browser.open(self.student_path + '/studycourse_1/999/validate_courses')
1984        self.assertTrue('The requested form is locked' in self.browser.contents)
1985
1986        self.browser.open(self.student_path + '/studycourse/999/reject_courses')
1987        self.assertFalse('The requested form is locked' in self.browser.contents)
1988        self.browser.open(self.student_path + '/studycourse_1/999/reject_courses')
1989        self.assertTrue('The requested form is locked' in self.browser.contents)
1990
1991        self.browser.open(self.student_path + '/studycourse/999/add')
1992        self.assertFalse('The requested form is locked' in self.browser.contents)
1993        self.browser.open(self.student_path + '/studycourse_1/999/add')
1994        self.assertTrue('The requested form is locked' in self.browser.contents)
1995
1996        self.browser.open(self.student_path + '/studycourse/999/edit')
1997        self.assertFalse('The requested form is locked' in self.browser.contents)
1998        self.browser.open(self.student_path + '/studycourse_1/999/edit')
1999        self.assertTrue('The requested form is locked' in self.browser.contents)
2000
2001        # Revert transfer
2002        self.browser.open(self.student_path + '/studycourse_1')
2003        self.browser.getLink("Reactivate").click()
2004        self.browser.getControl("Revert now").click()
2005        self.assertTrue('Previous transfer reverted' in self.browser.contents)
2006        results = list(
2007            cat.searchResults(
2008            certcode=('CERT1', 'CERT1')))
2009        self.assertTrue(results[0] is self.student)
2010        self.assertEqual([i for i in self.student.keys()],
2011            [u'accommodation', u'payments', u'studycourse'])
2012
2013    def test_login_as_student(self):
2014        # StudentImpersonators can login as student
2015        # Create clearance officer
2016        self.app['users'].addUser('mrofficer', 'mrofficersecret')
2017        self.app['users']['mrofficer'].email = 'mrofficer@foo.ng'
2018        self.app['users']['mrofficer'].title = 'Harry Actor'
2019        prmglobal = IPrincipalRoleManager(self.app)
2020        prmglobal.assignRoleToPrincipal('waeup.StudentImpersonator', 'mrofficer')
2021        prmglobal.assignRoleToPrincipal('waeup.StudentsManager', 'mrofficer')
2022        # Login as student impersonator
2023        self.browser.open(self.login_path)
2024        self.browser.getControl(name="form.login").value = 'mrofficer'
2025        self.browser.getControl(name="form.password").value = 'mrofficersecret'
2026        self.browser.getControl("Login").click()
2027        self.assertMatches('...You logged in...', self.browser.contents)
2028        self.browser.open(self.student_path)
2029        self.browser.getLink("Login as").click()
2030        self.browser.getControl("Set password now").click()
2031        temp_password = self.browser.getControl(name='form.password').value
2032        self.browser.getControl("Login now").click()
2033        self.assertMatches(
2034            '...You successfully logged in as...', self.browser.contents)
2035        # We are logged in as student and can see the 'My Data' tab
2036        self.assertMatches(
2037            '...<a href="#" class="dropdown-toggle" data-toggle="dropdown">...',
2038            self.browser.contents)
2039        self.assertMatches(
2040            '...My Data...',
2041            self.browser.contents)
2042        self.browser.getLink("Logout").click()
2043        # The student can't login with the original password ...
2044        self.browser.open(self.login_path)
2045        self.browser.getControl(name="form.login").value = self.student_id
2046        self.browser.getControl(name="form.password").value = 'spwd'
2047        self.browser.getControl("Login").click()
2048        self.assertMatches(
2049            '...Your account has been temporarily deactivated...',
2050            self.browser.contents)
2051        # ... but with the temporary password
2052        self.browser.open(self.login_path)
2053        self.browser.getControl(name="form.login").value = self.student_id
2054        self.browser.getControl(name="form.password").value = temp_password
2055        self.browser.getControl("Login").click()
2056        self.assertMatches('...You logged in...', self.browser.contents)
2057        # Creation of temp_password is properly logged
2058        logfile = os.path.join(
2059            self.app['datacenter'].storage, 'logs', 'students.log')
2060        logcontent = open(logfile).read()
2061        self.assertTrue(
2062            'mrofficer - students.browser.LoginAsStudentStep1 - K1000000 - '
2063            'temp_password generated: %s' % temp_password in logcontent)
2064
2065    def test_transcripts(self):
2066        studylevel = createObject(u'waeup.StudentStudyLevel')
2067        studylevel.level = 100
2068        studylevel.level_session = 2005
2069        self.student['studycourse'].entry_mode = 'ug_ft'
2070        self.student['studycourse'].addStudentStudyLevel(
2071            self.certificate, studylevel)
2072        studylevel2 = createObject(u'waeup.StudentStudyLevel')
2073        studylevel2.level = 110
2074        studylevel2.level_session = 2006
2075        self.student['studycourse'].addStudentStudyLevel(
2076            self.certificate, studylevel2)
2077        # Add second course (COURSE has been added automatically)
2078        courseticket = createObject('waeup.CourseTicket')
2079        courseticket.code = 'ANYCODE'
2080        courseticket.title = u'Any TITLE'
2081        courseticket.credits = 13
2082        courseticket.score = 66
2083        courseticket.semester = 1
2084        courseticket.dcode = u'ANYDCODE'
2085        courseticket.fcode = u'ANYFCODE'
2086        self.student['studycourse']['110']['COURSE2'] = courseticket
2087        self.student['studycourse']['100']['COURSE1'].score = 55
2088        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[0], 3.0)
2089        self.assertEqual(self.student['studycourse']['110'].gpa_params_rectified[0], 4.0)
2090        # Get transcript data
2091        td = self.student['studycourse'].getTranscriptData()
2092        self.assertEqual(td[0][0]['level_key'], '100')
2093        self.assertEqual(td[0][0]['sgpa'], 3.0)
2094        self.assertEqual(td[0][0]['level'].level, 100)
2095        self.assertEqual(td[0][0]['level'].level_session, 2005)
2096        self.assertEqual(td[0][0]['tickets_1'][0].code, 'COURSE1')
2097        self.assertEqual(td[0][1]['level_key'], '110')
2098        self.assertEqual(td[0][1]['sgpa'], 4.0)
2099        self.assertEqual(td[0][1]['level'].level, 110)
2100        self.assertEqual(td[0][1]['level'].level_session, 2006)
2101        self.assertEqual(td[0][1]['tickets_1'][0].code, 'ANYCODE')
2102        self.assertEqual(td[1], 3.57)
2103        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
2104        self.browser.open(self.student_path + '/studycourse/transcript')
2105        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2106        self.assertTrue('Transcript' in self.browser.contents)
2107        # Officers can open the pdf transcript
2108        self.browser.open(self.student_path + '/studycourse/transcript.pdf')
2109        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2110        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2111        path = os.path.join(samples_dir(), 'transcript.pdf')
2112        open(path, 'wb').write(self.browser.contents)
2113        print "Sample PDF transcript.pdf written to %s" % path
2114
2115    def test_process_transcript_request(self):
2116        IWorkflowState(self.student).setState('transcript requested')
2117        notify(grok.ObjectModifiedEvent(self.student))
2118        self.student.transcript_comment = (
2119            u'On 07/08/2013 08:59:54 UTC K1000000 wrote:\n\nComment line 1 \n'
2120            'Comment line2\n\nDispatch Address:\nAddress line 1 \n'
2121            'Address line2\n\n')
2122        # Create transcript officer
2123        self.app['users'].addUser('mrtranscript', 'mrtranscriptsecret')
2124        self.app['users']['mrtranscript'].email = 'mrtranscript@foo.ng'
2125        self.app['users']['mrtranscript'].title = 'Ruth Gordon'
2126        prmglobal = IPrincipalRoleManager(self.app)
2127        prmglobal.assignRoleToPrincipal('waeup.TranscriptOfficer', 'mrtranscript')
2128        # Login as transcript officer
2129        self.browser.open(self.login_path)
2130        self.browser.getControl(name="form.login").value = 'mrtranscript'
2131        self.browser.getControl(name="form.password").value = 'mrtranscriptsecret'
2132        self.browser.getControl("Login").click()
2133        self.assertMatches('...You logged in...', self.browser.contents)
2134        # Officer can see his roles
2135        self.browser.getLink("My Roles").click()
2136        self.assertMatches(
2137            '...<div>Transcript Officer</div>...',
2138            self.browser.contents)
2139        # Officer can search for students in state 'transcripr requested'
2140        self.browser.open(self.container_path)
2141        self.browser.getControl(name="searchtype").value = ['transcript']
2142        self.browser.getControl("Find student(s)").click()
2143        self.assertTrue('Anna Tester' in self.browser.contents)
2144        self.browser.getLink("K1000000").click()
2145        self.browser.getLink("Manage transcript request").click()
2146        self.assertTrue(' UTC K1000000 wrote:<br><br>Comment line 1 <br>'
2147        'Comment line2<br><br>Dispatch Address:<br>Address line 1 <br>'
2148        'Address line2<br><br></p>' in self.browser.contents)
2149        self.browser.getControl(name="comment").value = (
2150            'Hello,\nYour transcript has been sent to the address provided.')
2151        self.browser.getControl("Save comment and mark as processed").click()
2152        self.assertTrue(
2153            'UTC mrtranscript wrote:\n\nHello,\nYour transcript has '
2154            'been sent to the address provided.\n\n'
2155            in self.student.transcript_comment)
2156        # The comment has been logged
2157        logfile = os.path.join(
2158            self.app['datacenter'].storage, 'logs', 'students.log')
2159        logcontent = open(logfile).read()
2160        self.assertTrue(
2161            'mrtranscript - students.browser.StudentTranscriptRequestProcessFormPage - '
2162            'K1000000 - comment: Hello,<br>'
2163            'Your transcript has been sent to the address provided'
2164            in logcontent)
2165
2166class StudentUITests(StudentsFullSetup):
2167    # Tests for Student class views and pages
2168
2169    def test_student_change_password(self):
2170        # Students can change the password
2171        self.student.personal_updated = datetime.utcnow()
2172        self.browser.open(self.login_path)
2173        self.browser.getControl(name="form.login").value = self.student_id
2174        self.browser.getControl(name="form.password").value = 'spwd'
2175        self.browser.getControl("Login").click()
2176        self.assertEqual(self.browser.url, self.student_path)
2177        self.assertTrue('You logged in' in self.browser.contents)
2178        # Change password
2179        self.browser.getLink("Change password").click()
2180        self.browser.getControl(name="change_password").value = 'pw'
2181        self.browser.getControl(
2182            name="change_password_repeat").value = 'pw'
2183        self.browser.getControl("Save").click()
2184        self.assertTrue('Password must have at least' in self.browser.contents)
2185        self.browser.getControl(name="change_password").value = 'new_password'
2186        self.browser.getControl(
2187            name="change_password_repeat").value = 'new_passssword'
2188        self.browser.getControl("Save").click()
2189        self.assertTrue('Passwords do not match' in self.browser.contents)
2190        self.browser.getControl(name="change_password").value = 'new_password'
2191        self.browser.getControl(
2192            name="change_password_repeat").value = 'new_password'
2193        self.browser.getControl("Save").click()
2194        self.assertTrue('Password changed' in self.browser.contents)
2195        # We are still logged in. Changing the password hasn't thrown us out.
2196        self.browser.getLink("Base Data").click()
2197        self.assertEqual(self.browser.url, self.student_path)
2198        # We can logout
2199        self.browser.getLink("Logout").click()
2200        self.assertTrue('You have been logged out' in self.browser.contents)
2201        self.assertEqual(self.browser.url, 'http://localhost/app/index')
2202        # We can login again with the new password
2203        self.browser.getLink("Login").click()
2204        self.browser.open(self.login_path)
2205        self.browser.getControl(name="form.login").value = self.student_id
2206        self.browser.getControl(name="form.password").value = 'new_password'
2207        self.browser.getControl("Login").click()
2208        self.assertEqual(self.browser.url, self.student_path)
2209        self.assertTrue('You logged in' in self.browser.contents)
2210        return
2211
2212    def test_setpassword(self):
2213        # Set password for first-time access
2214        student = Student()
2215        student.reg_number = u'123456'
2216        student.firstname = u'Klaus'
2217        student.lastname = u'Tester'
2218        self.app['students'].addStudent(student)
2219        setpassword_path = 'http://localhost/app/setpassword'
2220        student_path = 'http://localhost/app/students/%s' % student.student_id
2221        self.browser.open(setpassword_path)
2222        self.browser.getControl(name="ac_series").value = self.existing_pwdseries
2223        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
2224        self.browser.getControl(name="reg_number").value = '223456'
2225        self.browser.getControl("Set").click()
2226        self.assertMatches('...No student found...',
2227                           self.browser.contents)
2228        self.browser.getControl(name="reg_number").value = '123456'
2229        self.browser.getControl(name="ac_number").value = '999999'
2230        self.browser.getControl("Set").click()
2231        self.assertMatches('...Access code is invalid...',
2232                           self.browser.contents)
2233        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
2234        self.browser.getControl("Set").click()
2235        self.assertMatches('...Password has been set. Your Student Id is...',
2236                           self.browser.contents)
2237        self.browser.getControl("Set").click()
2238        self.assertMatches(
2239            '...Password has already been set. Your Student Id is...',
2240            self.browser.contents)
2241        existing_pwdpin = self.pwdpins[1]
2242        parts = existing_pwdpin.split('-')[1:]
2243        existing_pwdseries, existing_pwdnumber = parts
2244        self.browser.getControl(name="ac_series").value = existing_pwdseries
2245        self.browser.getControl(name="ac_number").value = existing_pwdnumber
2246        self.browser.getControl(name="reg_number").value = '123456'
2247        self.browser.getControl("Set").click()
2248        self.assertMatches(
2249            '...You are using the wrong Access Code...',
2250            self.browser.contents)
2251        # The student can login with the new credentials
2252        self.browser.open(self.login_path)
2253        self.browser.getControl(name="form.login").value = student.student_id
2254        self.browser.getControl(
2255            name="form.password").value = self.existing_pwdnumber
2256        self.browser.getControl("Login").click()
2257        self.assertEqual(self.browser.url, student_path)
2258        self.assertTrue('You logged in' in self.browser.contents)
2259        return
2260
2261    def test_student_login(self):
2262        # Student cant login if their password is not set
2263        self.student.password = None
2264        self.browser.open(self.login_path)
2265        self.browser.getControl(name="form.login").value = self.student_id
2266        self.browser.getControl(name="form.password").value = 'spwd'
2267        self.browser.getControl("Login").click()
2268        self.assertTrue(
2269            'You entered invalid credentials.' in self.browser.contents)
2270        # We set the password again
2271        IUserAccount(
2272            self.app['students'][self.student_id]).setPassword('spwd')
2273        # Students can't login if their account is suspended/deactivated
2274        self.student.suspended = True
2275        self.browser.open(self.login_path)
2276        self.browser.getControl(name="form.login").value = self.student_id
2277        self.browser.getControl(name="form.password").value = 'spwd'
2278        self.browser.getControl("Login").click()
2279        self.assertMatches(
2280            '...<div class="alert alert-warning">'
2281            'Your account has been deactivated.</div>...', self.browser.contents)
2282        # If suspended_comment is set this message will be flashed instead
2283        self.student.suspended_comment = u'Aetsch baetsch!'
2284        self.browser.getControl(name="form.login").value = self.student_id
2285        self.browser.getControl(name="form.password").value = 'spwd'
2286        self.browser.getControl("Login").click()
2287        self.assertMatches(
2288            '...<div class="alert alert-warning">Aetsch baetsch!</div>...',
2289            self.browser.contents)
2290        self.student.suspended = False
2291        # Students can't login if a temporary password has been set and
2292        # is not expired
2293        self.app['students'][self.student_id].setTempPassword(
2294            'anybody', 'temp_spwd')
2295        self.browser.open(self.login_path)
2296        self.browser.getControl(name="form.login").value = self.student_id
2297        self.browser.getControl(name="form.password").value = 'spwd'
2298        self.browser.getControl("Login").click()
2299        self.assertMatches(
2300            '...Your account has been temporarily deactivated...',
2301            self.browser.contents)
2302        # The student can login with the temporary password
2303        self.browser.open(self.login_path)
2304        self.browser.getControl(name="form.login").value = self.student_id
2305        self.browser.getControl(name="form.password").value = 'temp_spwd'
2306        self.browser.getControl("Login").click()
2307        self.assertMatches(
2308            '...You logged in...', self.browser.contents)
2309        # Student can view the base data
2310        self.browser.open(self.student_path)
2311        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2312        self.assertEqual(self.browser.url, self.student_path)
2313        # When the password expires ...
2314        delta = timedelta(minutes=11)
2315        self.app['students'][self.student_id].temp_password[
2316            'timestamp'] = datetime.utcnow() - delta
2317        self.app['students'][self.student_id]._p_changed = True
2318        # ... the student will be automatically logged out
2319        self.assertRaises(
2320            Unauthorized, self.browser.open, self.student_path)
2321        # Then the student can login with the original password
2322        self.browser.open(self.login_path)
2323        self.browser.getControl(name="form.login").value = self.student_id
2324        self.browser.getControl(name="form.password").value = 'spwd'
2325        self.browser.getControl("Login").click()
2326        self.assertMatches(
2327            '...You logged in...', self.browser.contents)
2328
2329    def test_student_clearance(self):
2330        # Student cant login if their password is not set
2331        IWorkflowInfo(self.student).fireTransition('admit')
2332        self.browser.open(self.login_path)
2333        self.browser.getControl(name="form.login").value = self.student_id
2334        self.browser.getControl(name="form.password").value = 'spwd'
2335        self.browser.getControl("Login").click()
2336        self.assertMatches(
2337            '...You logged in...', self.browser.contents)
2338        # Admitted student can upload a passport picture
2339        self.browser.open(self.student_path + '/change_portrait')
2340        ctrl = self.browser.getControl(name='passportuploadedit')
2341        file_obj = open(SAMPLE_IMAGE, 'rb')
2342        file_ctrl = ctrl.mech_control
2343        file_ctrl.add_file(file_obj, filename='my_photo.jpg')
2344        self.browser.getControl(
2345            name='upload_passportuploadedit').click()
2346        self.assertTrue(
2347            'src="http://localhost/app/students/K1000000/passport.jpg"'
2348            in self.browser.contents)
2349        # Students can open admission letter
2350        self.browser.getLink("Base Data").click()
2351        self.browser.getLink("Download admission letter").click()
2352        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2353        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2354        # Student can view the clearance data
2355        self.browser.open(self.student_path)
2356        self.browser.getLink("Clearance Data").click()
2357        # Student can't open clearance edit form before starting clearance
2358        self.browser.open(self.student_path + '/cedit')
2359        self.assertMatches('...The requested form is locked...',
2360                           self.browser.contents)
2361        self.browser.getLink("Clearance Data").click()
2362        self.browser.getLink("Start clearance").click()
2363        self.student.email = None
2364        # Uups, we forgot to fill the email fields
2365        self.browser.getControl("Start clearance").click()
2366        self.assertMatches('...Not all required fields filled...',
2367                           self.browser.contents)
2368        self.browser.open(self.student_path + '/edit_base')
2369        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
2370        self.browser.getControl("Save").click()
2371        self.browser.open(self.student_path + '/start_clearance')
2372        self.browser.getControl(name="ac_series").value = '3'
2373        self.browser.getControl(name="ac_number").value = '4444444'
2374        self.browser.getControl("Start clearance now").click()
2375        self.assertMatches('...Activation code is invalid...',
2376                           self.browser.contents)
2377        self.browser.getControl(name="ac_series").value = self.existing_clrseries
2378        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
2379        # Owner is Hans Wurst, AC can't be invalidated
2380        self.browser.getControl("Start clearance now").click()
2381        self.assertMatches('...You are not the owner of this access code...',
2382                           self.browser.contents)
2383        # Set the correct owner
2384        self.existing_clrac.owner = self.student_id
2385        # clr_code might be set (and thus returns None) due importing
2386        # an empty clr_code column.
2387        self.student.clr_code = None
2388        self.browser.getControl("Start clearance now").click()
2389        self.assertMatches('...Clearance process has been started...',
2390                           self.browser.contents)
2391        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
2392        self.browser.getControl("Save", index=0).click()
2393        # Student can view the clearance data
2394        self.browser.getLink("Clearance Data").click()
2395        # and go back to the edit form
2396        self.browser.getLink("Edit").click()
2397        # Students can upload documents
2398        ctrl = self.browser.getControl(name='birthcertificateupload')
2399        file_obj = open(SAMPLE_IMAGE, 'rb')
2400        file_ctrl = ctrl.mech_control
2401        file_ctrl.add_file(file_obj, filename='my_birth_certificate.jpg')
2402        self.browser.getControl(
2403            name='upload_birthcertificateupload').click()
2404        self.assertTrue(
2405            'href="http://localhost/app/students/K1000000/birth_certificate"'
2406            in self.browser.contents)
2407        # Students can open clearance slip
2408        self.browser.getLink("View").click()
2409        self.browser.getLink("Download clearance slip").click()
2410        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2411        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2412        # Students can request clearance
2413        self.browser.open(self.edit_clearance_path)
2414        self.browser.getControl("Save and request clearance").click()
2415        self.browser.getControl(name="ac_series").value = self.existing_clrseries
2416        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
2417        self.browser.getControl("Request clearance now").click()
2418        self.assertMatches('...Clearance has been requested...',
2419                           self.browser.contents)
2420        # Student can't reopen clearance form after requesting clearance
2421        self.browser.open(self.student_path + '/cedit')
2422        self.assertMatches('...The requested form is locked...',
2423                           self.browser.contents)
2424
2425    def test_student_course_registration(self):
2426        # Student cant login if their password is not set
2427        IWorkflowInfo(self.student).fireTransition('admit')
2428        self.browser.open(self.login_path)
2429        self.browser.getControl(name="form.login").value = self.student_id
2430        self.browser.getControl(name="form.password").value = 'spwd'
2431        self.browser.getControl("Login").click()
2432        # Student can't add study level if not in state 'school fee paid'
2433        self.browser.open(self.student_path + '/studycourse/add')
2434        self.assertMatches('...The requested form is locked...',
2435                           self.browser.contents)
2436        # ... and must be transferred first
2437        IWorkflowState(self.student).setState('school fee paid')
2438        # Now students can add the current study level
2439        self.browser.getLink("Study Course").click()
2440        self.student['studycourse'].current_level = None
2441        self.browser.getLink("Add course list").click()
2442        self.assertMatches('...Your data are incomplete...',
2443                           self.browser.contents)
2444        self.student['studycourse'].current_level = 100
2445        self.browser.getLink("Add course list").click()
2446        self.assertMatches('...Add current level 100 (Year 1)...',
2447                           self.browser.contents)
2448        self.browser.getControl("Create course list now").click()
2449        # A level with one course ticket was created
2450        self.assertEqual(self.student['studycourse']['100'].number_of_tickets, 1)
2451        self.browser.getLink("100").click()
2452        self.browser.getLink("Edit course list").click()
2453        self.browser.getLink("here").click()
2454        self.browser.getControl(name="form.course").value = ['COURSE1']
2455        self.browser.getControl("Add course ticket").click()
2456        self.assertMatches('...The ticket exists...',
2457                           self.browser.contents)
2458        self.student['studycourse'].current_level = 200
2459        self.browser.getLink("Study Course").click()
2460        self.browser.getLink("Add course list").click()
2461        self.assertMatches('...Add current level 200 (Year 2)...',
2462                           self.browser.contents)
2463        self.browser.getControl("Create course list now").click()
2464        self.browser.getLink("200").click()
2465        self.browser.getLink("Edit course list").click()
2466        self.browser.getLink("here").click()
2467        self.browser.getControl(name="form.course").value = ['COURSE1']
2468        self.course.credits = 100
2469        self.browser.getControl("Add course ticket").click()
2470        self.assertMatches(
2471            '...Total credits exceed 50...', self.browser.contents)
2472        self.course.credits = 10
2473        self.browser.getControl("Add course ticket").click()
2474        self.assertMatches('...The ticket exists...',
2475                           self.browser.contents)
2476        # Indeed the ticket exists as carry-over course from level 100
2477        # since its score was 0
2478        self.assertTrue(
2479            self.student['studycourse']['200']['COURSE1'].carry_over is True)
2480        # Students can open the pdf course registration slip
2481        self.browser.open(self.student_path + '/studycourse/200')
2482        self.browser.getLink("Download course registration slip").click()
2483        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2484        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2485        # Students can remove course tickets
2486        self.browser.open(self.student_path + '/studycourse/200/edit')
2487        self.browser.getControl("Remove selected", index=0).click()
2488        self.assertTrue('No ticket selected' in self.browser.contents)
2489        # No ticket can be selected since the carry-over course is a core course
2490        self.assertRaises(
2491            LookupError, self.browser.getControl, name='val_id')
2492        self.student['studycourse']['200']['COURSE1'].mandatory = False
2493        self.browser.open(self.student_path + '/studycourse/200/edit')
2494        # Course list can't be registered if total_credits exceeds max_credits
2495        self.student['studycourse']['200']['COURSE1'].credits = 60
2496        self.browser.getControl("Register course list").click()
2497        self.assertTrue('Maximum credits of 50 exceeded' in self.browser.contents)
2498        # Student can now remove the ticket
2499        ctrl = self.browser.getControl(name='val_id')
2500        ctrl.getControl(value='COURSE1').selected = True
2501        self.browser.getControl("Remove selected", index=0).click()
2502        self.assertTrue('Successfully removed' in self.browser.contents)
2503        # Removing course tickets is properly logged
2504        logfile = os.path.join(
2505            self.app['datacenter'].storage, 'logs', 'students.log')
2506        logcontent = open(logfile).read()
2507        self.assertTrue('K1000000 - students.browser.StudyLevelEditFormPage '
2508        '- K1000000 - removed: COURSE1 at 200' in logcontent)
2509        # They can add the same ticket using the edit page directly.
2510        # We can do the same by adding the course on the manage page directly
2511        self.browser.getControl(name="course").value = 'COURSE1'
2512        self.browser.getControl("Add course ticket").click()
2513        # Adding course tickets is logged
2514        logfile = os.path.join(
2515            self.app['datacenter'].storage, 'logs', 'students.log')
2516        logcontent = open(logfile).read()
2517        self.assertTrue('K1000000 - students.browser.StudyLevelEditFormPage - '
2518            'K1000000 - added: COURSE1|200|2004' in logcontent)
2519        # Course list can be registered
2520        self.browser.getControl("Register course list").click()
2521        self.assertTrue('Course list has been registered' in self.browser.contents)
2522        self.assertEqual(self.student.state, 'courses registered')
2523        # Students can view the transcript
2524        #self.browser.open(self.studycourse_path)
2525        #self.browser.getLink("Transcript").click()
2526        #self.browser.getLink("Academic Transcript").click()
2527        #self.assertEqual(self.browser.headers['Status'], '200 Ok')
2528        #self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2529        return
2530
2531    def test_postgraduate_student_access(self):
2532        self.certificate.study_mode = 'pg_ft'
2533        self.certificate.start_level = 999
2534        self.certificate.end_level = 999
2535        self.student['studycourse'].current_level = 999
2536        IWorkflowState(self.student).setState('school fee paid')
2537        self.browser.open(self.login_path)
2538        self.browser.getControl(name="form.login").value = self.student_id
2539        self.browser.getControl(name="form.password").value = 'spwd'
2540        self.browser.getControl("Login").click()
2541        self.assertTrue(
2542            'You logged in.' in self.browser.contents)
2543        # Now students can add the current study level
2544        self.browser.getLink("Study Course").click()
2545        self.browser.getLink("Add course list").click()
2546        self.assertMatches('...Add current level Postgraduate Level...',
2547                           self.browser.contents)
2548        self.browser.getControl("Create course list now").click()
2549        # A level with one course ticket was created
2550        self.assertEqual(self.student['studycourse']['999'].number_of_tickets, 0)
2551        self.browser.getLink("999").click()
2552        self.browser.getLink("Edit course list").click()
2553        self.browser.getLink("here").click()
2554        self.browser.getControl(name="form.course").value = ['COURSE1']
2555        self.browser.getControl("Add course ticket").click()
2556        self.assertMatches('...Successfully added COURSE1...',
2557                           self.browser.contents)
2558        # Postgraduate students can't register course lists
2559        self.browser.getControl("Register course list").click()
2560        self.assertTrue("your course list can't bee registered"
2561            in self.browser.contents)
2562        self.assertEqual(self.student.state, 'school fee paid')
2563        return
2564
2565    def test_student_clearance_wo_clrcode(self):
2566        IWorkflowState(self.student).setState('clearance started')
2567        self.browser.open(self.login_path)
2568        self.browser.getControl(name="form.login").value = self.student_id
2569        self.browser.getControl(name="form.password").value = 'spwd'
2570        self.browser.getControl("Login").click()
2571        self.student.clearance_locked = False
2572        self.browser.open(self.edit_clearance_path)
2573        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
2574        self.browser.getControl("Save and request clearance").click()
2575        self.assertMatches('...Clearance has been requested...',
2576                           self.browser.contents)
2577
2578    def test_student_clearance_payment(self):
2579        # Login
2580        self.browser.open(self.login_path)
2581        self.browser.getControl(name="form.login").value = self.student_id
2582        self.browser.getControl(name="form.password").value = 'spwd'
2583        self.browser.getControl("Login").click()
2584
2585        # Students can add online clearance payment tickets
2586        self.browser.open(self.payments_path + '/addop')
2587        self.browser.getControl(name="form.p_category").value = ['clearance']
2588        self.browser.getControl("Create ticket").click()
2589        self.assertMatches('...ticket created...',
2590                           self.browser.contents)
2591
2592        # Students can't approve the payment
2593        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
2594        ctrl = self.browser.getControl(name='val_id')
2595        value = ctrl.options[0]
2596        self.browser.getLink(value).click()
2597        payment_url = self.browser.url
2598        self.assertRaises(
2599            Unauthorized, self.browser.open, payment_url + '/approve')
2600        # In the base package they can 'use' a fake approval view.
2601        # XXX: I tried to use
2602        # self.student['payments'][value].approveStudentPayment() instead.
2603        # But this function fails in
2604        # w.k.accesscodes.accesscode.create_accesscode.
2605        # grok.getSite returns None in tests.
2606        self.browser.open(payment_url + '/fake_approve')
2607        self.assertMatches('...Payment approved...',
2608                          self.browser.contents)
2609        expected = '''...
2610        <td>
2611          <span>Paid</span>
2612        </td>...'''
2613        expected = '''...
2614        <td>
2615          <span>Paid</span>
2616        </td>...'''
2617        self.assertMatches(expected,self.browser.contents)
2618        payment_id = self.student['payments'].keys()[0]
2619        payment = self.student['payments'][payment_id]
2620        self.assertEqual(payment.p_state, 'paid')
2621        self.assertEqual(payment.r_amount_approved, 3456.0)
2622        self.assertEqual(payment.r_code, 'AP')
2623        self.assertEqual(payment.r_desc, u'Payment approved by Anna Tester')
2624        # The new CLR-0 pin has been created
2625        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
2626        pin = self.app['accesscodes']['CLR-0'].keys()[0]
2627        ac = self.app['accesscodes']['CLR-0'][pin]
2628        self.assertEqual(ac.owner, self.student_id)
2629        self.assertEqual(ac.cost, 3456.0)
2630
2631        # Students can open the pdf payment slip
2632        self.browser.open(payment_url + '/payment_slip.pdf')
2633        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2634        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2635
2636        # The new CLR-0 pin can be used for starting clearance
2637        # but they have to upload a passport picture first
2638        # which is only possible in state admitted
2639        self.browser.open(self.student_path + '/change_portrait')
2640        self.assertMatches('...form is locked...',
2641                          self.browser.contents)
2642        IWorkflowInfo(self.student).fireTransition('admit')
2643        self.browser.open(self.student_path + '/change_portrait')
2644        image = open(SAMPLE_IMAGE, 'rb')
2645        ctrl = self.browser.getControl(name='passportuploadedit')
2646        file_ctrl = ctrl.mech_control
2647        file_ctrl.add_file(image, filename='my_photo.jpg')
2648        self.browser.getControl(
2649            name='upload_passportuploadedit').click()
2650        self.browser.open(self.student_path + '/start_clearance')
2651        parts = pin.split('-')[1:]
2652        clrseries, clrnumber = parts
2653        self.browser.getControl(name="ac_series").value = clrseries
2654        self.browser.getControl(name="ac_number").value = clrnumber
2655        self.browser.getControl("Start clearance now").click()
2656        self.assertMatches('...Clearance process has been started...',
2657                           self.browser.contents)
2658
2659    def test_student_schoolfee_payment(self):
2660        configuration = createObject('waeup.SessionConfiguration')
2661        configuration.academic_session = 2005
2662        self.app['configuration'].addSessionConfiguration(configuration)
2663        # Login
2664        self.browser.open(self.login_path)
2665        self.browser.getControl(name="form.login").value = self.student_id
2666        self.browser.getControl(name="form.password").value = 'spwd'
2667        self.browser.getControl("Login").click()
2668
2669        # Students can add online school fee payment tickets.
2670        IWorkflowState(self.student).setState('returning')
2671        self.browser.open(self.payments_path)
2672        self.assertRaises(
2673            LookupError, self.browser.getControl, name='val_id')
2674        self.browser.getLink("Add current session payment ticket").click()
2675        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2676        self.browser.getControl("Create ticket").click()
2677        self.assertMatches('...ticket created...',
2678                           self.browser.contents)
2679        ctrl = self.browser.getControl(name='val_id')
2680        value = ctrl.options[0]
2681        self.browser.getLink(value).click()
2682        self.assertMatches('...Amount Authorized...',
2683                           self.browser.contents)
2684        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2685        # Payment session and will be calculated as defined
2686        # in w.k.students.utils because we set changed the state
2687        # to returning
2688        self.assertEqual(self.student['payments'][value].p_session, 2005)
2689        self.assertEqual(self.student['payments'][value].p_level, 200)
2690
2691        # Student is the payer of the payment ticket.
2692        payer = IPayer(self.student['payments'][value])
2693        self.assertEqual(payer.display_fullname, 'Anna Tester')
2694        self.assertEqual(payer.id, self.student_id)
2695        self.assertEqual(payer.faculty, 'fac1')
2696        self.assertEqual(payer.department, 'dep1')
2697
2698        # We simulate the approval
2699        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
2700        self.browser.open(self.browser.url + '/fake_approve')
2701        self.assertMatches('...Payment approved...',
2702                          self.browser.contents)
2703
2704        # The new SFE-0 pin can be used for starting new session
2705        self.browser.open(self.studycourse_path)
2706        self.browser.getLink('Start new session').click()
2707        pin = self.app['accesscodes']['SFE-0'].keys()[0]
2708        parts = pin.split('-')[1:]
2709        sfeseries, sfenumber = parts
2710        self.browser.getControl(name="ac_series").value = sfeseries
2711        self.browser.getControl(name="ac_number").value = sfenumber
2712        self.browser.getControl("Start now").click()
2713        self.assertMatches('...Session started...',
2714                           self.browser.contents)
2715        self.assertTrue(self.student.state == 'school fee paid')
2716        return
2717
2718    def test_student_bedallocation_payment(self):
2719        # Login
2720        self.browser.open(self.login_path)
2721        self.browser.getControl(name="form.login").value = self.student_id
2722        self.browser.getControl(name="form.password").value = 'spwd'
2723        self.browser.getControl("Login").click()
2724        self.browser.open(self.payments_path)
2725        self.browser.open(self.payments_path + '/addop')
2726        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
2727        self.browser.getControl("Create ticket").click()
2728        self.assertMatches('...ticket created...',
2729                           self.browser.contents)
2730        # Students can remove only online payment tickets which have
2731        # not received a valid callback
2732        self.browser.open(self.payments_path)
2733        ctrl = self.browser.getControl(name='val_id')
2734        value = ctrl.options[0]
2735        ctrl.getControl(value=value).selected = True
2736        self.browser.getControl("Remove selected", index=0).click()
2737        self.assertTrue('Successfully removed' in self.browser.contents)
2738
2739    def test_student_maintenance_payment(self):
2740        # Login
2741        self.browser.open(self.login_path)
2742        self.browser.getControl(name="form.login").value = self.student_id
2743        self.browser.getControl(name="form.password").value = 'spwd'
2744        self.browser.getControl("Login").click()
2745        self.browser.open(self.payments_path)
2746        self.browser.open(self.payments_path + '/addop')
2747        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
2748        self.browser.getControl("Create ticket").click()
2749        self.assertMatches('...You have not yet booked accommodation...',
2750                           self.browser.contents)
2751        # We continue this test in test_student_accommodation
2752
2753    def test_student_previous_payments(self):
2754        configuration = createObject('waeup.SessionConfiguration')
2755        configuration.academic_session = 2000
2756        configuration.clearance_fee = 3456.0
2757        configuration.booking_fee = 123.4
2758        self.app['configuration'].addSessionConfiguration(configuration)
2759        configuration2 = createObject('waeup.SessionConfiguration')
2760        configuration2.academic_session = 2003
2761        configuration2.clearance_fee = 3456.0
2762        configuration2.booking_fee = 123.4
2763        self.app['configuration'].addSessionConfiguration(configuration2)
2764        configuration3 = createObject('waeup.SessionConfiguration')
2765        configuration3.academic_session = 2005
2766        configuration3.clearance_fee = 3456.0
2767        configuration3.booking_fee = 123.4
2768        self.app['configuration'].addSessionConfiguration(configuration3)
2769        self.student['studycourse'].entry_session = 2002
2770
2771        # Login
2772        self.browser.open(self.login_path)
2773        self.browser.getControl(name="form.login").value = self.student_id
2774        self.browser.getControl(name="form.password").value = 'spwd'
2775        self.browser.getControl("Login").click()
2776
2777        # Students can add previous school fee payment tickets in any state.
2778        IWorkflowState(self.student).setState('courses registered')
2779        self.browser.open(self.payments_path)
2780        self.browser.getLink("Add previous session payment ticket").click()
2781
2782        # Previous session payment form is provided
2783        self.assertEqual(self.student.current_session, 2004)
2784        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2785        self.browser.getControl(name="form.p_session").value = ['2000']
2786        self.browser.getControl(name="form.p_level").value = ['300']
2787        self.browser.getControl("Create ticket").click()
2788        self.assertMatches('...The previous session must not fall below...',
2789                           self.browser.contents)
2790        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2791        self.browser.getControl(name="form.p_session").value = ['2005']
2792        self.browser.getControl(name="form.p_level").value = ['300']
2793        self.browser.getControl("Create ticket").click()
2794        self.assertMatches('...This is not a previous session...',
2795                           self.browser.contents)
2796        # Students can pay current session school fee.
2797        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2798        self.browser.getControl(name="form.p_session").value = ['2004']
2799        self.browser.getControl(name="form.p_level").value = ['300']
2800        self.browser.getControl("Create ticket").click()
2801        self.assertMatches('...ticket created...',
2802                           self.browser.contents)
2803        ctrl = self.browser.getControl(name='val_id')
2804        value = ctrl.options[0]
2805        self.browser.getLink(value).click()
2806        self.assertMatches('...Amount Authorized...',
2807                           self.browser.contents)
2808        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2809
2810        # Payment session is properly set
2811        self.assertEqual(self.student['payments'][value].p_session, 2004)
2812        self.assertEqual(self.student['payments'][value].p_level, 300)
2813
2814        # We simulate the approval
2815        self.browser.open(self.browser.url + '/fake_approve')
2816        self.assertMatches('...Payment approved...',
2817                          self.browser.contents)
2818
2819        # No AC has been created
2820        self.assertEqual(len(self.app['accesscodes']['SFE-0'].keys()), 0)
2821        self.assertTrue(self.student['payments'][value].ac is None)
2822
2823        # Current payment flag is set False
2824        self.assertFalse(self.student['payments'][value].p_current)
2825
2826        # Button and form are not available for students who are in
2827        # states up to cleared
2828        self.student['studycourse'].entry_session = 2004
2829        IWorkflowState(self.student).setState('cleared')
2830        self.browser.open(self.payments_path)
2831        self.assertFalse(
2832            "Add previous session payment ticket" in self.browser.contents)
2833        self.browser.open(self.payments_path + '/addpp')
2834        self.assertTrue(
2835            "No previous payment to be made" in self.browser.contents)
2836        return
2837
2838    def test_postgraduate_student_payments(self):
2839        configuration = createObject('waeup.SessionConfiguration')
2840        configuration.academic_session = 2005
2841        self.app['configuration'].addSessionConfiguration(configuration)
2842        self.certificate.study_mode = 'pg_ft'
2843        self.certificate.start_level = 999
2844        self.certificate.end_level = 999
2845        self.student['studycourse'].current_level = 999
2846        # Login
2847        self.browser.open(self.login_path)
2848        self.browser.getControl(name="form.login").value = self.student_id
2849        self.browser.getControl(name="form.password").value = 'spwd'
2850        self.browser.getControl("Login").click()
2851        # Students can add online school fee payment tickets.
2852        IWorkflowState(self.student).setState('cleared')
2853        self.browser.open(self.payments_path)
2854        self.browser.getLink("Add current session payment ticket").click()
2855        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2856        self.browser.getControl("Create ticket").click()
2857        self.assertMatches('...ticket created...',
2858                           self.browser.contents)
2859        ctrl = self.browser.getControl(name='val_id')
2860        value = ctrl.options[0]
2861        self.browser.getLink(value).click()
2862        self.assertMatches('...Amount Authorized...',
2863                           self.browser.contents)
2864        # Payment session and level are current ones.
2865        # Postgrads have to pay school_fee_1.
2866        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
2867        self.assertEqual(self.student['payments'][value].p_session, 2004)
2868        self.assertEqual(self.student['payments'][value].p_level, 999)
2869
2870        # We simulate the approval
2871        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
2872        self.browser.open(self.browser.url + '/fake_approve')
2873        self.assertMatches('...Payment approved...',
2874                          self.browser.contents)
2875
2876        # The new SFE-0 pin can be used for starting session
2877        self.browser.open(self.studycourse_path)
2878        self.browser.getLink('Start new session').click()
2879        pin = self.app['accesscodes']['SFE-0'].keys()[0]
2880        parts = pin.split('-')[1:]
2881        sfeseries, sfenumber = parts
2882        self.browser.getControl(name="ac_series").value = sfeseries
2883        self.browser.getControl(name="ac_number").value = sfenumber
2884        self.browser.getControl("Start now").click()
2885        self.assertMatches('...Session started...',
2886                           self.browser.contents)
2887        self.assertTrue(self.student.state == 'school fee paid')
2888
2889        # Postgrad students do not need to register courses the
2890        # can just pay for the next session.
2891        self.browser.open(self.payments_path)
2892        # Remove first payment to be sure that we access the right ticket
2893        del self.student['payments'][value]
2894        self.browser.getLink("Add current session payment ticket").click()
2895        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2896        self.browser.getControl("Create ticket").click()
2897        ctrl = self.browser.getControl(name='val_id')
2898        value = ctrl.options[0]
2899        self.browser.getLink(value).click()
2900        # Payment session has increased by one, payment level remains the same.
2901        # Returning Postgraduates have to pay school_fee_2.
2902        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2903        self.assertEqual(self.student['payments'][value].p_session, 2005)
2904        self.assertEqual(self.student['payments'][value].p_level, 999)
2905
2906        # Student is still in old session
2907        self.assertEqual(self.student.current_session, 2004)
2908
2909        # We do not need to pay the ticket if any other
2910        # SFE pin is provided
2911        pin_container = self.app['accesscodes']
2912        pin_container.createBatch(
2913            datetime.utcnow(), 'some_userid', 'SFE', 9.99, 1)
2914        pin = pin_container['SFE-1'].values()[0].representation
2915        sfeseries, sfenumber = pin.split('-')[1:]
2916        # The new SFE-1 pin can be used for starting new session
2917        self.browser.open(self.studycourse_path)
2918        self.browser.getLink('Start new session').click()
2919        self.browser.getControl(name="ac_series").value = sfeseries
2920        self.browser.getControl(name="ac_number").value = sfenumber
2921        self.browser.getControl("Start now").click()
2922        self.assertMatches('...Session started...',
2923                           self.browser.contents)
2924        self.assertTrue(self.student.state == 'school fee paid')
2925        # Student is in new session
2926        self.assertEqual(self.student.current_session, 2005)
2927        self.assertEqual(self.student['studycourse'].current_level, 999)
2928        return
2929
2930    def test_student_accommodation(self):
2931        # Login
2932        self.browser.open(self.login_path)
2933        self.browser.getControl(name="form.login").value = self.student_id
2934        self.browser.getControl(name="form.password").value = 'spwd'
2935        self.browser.getControl("Login").click()
2936
2937        # Students can add online booking fee payment tickets and open the
2938        # callback view (see test_manage_payments)
2939        self.browser.getLink("Payments").click()
2940        self.browser.getLink("Add current session payment ticket").click()
2941        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
2942        self.browser.getControl("Create ticket").click()
2943        ctrl = self.browser.getControl(name='val_id')
2944        value = ctrl.options[0]
2945        self.browser.getLink(value).click()
2946        self.browser.open(self.browser.url + '/fake_approve')
2947        # The new HOS-0 pin has been created
2948        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
2949        pin = self.app['accesscodes']['HOS-0'].keys()[0]
2950        ac = self.app['accesscodes']['HOS-0'][pin]
2951        parts = pin.split('-')[1:]
2952        sfeseries, sfenumber = parts
2953
2954        # Students can use HOS code and book a bed space with it ...
2955        self.browser.open(self.acco_path)
2956        # ... but not if booking period has expired ...
2957        self.app['hostels'].enddate = datetime.now(pytz.utc)
2958        self.browser.getLink("Book accommodation").click()
2959        self.assertMatches('...Outside booking period: ...',
2960                           self.browser.contents)
2961        self.app['hostels'].enddate = datetime.now(pytz.utc) + timedelta(days=10)
2962        # ... or student is not the an allowed state ...
2963        self.browser.getLink("Book accommodation").click()
2964        self.assertMatches('...You are in the wrong...',
2965                           self.browser.contents)
2966        IWorkflowInfo(self.student).fireTransition('admit')
2967        self.browser.getLink("Book accommodation").click()
2968        self.assertMatches('...Activation Code:...',
2969                           self.browser.contents)
2970        # Student can't used faked ACs ...
2971        self.browser.getControl(name="ac_series").value = u'nonsense'
2972        self.browser.getControl(name="ac_number").value = sfenumber
2973        self.browser.getControl("Create bed ticket").click()
2974        self.assertMatches('...Activation code is invalid...',
2975                           self.browser.contents)
2976        # ... or ACs owned by somebody else.
2977        ac.owner = u'Anybody'
2978        self.browser.getControl(name="ac_series").value = sfeseries
2979        self.browser.getControl(name="ac_number").value = sfenumber
2980        self.browser.getControl("Create bed ticket").click()
2981        self.assertMatches('...You are not the owner of this access code...',
2982                           self.browser.contents)
2983        ac.owner = self.student_id
2984        self.browser.getControl(name="ac_series").value = sfeseries
2985        self.browser.getControl(name="ac_number").value = sfenumber
2986        self.browser.getControl("Create bed ticket").click()
2987        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
2988                           self.browser.contents)
2989
2990        # Bed has been allocated
2991        bed = self.app['hostels']['hall-1']['hall-1_A_101_A']
2992        self.assertTrue(bed.owner == self.student_id)
2993
2994        # BedTicketAddPage is now blocked
2995        self.browser.getLink("Book accommodation").click()
2996        self.assertMatches('...You already booked a bed space...',
2997            self.browser.contents)
2998
2999        # The bed ticket displays the data correctly
3000        self.browser.open(self.acco_path + '/2004')
3001        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
3002                           self.browser.contents)
3003        self.assertMatches('...2004/2005...', self.browser.contents)
3004        self.assertMatches('...regular_male_fr...', self.browser.contents)
3005        self.assertMatches('...%s...' % pin, self.browser.contents)
3006
3007        # Students can open the pdf slip
3008        self.browser.open(self.browser.url + '/bed_allocation_slip.pdf')
3009        self.assertEqual(self.browser.headers['Status'], '200 Ok')
3010        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
3011
3012        # Students can't relocate themselves
3013        self.assertFalse('Relocate' in self.browser.contents)
3014        relocate_path = self.acco_path + '/2004/relocate'
3015        self.assertRaises(
3016            Unauthorized, self.browser.open, relocate_path)
3017
3018        # Students can't the Remove button and check boxes
3019        self.browser.open(self.acco_path)
3020        self.assertFalse('Remove' in self.browser.contents)
3021        self.assertFalse('val_id' in self.browser.contents)
3022
3023        # Students can pay maintenance fee now
3024        self.browser.open(self.payments_path)
3025        self.browser.open(self.payments_path + '/addop')
3026        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
3027        self.browser.getControl("Create ticket").click()
3028        self.assertMatches('...Payment ticket created...',
3029                           self.browser.contents)
3030
3031        ctrl = self.browser.getControl(name='val_id')
3032        value = ctrl.options[0]
3033        # Maintennace fee is taken from the hostel object
3034        self.assertEqual(self.student['payments'][value].amount_auth, 876.0)
3035        # If the hostel's maintenance fee isn't set, the fee is
3036        # taken from the session configuration object.
3037        self.app['hostels']['hall-1'].maint_fee = 0.0
3038        self.browser.open(self.payments_path + '/addop')
3039        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
3040        self.browser.getControl("Create ticket").click()
3041        ctrl = self.browser.getControl(name='val_id')
3042        value = ctrl.options[1]
3043        self.assertEqual(self.student['payments'][value].amount_auth, 987.0)
3044        return
3045
3046    def test_change_password_request(self):
3047        self.browser.open('http://localhost/app/changepw')
3048        self.browser.getControl(name="form.identifier").value = '123'
3049        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
3050        self.browser.getControl("Send login credentials").click()
3051        self.assertTrue('An email with' in self.browser.contents)
3052
3053    def test_student_expired_personal_data(self):
3054        # Login
3055        IWorkflowState(self.student).setState('school fee paid')
3056        delta = timedelta(days=180)
3057        self.student.personal_updated = datetime.utcnow() - delta
3058        self.browser.open(self.login_path)
3059        self.browser.getControl(name="form.login").value = self.student_id
3060        self.browser.getControl(name="form.password").value = 'spwd'
3061        self.browser.getControl("Login").click()
3062        self.assertEqual(self.browser.url, self.student_path)
3063        self.assertTrue(
3064            'You logged in' in self.browser.contents)
3065        # Students don't see personal_updated field in edit form
3066        self.browser.open(self.edit_personal_path)
3067        self.assertFalse('Updated' in self.browser.contents)
3068        self.browser.open(self.personal_path)
3069        self.assertTrue('Updated' in self.browser.contents)
3070        self.browser.getLink("Logout").click()
3071        delta = timedelta(days=181)
3072        self.student.personal_updated = datetime.utcnow() - delta
3073        self.browser.open(self.login_path)
3074        self.browser.getControl(name="form.login").value = self.student_id
3075        self.browser.getControl(name="form.password").value = 'spwd'
3076        self.browser.getControl("Login").click()
3077        self.assertEqual(self.browser.url, self.edit_personal_path)
3078        self.assertTrue(
3079            'Your personal data record is outdated.' in self.browser.contents)
3080
3081    def test_request_transcript(self):
3082        IWorkflowState(self.student).setState('graduated')
3083        self.browser.open(self.login_path)
3084        self.browser.getControl(name="form.login").value = self.student_id
3085        self.browser.getControl(name="form.password").value = 'spwd'
3086        self.browser.getControl("Login").click()
3087        self.assertMatches(
3088            '...You logged in...', self.browser.contents)
3089        # Create payment ticket
3090        self.browser.open(self.payments_path)
3091        self.browser.open(self.payments_path + '/addop')
3092        self.browser.getControl(name="form.p_category").value = ['transcript']
3093        self.browser.getControl("Create ticket").click()
3094        ctrl = self.browser.getControl(name='val_id')
3095        value = ctrl.options[0]
3096        self.browser.getLink(value).click()
3097        self.assertMatches('...Amount Authorized...',
3098                           self.browser.contents)
3099        self.assertEqual(self.student['payments'][value].amount_auth, 4567.0)
3100        # Student is the payer of the payment ticket.
3101        payer = IPayer(self.student['payments'][value])
3102        self.assertEqual(payer.display_fullname, 'Anna Tester')
3103        self.assertEqual(payer.id, self.student_id)
3104        self.assertEqual(payer.faculty, 'fac1')
3105        self.assertEqual(payer.department, 'dep1')
3106        # We simulate the approval and fetch the pin
3107        self.assertEqual(len(self.app['accesscodes']['TSC-0']),0)
3108        self.browser.open(self.browser.url + '/fake_approve')
3109        self.assertMatches('...Payment approved...',
3110                          self.browser.contents)
3111        pin = self.app['accesscodes']['TSC-0'].keys()[0]
3112        parts = pin.split('-')[1:]
3113        tscseries, tscnumber = parts
3114        # Student can use the pin to send the transcript request
3115        self.browser.open(self.student_path)
3116        self.browser.getLink("Request transcript").click()
3117        self.browser.getControl(name="ac_series").value = tscseries
3118        self.browser.getControl(name="ac_number").value = tscnumber
3119        self.browser.getControl(name="comment").value = 'Comment line 1 \nComment line2'
3120        self.browser.getControl(name="address").value = 'Address line 1 \nAddress line2'
3121        self.browser.getControl("Submit").click()
3122        self.assertMatches('...Transcript processing has been started...',
3123                          self.browser.contents)
3124        self.assertEqual(self.student.state, 'transcript requested')
3125        self.assertMatches(
3126            '... UTC K1000000 wrote:\n\nComment line 1 \n'
3127            'Comment line2\n\nDispatch Address:\nAddress line 1 \n'
3128            'Address line2\n\n', self.student.transcript_comment)
3129        # The comment has been logged
3130        logfile = os.path.join(
3131            self.app['datacenter'].storage, 'logs', 'students.log')
3132        logcontent = open(logfile).read()
3133        self.assertTrue(
3134            'K1000000 - students.browser.StudentTranscriptRequestPage - '
3135            'K1000000 - comment: Comment line 1 <br>Comment line2\n'
3136            in logcontent)
3137
3138class StudentRequestPWTests(StudentsFullSetup):
3139    # Tests for student registration
3140
3141    layer = FunctionalLayer
3142
3143    def test_request_pw(self):
3144        # Student with wrong number can't be found.
3145        self.browser.open('http://localhost/app/requestpw')
3146        self.browser.getControl(name="form.firstname").value = 'Anna'
3147        self.browser.getControl(name="form.number").value = 'anynumber'
3148        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
3149        self.browser.getControl("Send login credentials").click()
3150        self.assertTrue('No student record found.'
3151            in self.browser.contents)
3152        # Anonymous is not informed that firstname verification failed.
3153        # It seems that the record doesn't exist.
3154        self.browser.open('http://localhost/app/requestpw')
3155        self.browser.getControl(name="form.firstname").value = 'Johnny'
3156        self.browser.getControl(name="form.number").value = '123'
3157        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
3158        self.browser.getControl("Send login credentials").click()
3159        self.assertTrue('No student record found.'
3160            in self.browser.contents)
3161        # Even with the correct firstname we can't register if a
3162        # password has been set and used.
3163        self.browser.getControl(name="form.firstname").value = 'Anna'
3164        self.browser.getControl(name="form.number").value = '123'
3165        self.browser.getControl("Send login credentials").click()
3166        self.assertTrue('Your password has already been set and used.'
3167            in self.browser.contents)
3168        self.browser.open('http://localhost/app/requestpw')
3169        self.app['students'][self.student_id].password = None
3170        # The firstname field, used for verification, is not case-sensitive.
3171        self.browser.getControl(name="form.firstname").value = 'aNNa'
3172        self.browser.getControl(name="form.number").value = '123'
3173        self.browser.getControl(name="form.email").value = 'new@yy.zz'
3174        self.browser.getControl("Send login credentials").click()
3175        # Yeah, we succeded ...
3176        self.assertTrue('Your password request was successful.'
3177            in self.browser.contents)
3178        # We can also use the matric_number instead.
3179        self.browser.open('http://localhost/app/requestpw')
3180        self.browser.getControl(name="form.firstname").value = 'aNNa'
3181        self.browser.getControl(name="form.number").value = '234'
3182        self.browser.getControl(name="form.email").value = 'new@yy.zz'
3183        self.browser.getControl("Send login credentials").click()
3184        self.assertTrue('Your password request was successful.'
3185            in self.browser.contents)
3186        # ... and  student can be found in the catalog via the email address
3187        cat = queryUtility(ICatalog, name='students_catalog')
3188        results = list(
3189            cat.searchResults(
3190            email=('new@yy.zz', 'new@yy.zz')))
3191        self.assertEqual(self.student,results[0])
3192        logfile = os.path.join(
3193            self.app['datacenter'].storage, 'logs', 'main.log')
3194        logcontent = open(logfile).read()
3195        self.assertTrue('zope.anybody - students.browser.StudentRequestPasswordPage - '
3196                        '234 (K1000000) - new@yy.zz' in logcontent)
3197        return
3198
3199    def test_student_locked_level_forms(self):
3200
3201        # Add two study levels, one current and one previous
3202        studylevel = createObject(u'waeup.StudentStudyLevel')
3203        studylevel.level = 100
3204        self.student['studycourse'].addStudentStudyLevel(
3205            self.certificate, studylevel)
3206        studylevel = createObject(u'waeup.StudentStudyLevel')
3207        studylevel.level = 200
3208        self.student['studycourse'].addStudentStudyLevel(
3209            self.certificate, studylevel)
3210        IWorkflowState(self.student).setState('school fee paid')
3211        self.student['studycourse'].current_level = 200
3212
3213        self.browser.open(self.login_path)
3214        self.browser.getControl(name="form.login").value = self.student_id
3215        self.browser.getControl(name="form.password").value = 'spwd'
3216        self.browser.getControl("Login").click()
3217
3218        self.browser.open(self.student_path + '/studycourse/200/edit')
3219        self.assertFalse('The requested form is locked' in self.browser.contents)
3220        self.browser.open(self.student_path + '/studycourse/100/edit')
3221        self.assertTrue('The requested form is locked' in self.browser.contents)
3222
3223        self.browser.open(self.student_path + '/studycourse/200/ctadd')
3224        self.assertFalse('The requested form is locked' in self.browser.contents)
3225        self.browser.open(self.student_path + '/studycourse/100/ctadd')
3226        self.assertTrue('The requested form is locked' in self.browser.contents)
3227
3228        IWorkflowState(self.student).setState('courses registered')
3229        self.browser.open(self.student_path + '/studycourse/200/edit')
3230        self.assertTrue('The requested form is locked' in self.browser.contents)
3231        self.browser.open(self.student_path + '/studycourse/200/ctadd')
3232        self.assertTrue('The requested form is locked' in self.browser.contents)
3233
3234
3235class PublicPagesTests(StudentsFullSetup):
3236    # Tests for simple webservices
3237
3238    layer = FunctionalLayer
3239
3240    def test_paymentrequest(self):
3241        payment = createObject('waeup.StudentOnlinePayment')
3242        payment.p_category = u'schoolfee'
3243        payment.p_session = self.student.current_session
3244        payment.p_item = u'My Certificate'
3245        payment.p_id = u'anyid'
3246        self.student['payments']['anykey'] = payment
3247        # Request information about unpaid payment ticket
3248        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
3249        self.assertEqual(self.browser.contents, '-1')
3250        # Request information about paid payment ticket
3251        payment.p_state = u'paid'
3252        notify(grok.ObjectModifiedEvent(payment))
3253        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
3254        self.assertEqual(self.browser.contents,
3255            'FULL_NAME=Anna Tester&FACULTY=fac1&DEPARTMENT=dep1'
3256            '&PAYMENT_ITEM=My Certificate&PAYMENT_CATEGORY=School Fee'
3257            '&ACADEMIC_SESSION=2004/2005&MATRIC_NUMBER=234&REG_NUMBER=123'
3258            '&FEE_AMOUNT=0.0')
3259        self.browser.open('http://localhost/app/paymentrequest?NONSENSE=nonsense')
3260        self.assertEqual(self.browser.contents, '-1')
3261        self.browser.open('http://localhost/app/paymentrequest?P_ID=nonsense')
3262        self.assertEqual(self.browser.contents, '-1')
3263
3264class StudentDataExportTests(StudentsFullSetup, FunctionalAsyncTestCase):
3265    # Tests for StudentsContainer class views and pages
3266
3267    layer = FunctionalLayer
3268
3269    def wait_for_export_job_completed(self):
3270        # helper function waiting until the current export job is completed
3271        manager = getUtility(IJobManager)
3272        job_id = self.app['datacenter'].running_exports[0][0]
3273        job = manager.get(job_id)
3274        wait_for_result(job)
3275        return job_id
3276
3277    def test_datacenter_export(self):
3278        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3279        self.browser.open('http://localhost/app/datacenter/@@exportconfig')
3280        self.browser.getControl(name="exporter").value = ['bursary']
3281        self.browser.getControl(name="session").value = ['2004']
3282        self.browser.getControl(name="level").value = ['100']
3283        self.browser.getControl(name="mode").value = ['ug_ft']
3284        self.browser.getControl(name="payments_start").value = '13/12/2012'
3285        self.browser.getControl(name="payments_end").value = '14/12/2012'
3286        self.browser.getControl("Create CSV file").click()
3287
3288        # When the job is finished and we reload the page...
3289        job_id = self.wait_for_export_job_completed()
3290        # ... the csv file can be downloaded ...
3291        self.browser.open('http://localhost/app/datacenter/@@export')
3292        self.browser.getLink("Download").click()
3293        self.assertEqual(self.browser.headers['content-type'],
3294            'text/csv; charset=UTF-8')
3295        self.assertTrue(
3296            'filename="WAeUP.Kofa_bursary_%s.csv' % job_id in
3297            self.browser.headers['content-disposition'])
3298        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3299        job_id = self.app['datacenter'].running_exports[0][0]
3300        # ... and discarded
3301        self.browser.open('http://localhost/app/datacenter/@@export')
3302        self.browser.getControl("Discard").click()
3303        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3304        # Creation, downloading and discarding is logged
3305        logfile = os.path.join(
3306            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3307        logcontent = open(logfile).read()
3308        self.assertTrue(
3309            'zope.mgr - students.browser.DatacenterExportJobContainerJobConfig '
3310            '- exported: bursary (2004, 100, ug_ft, None, None, None, '
3311            '13/12/2012, 14/12/2012), job_id=%s'
3312            % job_id in logcontent
3313            )
3314        self.assertTrue(
3315            'zope.mgr - browser.pages.ExportCSVView '
3316            '- downloaded: WAeUP.Kofa_bursary_%s.csv, job_id=%s'
3317            % (job_id, job_id) in logcontent
3318            )
3319        self.assertTrue(
3320            'zope.mgr - browser.pages.ExportCSVPage '
3321            '- discarded: job_id=%s' % job_id in logcontent
3322            )
3323
3324    def test_datacenter_export_selected(self):
3325        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3326        self.browser.open('http://localhost/app/datacenter/@@exportselected')
3327        self.browser.getControl(name="exporter").value = ['students']
3328        self.browser.getControl(name="students").value = 'K1000000'
3329        self.browser.getControl("Create CSV file").click()
3330        # When the job is finished and we reload the page...
3331        job_id = self.wait_for_export_job_completed()
3332        # ... the csv file can be downloaded ...
3333        self.browser.open('http://localhost/app/datacenter/@@export')
3334        self.browser.getLink("Download").click()
3335        self.assertEqual(self.browser.headers['content-type'],
3336            'text/csv; charset=UTF-8')
3337        self.assertTrue(
3338            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3339            self.browser.headers['content-disposition'])
3340        self.assertTrue(
3341            'adm_code,clearance_locked,clr_code,date_of_birth,email,employer,'
3342            'firstname,lastname,matric_number,middlename,nationality,'
3343            'officer_comment,perm_address,personal_updated,phone,reg_number,'
3344            'sex,student_id,suspended,suspended_comment,transcript_comment,'
3345            'password,state,history,certcode,is_postgrad,current_level,'
3346            'current_session\r\n'
3347            ',1,,1981-02-04#,aa@aa.ng,,Anna,Tester,234,,,,,,'
3348            '1234#,123,m,K1000000,0,,,{SSHA}' in self.browser.contents)
3349
3350    def test_payment_dates(self):
3351        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3352        self.browser.open('http://localhost/app/datacenter/@@exportconfig')
3353        self.browser.getControl(name="exporter").value = ['bursary']
3354        self.browser.getControl(name="session").value = ['2004']
3355        self.browser.getControl(name="level").value = ['100']
3356        self.browser.getControl(name="mode").value = ['ug_ft']
3357        self.browser.getControl(name="payments_start").value = '13/12/2012'
3358        # If one payment date is missing, an error message appears
3359        self.browser.getControl(name="payments_end").value = ''
3360        self.browser.getControl("Create CSV file").click()
3361        self.assertTrue('Payment dates do not match format d/m/Y'
3362            in self.browser.contents)
3363
3364    def test_faculties_export(self):
3365        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3366        facs_path = 'http://localhost/app/faculties'
3367        self.browser.open(facs_path)
3368        self.browser.getLink("Export student data").click()
3369        self.browser.getControl("Configure new export").click()
3370        self.browser.getControl(name="exporter").value = ['bursary']
3371        self.browser.getControl(name="session").value = ['2004']
3372        self.browser.getControl(name="level").value = ['100']
3373        self.browser.getControl(name="mode").value = ['ug_ft']
3374        self.browser.getControl(name="payments_start").value = '13/12/2012'
3375        self.browser.getControl(name="payments_end").value = '14/12/2012'
3376        self.browser.getControl("Create CSV file").click()
3377
3378        # When the job is finished and we reload the page...
3379        job_id = self.wait_for_export_job_completed()
3380        self.browser.open(facs_path + '/exports')
3381        # ... the csv file can be downloaded ...
3382        self.browser.getLink("Download").click()
3383        self.assertEqual(self.browser.headers['content-type'],
3384            'text/csv; charset=UTF-8')
3385        self.assertTrue(
3386            'filename="WAeUP.Kofa_bursary_%s.csv' % job_id in
3387            self.browser.headers['content-disposition'])
3388        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3389        job_id = self.app['datacenter'].running_exports[0][0]
3390        # ... and discarded
3391        self.browser.open(facs_path + '/exports')
3392        self.browser.getControl("Discard").click()
3393        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3394        # Creation, downloading and discarding is logged
3395        logfile = os.path.join(
3396            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3397        logcontent = open(logfile).read()
3398        self.assertTrue(
3399            'zope.mgr - students.browser.FacultiesExportJobContainerJobConfig '
3400            '- exported: bursary (2004, 100, ug_ft, None, None, None, '
3401            '13/12/2012, 14/12/2012), job_id=%s'
3402            % job_id in logcontent
3403            )
3404        self.assertTrue(
3405            'zope.mgr - students.browser.ExportJobContainerDownload '
3406            '- downloaded: WAeUP.Kofa_bursary_%s.csv, job_id=%s'
3407            % (job_id, job_id) in logcontent
3408            )
3409        self.assertTrue(
3410            'zope.mgr - students.browser.ExportJobContainerOverview '
3411            '- discarded: job_id=%s' % job_id in logcontent
3412            )
3413
3414    def test_faculty_export(self):
3415        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3416        fac1_path = 'http://localhost/app/faculties/fac1'
3417        self.browser.open(fac1_path)
3418        self.browser.getLink("Export student data").click()
3419        self.browser.getControl("Configure new export").click()
3420        self.browser.getControl(name="exporter").value = ['students']
3421        self.browser.getControl(name="session").value = ['2004']
3422        self.browser.getControl(name="level").value = ['100']
3423        self.browser.getControl(name="mode").value = ['ug_ft']
3424        # The testbrowser does not hide the payment period fields, but
3425        # values are ignored when using the students exporter.
3426        self.browser.getControl(name="payments_start").value = '13/12/2012'
3427        self.browser.getControl(name="payments_end").value = '14/12/2012'
3428        self.browser.getControl("Create CSV file").click()
3429
3430        # When the job is finished and we reload the page...
3431        job_id = self.wait_for_export_job_completed()
3432        self.browser.open(fac1_path + '/exports')
3433        # ... the csv file can be downloaded ...
3434        self.browser.getLink("Download").click()
3435        self.assertEqual(self.browser.headers['content-type'],
3436            'text/csv; charset=UTF-8')
3437        self.assertTrue(
3438            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3439            self.browser.headers['content-disposition'])
3440        self.assertTrue(
3441            'adm_code,clearance_locked,clr_code,date_of_birth,email,employer,'
3442            'firstname,lastname,matric_number,middlename,nationality,'
3443            'officer_comment,perm_address,personal_updated,phone,reg_number,'
3444            'sex,student_id,suspended,suspended_comment,transcript_comment,'
3445            'password,state,history,certcode,is_postgrad,current_level,'
3446            'current_session\r\n'
3447            ',1,,1981-02-04#,aa@aa.ng,,Anna,Tester,234,,,,,,'
3448            '1234#,123,m,K1000000,0,,,{SSHA}' in self.browser.contents)
3449        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3450        job_id = self.app['datacenter'].running_exports[0][0]
3451        # ... and discarded
3452        self.browser.open(fac1_path + '/exports')
3453        self.browser.getControl("Discard").click()
3454        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3455        # Creation, downloading and discarding is logged
3456        logfile = os.path.join(
3457            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3458        logcontent = open(logfile).read()
3459        self.assertTrue(
3460            'zope.mgr - students.browser.FacultyExportJobContainerJobConfig '
3461            '- exported: students (2004, 100, ug_ft, fac1, None, None, '
3462            '13/12/2012, 14/12/2012), job_id=%s'
3463            % job_id in logcontent
3464            )
3465        self.assertTrue(
3466            'zope.mgr - students.browser.ExportJobContainerDownload '
3467            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3468            % (job_id, job_id) in logcontent
3469            )
3470        self.assertTrue(
3471            'zope.mgr - students.browser.ExportJobContainerOverview '
3472            '- discarded: job_id=%s' % job_id in logcontent
3473            )
3474
3475    def test_department_export(self):
3476        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3477        dep1_path = 'http://localhost/app/faculties/fac1/dep1'
3478        self.browser.open(dep1_path)
3479        self.browser.getLink("Export student data").click()
3480        self.browser.getControl("Configure new export").click()
3481        self.browser.getControl(name="exporter").value = ['students']
3482        self.browser.getControl(name="session").value = ['2004']
3483        self.browser.getControl(name="level").value = ['100']
3484        self.browser.getControl(name="mode").value = ['ug_ft']
3485        # The testbrowser does not hide the payment period fields, but
3486        # values are ignored when using the students exporter.
3487        self.browser.getControl(name="payments_start").value = '13/12/2012'
3488        self.browser.getControl(name="payments_end").value = '14/12/2012'
3489        self.browser.getControl("Create CSV file").click()
3490
3491        # When the job is finished and we reload the page...
3492        job_id = self.wait_for_export_job_completed()
3493        self.browser.open(dep1_path + '/exports')
3494        # ... the csv file can be downloaded ...
3495        self.browser.getLink("Download").click()
3496        self.assertEqual(self.browser.headers['content-type'],
3497            'text/csv; charset=UTF-8')
3498        self.assertTrue(
3499            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3500            self.browser.headers['content-disposition'])
3501        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3502        job_id = self.app['datacenter'].running_exports[0][0]
3503        # ... and discarded
3504        self.browser.open(dep1_path + '/exports')
3505        self.browser.getControl("Discard").click()
3506        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3507        # Creation, downloading and discarding is logged
3508        logfile = os.path.join(
3509            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3510        logcontent = open(logfile).read()
3511        self.assertTrue(
3512            'zope.mgr - students.browser.DepartmentExportJobContainerJobConfig '
3513            '- exported: students (2004, 100, ug_ft, None, dep1, None, '
3514            '13/12/2012, 14/12/2012), job_id=%s'
3515            % job_id in logcontent
3516            )
3517        self.assertTrue(
3518            'zope.mgr - students.browser.ExportJobContainerDownload '
3519            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3520            % (job_id, job_id) in logcontent
3521            )
3522        self.assertTrue(
3523            'zope.mgr - students.browser.ExportJobContainerOverview '
3524            '- discarded: job_id=%s' % job_id in logcontent
3525            )
3526
3527    def test_certificate_export(self):
3528        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3529        cert1_path = 'http://localhost/app/faculties/fac1/dep1/certificates/CERT1'
3530        self.browser.open(cert1_path)
3531        self.browser.getLink("Export student data").click()
3532        self.browser.getControl("Configure new export").click()
3533        self.browser.getControl(name="exporter").value = ['students']
3534        self.browser.getControl(name="session").value = ['2004']
3535        self.browser.getControl(name="level").value = ['100']
3536        self.browser.getControl("Create CSV file").click()
3537
3538        # When the job is finished and we reload the page...
3539        job_id = self.wait_for_export_job_completed()
3540        self.browser.open(cert1_path + '/exports')
3541        # ... the csv file can be downloaded ...
3542        self.browser.getLink("Download").click()
3543        self.assertEqual(self.browser.headers['content-type'],
3544            'text/csv; charset=UTF-8')
3545        self.assertTrue(
3546            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3547            self.browser.headers['content-disposition'])
3548        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3549        job_id = self.app['datacenter'].running_exports[0][0]
3550        # ... and discarded
3551        self.browser.open(cert1_path + '/exports')
3552        self.browser.getControl("Discard").click()
3553        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3554        # Creation, downloading and discarding is logged
3555        logfile = os.path.join(
3556            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3557        logcontent = open(logfile).read()
3558        self.assertTrue(
3559            'zope.mgr - students.browser.CertificateExportJobContainerJobConfig '
3560            '- exported: students (2004, 100, None, None, None, CERT1, None, None), '
3561            'job_id=%s'
3562            % job_id in logcontent
3563            )
3564        self.assertTrue(
3565            'zope.mgr - students.browser.ExportJobContainerDownload '
3566            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3567            % (job_id, job_id) in logcontent
3568            )
3569        self.assertTrue(
3570            'zope.mgr - students.browser.ExportJobContainerOverview '
3571            '- discarded: job_id=%s' % job_id in logcontent
3572            )
3573
3574    def test_course_export_students(self):
3575        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3576        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
3577        self.browser.open(course1_path)
3578        self.browser.getLink("Export student data").click()
3579        self.browser.getControl("Configure new export").click()
3580        self.browser.getControl(name="exporter").value = ['students']
3581        self.browser.getControl(name="session").value = ['2004']
3582        self.browser.getControl(name="level").value = ['100']
3583        self.browser.getControl("Create CSV file").click()
3584
3585        # When the job is finished and we reload the page...
3586        job_id = self.wait_for_export_job_completed()
3587        self.browser.open(course1_path + '/exports')
3588        # ... the csv file can be downloaded ...
3589        self.browser.getLink("Download").click()
3590        self.assertEqual(self.browser.headers['content-type'],
3591            'text/csv; charset=UTF-8')
3592        self.assertTrue(
3593            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3594            self.browser.headers['content-disposition'])
3595        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3596        job_id = self.app['datacenter'].running_exports[0][0]
3597        # ... and discarded
3598        self.browser.open(course1_path + '/exports')
3599        self.browser.getControl("Discard").click()
3600        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3601        # Creation, downloading and discarding is logged
3602        logfile = os.path.join(
3603            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3604        logcontent = open(logfile).read()
3605        self.assertTrue(
3606            'zope.mgr - students.browser.CourseExportJobContainerJobConfig '
3607            '- exported: students (2004, 100, COURSE1), job_id=%s'
3608            % job_id in logcontent
3609            )
3610        self.assertTrue(
3611            'zope.mgr - students.browser.ExportJobContainerDownload '
3612            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3613            % (job_id, job_id) in logcontent
3614            )
3615        self.assertTrue(
3616            'zope.mgr - students.browser.ExportJobContainerOverview '
3617            '- discarded: job_id=%s' % job_id in logcontent
3618            )
3619
3620    def test_course_export_coursetickets(self):
3621        # We add study level 100 to the student's studycourse
3622        studylevel = StudentStudyLevel()
3623        studylevel.level = 100
3624        studylevel.level_session = 2004
3625        self.student['studycourse'].addStudentStudyLevel(
3626            self.certificate,studylevel)
3627        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3628        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
3629        self.browser.open(course1_path)
3630        self.browser.getLink("Export student data").click()
3631        self.browser.getControl("Configure new export").click()
3632        self.browser.getControl(name="exporter").value = ['coursetickets']
3633        self.browser.getControl(name="session").value = ['2004']
3634        self.browser.getControl(name="level").value = ['100']
3635        self.browser.getControl("Create CSV file").click()
3636        # When the job is finished and we reload the page...
3637        job_id = self.wait_for_export_job_completed()
3638        self.browser.open(course1_path + '/exports')
3639        # ... the csv file can be downloaded ...
3640        self.browser.getLink("Download").click()
3641        self.assertEqual(self.browser.headers['content-type'],
3642            'text/csv; charset=UTF-8')
3643        self.assertTrue(
3644            'filename="WAeUP.Kofa_coursetickets_%s.csv' % job_id in
3645            self.browser.headers['content-disposition'])
3646        # ... and contains the course ticket COURSE1
3647        self.assertEqual(self.browser.contents,
3648            'automatic,carry_over,code,credits,dcode,fcode,level,'
3649            'level_session,mandatory,passmark,score,semester,title,'
3650            'student_id,certcode,display_fullname\r\n1,0,COURSE1,10,'
3651            'dep1,fac1,100,2004,1,40,,1,'
3652            'Unnamed Course,K1000000,CERT1,Anna Tester\r\n')
3653
3654        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3655        job_id = self.app['datacenter'].running_exports[0][0]
3656        # Thew job can be discarded
3657        self.browser.open(course1_path + '/exports')
3658        self.browser.getControl("Discard").click()
3659        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3660        # Creation, downloading and discarding is logged
3661        logfile = os.path.join(
3662            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3663        logcontent = open(logfile).read()
3664        self.assertTrue(
3665            'zope.mgr - students.browser.CourseExportJobContainerJobConfig '
3666            '- exported: coursetickets (2004, 100, COURSE1), job_id=%s'
3667            % job_id in logcontent
3668            )
3669        self.assertTrue(
3670            'zope.mgr - students.browser.ExportJobContainerDownload '
3671            '- downloaded: WAeUP.Kofa_coursetickets_%s.csv, job_id=%s'
3672            % (job_id, job_id) in logcontent
3673            )
3674        self.assertTrue(
3675            'zope.mgr - students.browser.ExportJobContainerOverview '
3676            '- discarded: job_id=%s' % job_id in logcontent
3677            )
3678
3679    def test_export_departmet_officers(self):
3680        # Create department officer
3681        self.app['users'].addUser('mrdepartment', 'mrdepartmentsecret')
3682        self.app['users']['mrdepartment'].email = 'mrdepartment@foo.ng'
3683        self.app['users']['mrdepartment'].title = 'Carlo Pitter'
3684        # Assign local role
3685        department = self.app['faculties']['fac1']['dep1']
3686        prmlocal = IPrincipalRoleManager(department)
3687        prmlocal.assignRoleToPrincipal('waeup.local.DepartmentOfficer', 'mrdepartment')
3688        # Login as department officer
3689        self.browser.open(self.login_path)
3690        self.browser.getControl(name="form.login").value = 'mrdepartment'
3691        self.browser.getControl(name="form.password").value = 'mrdepartmentsecret'
3692        self.browser.getControl("Login").click()
3693        self.assertMatches('...You logged in...', self.browser.contents)
3694        self.browser.open("http://localhost/app/faculties/fac1/dep1")
3695        self.browser.getLink("Export student data").click()
3696        self.browser.getControl("Configure new export").click()
3697        # Only the paymentsoverview exporter is available for department officers
3698        self.assertFalse('<option value="students">' in self.browser.contents)
3699        self.assertTrue(
3700            '<option value="paymentsoverview">' in self.browser.contents)
3701        self.browser.getControl(name="exporter").value = ['paymentsoverview']
3702        self.browser.getControl(name="session").value = ['2004']
3703        self.browser.getControl(name="level").value = ['100']
3704        self.browser.getControl("Create CSV file").click()
3705        self.assertTrue('Export started' in self.browser.contents)
3706        # Thew job can be discarded
3707        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3708        #job_id = self.app['datacenter'].running_exports[0][0]
3709        job_id = self.wait_for_export_job_completed()
3710        self.browser.open("http://localhost/app/faculties/fac1/dep1/exports")
3711        self.browser.getControl("Discard").click()
3712        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3713
3714    def test_export_bursary_officers(self):
3715        # Create bursary officer
3716        self.app['users'].addUser('mrbursary', 'mrbursarysecret')
3717        self.app['users']['mrbursary'].email = 'mrbursary@foo.ng'
3718        self.app['users']['mrbursary'].title = 'Carlo Pitter'
3719        prmglobal = IPrincipalRoleManager(self.app)
3720        prmglobal.assignRoleToPrincipal('waeup.BursaryOfficer', 'mrbursary')
3721        # Login as bursary officer
3722        self.browser.open(self.login_path)
3723        self.browser.getControl(name="form.login").value = 'mrbursary'
3724        self.browser.getControl(name="form.password").value = 'mrbursarysecret'
3725        self.browser.getControl("Login").click()
3726        self.assertMatches('...You logged in...', self.browser.contents)
3727        self.browser.getLink("Academics").click()
3728        self.browser.getLink("Export student data").click()
3729        self.browser.getControl("Configure new export").click()
3730        # Only the bursary exporter is available for bursary officers
3731        # not only at facultiescontainer level ...
3732        self.assertFalse('<option value="students">' in self.browser.contents)
3733        self.assertTrue('<option value="bursary">' in self.browser.contents)
3734        self.browser.getControl(name="exporter").value = ['bursary']
3735        self.browser.getControl(name="session").value = ['2004']
3736        self.browser.getControl(name="level").value = ['100']
3737        self.browser.getControl("Create CSV file").click()
3738        self.assertTrue('Export started' in self.browser.contents)
3739        # ... but also at other levels
3740        self.browser.open('http://localhost/app/faculties/fac1/dep1')
3741        self.browser.getLink("Export student data").click()
3742        self.browser.getControl("Configure new export").click()
3743        self.assertFalse('<option value="students">' in self.browser.contents)
3744        self.assertTrue('<option value="bursary">' in self.browser.contents)
3745        # Thew job can be discarded
3746        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3747        #job_id = self.app['datacenter'].running_exports[0][0]
3748        job_id = self.wait_for_export_job_completed()
3749        self.browser.open('http://localhost/app/faculties/exports')
3750        self.browser.getControl("Discard").click()
3751        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
Note: See TracBrowser for help on using the repository browser.