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

Last change on this file since 16213 was 16213, checked in by Henrik Bettermann, 4 years ago

Remove contact email header and add footer.

  • Property svn:keywords set to Id
File size: 255.7 KB
Line 
1# -*- coding: utf-8 -*-
2## $Id: test_browser.py 16213 2020-08-25 15:18:15Z henrik $
3##
4## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
5## This program is free software; you can redistribute it and/or modify
6## it under the terms of the GNU General Public License as published by
7## the Free Software Foundation; either version 2 of the License, or
8## (at your option) any later version.
9##
10## This program is distributed in the hope that it will be useful,
11## but WITHOUT ANY WARRANTY; without even the implied warranty of
12## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13## GNU General Public License for more details.
14##
15## You should have received a copy of the GNU General Public License
16## along with this program; if not, write to the Free Software
17## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18##
19"""
20Test the student-related UI components.
21"""
22import shutil
23import tempfile
24import pytz
25import base64
26from datetime import datetime, timedelta, date
27from StringIO import StringIO
28import os
29import grok
30import logging
31from zc.async.testing import wait_for_result
32from zope.event import notify
33from zope.component import createObject, queryUtility, getUtility
34from zope.component.hooks import setSite, clearSite
35from zope.catalog.interfaces import ICatalog
36from zope.security.interfaces import Unauthorized
37from zope.securitypolicy.interfaces import IPrincipalRoleManager
38from zope.testbrowser.testing import Browser
39from zope.interface import implementedBy
40from zope.schema.fieldproperty import FieldProperty
41from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
42from waeup.kofa.testing import FunctionalLayer, FunctionalTestCase
43from waeup.kofa.app import University
44from waeup.kofa.interfaces import IFileStoreNameChooser, IExtFileStore
45from waeup.kofa.payments.interfaces import IPayer
46from waeup.kofa.students.payments import StudentOnlinePayment
47from waeup.kofa.students.student import Student
48from waeup.kofa.students.studylevel import StudentStudyLevel
49from waeup.kofa.university.faculty import Faculty
50from waeup.kofa.university.department import Department
51from waeup.kofa.interfaces import IUserAccount, IJobManager, VALIDATED, CREATED
52from waeup.kofa.authentication import LocalRoleSetEvent
53from waeup.kofa.hostels.hostel import Hostel, Bed, NOT_OCCUPIED
54from waeup.kofa.tests.test_async import FunctionalAsyncTestCase
55from waeup.kofa.browser.tests.test_pdf import samples_dir
56from waeup.kofa.tests.test_authentication import SECRET
57
58PH_LEN = 15911  # Length of placeholder file
59
60SAMPLE_IMAGE = os.path.join(os.path.dirname(__file__), 'test_image.jpg')
61SAMPLE_IMAGE_BMP = os.path.join(os.path.dirname(__file__), 'test_image.bmp')
62URL_LECTURER_LANDING = 'http://localhost/app/my_courses'
63
64curr_year = datetime.now().year
65
66def lookup_submit_value(name, value, browser):
67    """Find a button with a certain value."""
68    for num in range(0, 100):
69        try:
70            button = browser.getControl(name=name, index=num)
71            if button.value.endswith(value):
72                return button
73        except IndexError:
74            break
75    return None
76
77
78class StudentsFullSetup(FunctionalTestCase):
79    # A test case that only contains a setup and teardown
80    #
81    # Complete setup for students handlings is rather complex and
82    # requires lots of things created before we can start. This is a
83    # setup that does all this, creates a university, creates PINs,
84    # etc.  so that we do not have to bother with that in different
85    # test cases.
86
87    layer = FunctionalLayer
88
89    def setUp(self):
90        super(StudentsFullSetup, self).setUp()
91
92        # Setup a sample site for each test
93        app = University()
94        self.dc_root = tempfile.mkdtemp()
95        app['datacenter'].setStoragePath(self.dc_root)
96
97        # Prepopulate the ZODB...
98        self.getRootFolder()['app'] = app
99        # we add the site immediately after creation to the
100        # ZODB. Catalogs and other local utilities are not setup
101        # before that step.
102        self.app = self.getRootFolder()['app']
103        # Set site here. Some of the following setup code might need
104        # to access grok.getSite() and should get our new app then
105        setSite(app)
106
107        # Add student with subobjects
108        student = createObject('waeup.Student')
109        student.firstname = u'Anna'
110        student.lastname = u'Tester'
111        student.reg_number = u'123'
112        student.matric_number = u'234'
113        student.sex = u'm'
114        student.email = 'aa@aa.ng'
115        student.phone = u'1234'
116        student.date_of_birth = date(1981, 2, 4)
117        self.app['students'].addStudent(student)
118        self.student_id = student.student_id
119        self.student = self.app['students'][self.student_id]
120
121        # Set password
122        IUserAccount(
123            self.app['students'][self.student_id]).setPassword('spwd')
124
125        self.login_path = 'http://localhost/app/login'
126        self.container_path = 'http://localhost/app/students'
127        self.manage_container_path = self.container_path + '/@@manage'
128        self.add_student_path = self.container_path + '/addstudent'
129        self.student_path = self.container_path + '/' + self.student_id
130        self.manage_student_path = self.student_path + '/manage_base'
131        self.trigtrans_path = self.student_path + '/trigtrans'
132        self.clearance_path = self.student_path + '/view_clearance'
133        self.personal_path = self.student_path + '/view_personal'
134        self.edit_clearance_path = self.student_path + '/cedit'
135        self.manage_clearance_path = self.student_path + '/manage_clearance'
136        self.edit_personal_path = self.student_path + '/edit_personal'
137        self.manage_personal_path = self.student_path + '/manage_personal'
138        self.studycourse_path = self.student_path + '/studycourse'
139        self.payments_path = self.student_path + '/payments'
140        self.acco_path = self.student_path + '/accommodation'
141        self.history_path = self.student_path + '/history'
142
143        # Create 5 access codes with prefix'PWD'
144        pin_container = self.app['accesscodes']
145        pin_container.createBatch(
146            datetime.utcnow(), 'some_userid', 'PWD', 9.99, 5)
147        pins = pin_container['PWD-1'].values()
148        self.pwdpins = [x.representation for x in pins]
149        self.existing_pwdpin = self.pwdpins[0]
150        parts = self.existing_pwdpin.split('-')[1:]
151        self.existing_pwdseries, self.existing_pwdnumber = parts
152        # Create 5 access codes with prefix 'CLR'
153        pin_container.createBatch(
154            datetime.now(), 'some_userid', 'CLR', 9.99, 5)
155        pins = pin_container['CLR-1'].values()
156        pins[0].owner = u'Hans Wurst'
157        self.existing_clrac = pins[0]
158        self.existing_clrpin = pins[0].representation
159        parts = self.existing_clrpin.split('-')[1:]
160        self.existing_clrseries, self.existing_clrnumber = parts
161        # Create 2 access codes with prefix 'HOS'
162        pin_container.createBatch(
163            datetime.now(), 'some_userid', 'HOS', 9.99, 2)
164        pins = pin_container['HOS-1'].values()
165        self.existing_hosac = pins[0]
166        self.existing_hospin = pins[0].representation
167        parts = self.existing_hospin.split('-')[1:]
168        self.existing_hosseries, self.existing_hosnumber = parts
169
170        # Populate university
171        self.certificate = createObject('waeup.Certificate')
172        self.certificate.code = u'CERT1'
173        self.certificate.application_category = 'basic'
174        self.certificate.study_mode = 'ug_ft'
175        self.certificate.start_level = 100
176        self.certificate.end_level = 500
177        self.certificate.school_fee_1 = 40000.0
178        self.certificate.school_fee_2 = 20000.0
179        self.app['faculties']['fac1'] = Faculty(code=u'fac1')
180        self.app['faculties']['fac1']['dep1'] = Department(code=u'dep1')
181        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
182            self.certificate)
183        self.course = createObject('waeup.Course')
184        self.course.code = 'COURSE1'
185        self.course.semester = 1
186        self.course.credits = 10
187        self.course.passmark = 40
188        self.app['faculties']['fac1']['dep1'].courses.addCourse(
189            self.course)
190        self.app['faculties']['fac1']['dep1'].certificates[
191            'CERT1'].addCertCourse(self.course, level=100)
192
193        # Configure university and hostels
194        self.app['hostels'].accommodation_states = ['admitted']
195        self.app['hostels'].accommodation_session = 2004
196        delta = timedelta(days=10)
197        self.app['hostels'].startdate = datetime.now(pytz.utc) - delta
198        self.app['hostels'].enddate = datetime.now(pytz.utc) + delta
199        self.app['configuration'].carry_over = True
200        configuration = createObject('waeup.SessionConfiguration')
201        configuration.academic_session = 2004
202        configuration.clearance_fee = 3456.0
203        configuration.transcript_fee = 4567.0
204        configuration.booking_fee = 123.4
205        configuration.maint_fee = 987.0
206        configuration.transfer_fee = 456.0
207        configuration.late_registration_fee = 345.0
208        self.app['configuration'].addSessionConfiguration(configuration)
209
210        # Create a hostel with two beds
211        hostel = Hostel()
212        hostel.hostel_id = u'hall-1'
213        hostel.hostel_name = u'Hall 1'
214        hostel.maint_fee = 876.0
215        self.app['hostels'].addHostel(hostel)
216        bed = Bed()
217        bed.bed_id = u'hall-1_A_101_A'
218        bed.bed_number = 1
219        bed.owner = NOT_OCCUPIED
220        bed.bed_type = u'regular_male_fr'
221        self.app['hostels'][hostel.hostel_id].addBed(bed)
222        bed = Bed()
223        bed.bed_id = u'hall-1_A_101_B'
224        bed.bed_number = 2
225        bed.owner = NOT_OCCUPIED
226        bed.bed_type = u'regular_female_fr'
227        self.app['hostels'][hostel.hostel_id].addBed(bed)
228
229        # Set study course attributes of test student
230        self.student['studycourse'].certificate = self.certificate
231        self.student['studycourse'].current_session = 2004
232        self.student['studycourse'].entry_session = 2004
233        self.student['studycourse'].current_verdict = 'A'
234        self.student['studycourse'].current_level = 100
235        # Update the catalog
236        notify(grok.ObjectModifiedEvent(self.student))
237
238        # Put the prepopulated site into test ZODB and prepare test
239        # browser
240        self.browser = Browser()
241        self.browser.handleErrors = False
242
243    def tearDown(self):
244        super(StudentsFullSetup, self).tearDown()
245        clearSite()
246        shutil.rmtree(self.dc_root)
247
248
249class StudentsContainerUITests(StudentsFullSetup):
250    # Tests for StudentsContainer class views and pages
251
252    layer = FunctionalLayer
253
254    def test_anonymous_access(self):
255        # Anonymous users can't access students containers
256        self.assertRaises(
257            Unauthorized, self.browser.open, self.container_path)
258        self.assertRaises(
259            Unauthorized, self.browser.open, self.manage_container_path)
260        return
261
262    def test_manage_access(self):
263        # Managers can access the view page of students
264        # containers and can perform actions
265        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
266        self.browser.open(self.container_path)
267        self.assertEqual(self.browser.headers['Status'], '200 Ok')
268        self.assertEqual(self.browser.url, self.container_path)
269        self.browser.getLink("Manage students section").click()
270        self.assertEqual(self.browser.headers['Status'], '200 Ok')
271        self.assertEqual(self.browser.url, self.manage_container_path)
272        return
273
274    def test_add_search_delete_students(self):
275        # Managers can add search and remove students
276        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
277        self.browser.open(self.manage_container_path)
278        self.browser.getLink("Add student").click()
279        self.assertEqual(self.browser.headers['Status'], '200 Ok')
280        self.assertEqual(self.browser.url, self.add_student_path)
281        self.browser.getControl(name="form.firstname").value = 'Bob'
282        self.browser.getControl(name="form.lastname").value = 'Tester'
283        self.browser.getControl(name="form.reg_number").value = '123'
284        self.browser.getControl("Create student").click()
285        self.assertTrue(
286            'Registration number exists already' in self.browser.contents)
287        self.browser.getControl(name="form.reg_number").value = '1234'
288        self.browser.getControl("Create student").click()
289        self.assertTrue('Student record created' in self.browser.contents)
290
291        # Registration and matric numbers must be unique
292        self.browser.getLink("Manage").click()
293        self.browser.getControl(name="form.reg_number").value = '123'
294        self.browser.getControl("Save").click()
295        self.assertMatches('...Registration number exists...',
296                           self.browser.contents)
297        self.browser.getControl(name="form.reg_number").value = '789'
298        self.browser.getControl(name="form.matric_number").value = '234'
299        self.browser.getControl("Save").click()
300        self.assertMatches('...Matriculation number exists...',
301                           self.browser.contents)
302
303        # We can find a student with a certain student_id
304        self.browser.open(self.container_path)
305        self.browser.getControl("Find student(s)").click()
306        self.assertTrue('Empty search string' in self.browser.contents)
307        self.browser.getControl(name="searchtype").value = ['student_id']
308        self.browser.getControl(name="searchterm").value = self.student_id
309        self.browser.getControl("Find student(s)").click()
310        self.assertTrue('Anna Tester' in self.browser.contents)
311
312        # We can find a student in a certain session
313        self.browser.open(self.container_path)
314        self.browser.getControl(name="searchtype").value = ['current_session']
315        self.browser.getControl(name="searchterm").value = '2004'
316        self.browser.getControl("Find student(s)").click()
317        self.assertTrue('Anna Tester' in self.browser.contents)
318        # Session fileds require integer values
319        self.browser.open(self.container_path)
320        self.browser.getControl(name="searchtype").value = ['current_session']
321        self.browser.getControl(name="searchterm").value = '2004/2005'
322        self.browser.getControl("Find student(s)").click()
323        self.assertTrue('Only year dates allowed' in self.browser.contents)
324        self.browser.open(self.manage_container_path)
325        self.browser.getControl(name="searchtype").value = ['current_session']
326        self.browser.getControl(name="searchterm").value = '2004/2005'
327        self.browser.getControl("Find student(s)").click()
328        self.assertTrue('Only year dates allowed' in self.browser.contents)
329
330        # We can find a student in a certain study_mode
331        self.browser.open(self.container_path)
332        self.browser.getControl(name="searchtype").value = ['current_mode']
333        self.browser.getControl(name="searchterm").value = 'ug_ft'
334        self.browser.getControl("Find student(s)").click()
335        self.assertTrue('Anna Tester' in self.browser.contents)
336
337        # We can find a student in a certain department
338        self.browser.open(self.container_path)
339        self.browser.getControl(name="searchtype").value = ['depcode']
340        self.browser.getControl(name="searchterm").value = 'dep1'
341        self.browser.getControl("Find student(s)").click()
342        self.assertTrue('Anna Tester' in self.browser.contents)
343
344        # We can find a student by searching for all kind of name parts
345        self.browser.open(self.manage_container_path)
346        self.browser.getControl("Find student(s)").click()
347        self.assertTrue('Empty search string' in self.browser.contents)
348        self.browser.getControl(name="searchtype").value = ['fullname']
349        self.browser.getControl(name="searchterm").value = 'Anna Tester'
350        self.browser.getControl("Find student(s)").click()
351        self.assertTrue('Anna Tester' in self.browser.contents)
352        self.browser.open(self.manage_container_path)
353        self.browser.getControl(name="searchtype").value = ['fullname']
354        self.browser.getControl(name="searchterm").value = 'Anna'
355        self.browser.getControl("Find student(s)").click()
356        self.assertTrue('Anna Tester' in self.browser.contents)
357        self.browser.open(self.manage_container_path)
358        self.browser.getControl(name="searchtype").value = ['fullname']
359        self.browser.getControl(name="searchterm").value = 'Tester'
360        self.browser.getControl("Find student(s)").click()
361        self.assertTrue('Anna Tester' in self.browser.contents)
362        self.browser.open(self.manage_container_path)
363        self.browser.getControl(name="searchtype").value = ['fullname']
364        self.browser.getControl(name="searchterm").value = 'An'
365        self.browser.getControl("Find student(s)").click()
366        self.assertFalse('Anna Tester' in self.browser.contents)
367        self.browser.open(self.manage_container_path)
368        self.browser.getControl(name="searchtype").value = ['fullname']
369        self.browser.getControl(name="searchterm").value = 'An*'
370        self.browser.getControl("Find student(s)").click()
371        self.assertTrue('Anna Tester' in self.browser.contents)
372        self.browser.open(self.manage_container_path)
373        self.browser.getControl(name="searchtype").value = ['fullname']
374        self.browser.getControl(name="searchterm").value = 'tester'
375        self.browser.getControl("Find student(s)").click()
376        self.assertTrue('Anna Tester' in self.browser.contents)
377        self.browser.open(self.manage_container_path)
378        self.browser.getControl(name="searchtype").value = ['fullname']
379        self.browser.getControl(name="searchterm").value = 'Tester Ana'
380        self.browser.getControl("Find student(s)").click()
381        self.assertFalse('Anna Tester' in self.browser.contents)
382        self.browser.open(self.manage_container_path)
383        self.browser.getControl(name="searchtype").value = ['fullname']
384        self.browser.getControl(name="searchterm").value = 'Tester Anna'
385        self.browser.getControl("Find student(s)").click()
386        self.assertTrue('Anna Tester' in self.browser.contents)
387        # The old searchterm will be used again
388        self.browser.getControl("Find student(s)").click()
389        self.assertTrue('Anna Tester' in self.browser.contents)
390
391        # We can find suspended students
392        self.student.suspended = True
393        notify(grok.ObjectModifiedEvent(self.student))
394        self.browser.open(self.manage_container_path)
395        self.browser.getControl(name="searchtype").value = ['suspended']
396        self.browser.getControl("Find student(s)").click()
397        self.assertTrue('Anna Tester' in self.browser.contents)
398        self.browser.open(self.container_path)
399        self.browser.getControl(name="searchtype").value = ['suspended']
400        self.browser.getControl("Find student(s)").click()
401        self.assertTrue('Anna Tester' in self.browser.contents)
402
403        # The catalog is informed when studycourse objects have been
404        # edited
405        self.browser.open(self.studycourse_path + '/manage')
406        self.browser.getControl(name="form.current_session").value = ['2010']
407        self.browser.getControl(name="form.entry_session").value = ['2010']
408        self.browser.getControl(name="form.entry_mode").value = ['ug_ft']
409        self.browser.getControl("Save").click()
410
411        # We can find the student in the new session
412        self.browser.open(self.manage_container_path)
413        self.browser.getControl(name="searchtype").value = ['current_session']
414        self.browser.getControl(name="searchterm").value = '2010'
415        self.browser.getControl("Find student(s)").click()
416        self.assertTrue('Anna Tester' in self.browser.contents)
417
418        ctrl = self.browser.getControl(name='entries')
419        ctrl.getControl(value=self.student_id).selected = True
420        self.browser.getControl("Remove selected", index=0).click()
421        self.assertTrue('Successfully removed' in self.browser.contents)
422        self.browser.getControl(name="searchtype").value = ['student_id']
423        self.browser.getControl(name="searchterm").value = self.student_id
424        self.browser.getControl("Find student(s)").click()
425        self.assertTrue('No student found' in self.browser.contents)
426
427        self.browser.open(self.container_path)
428        self.browser.getControl(name="searchtype").value = ['student_id']
429        self.browser.getControl(name="searchterm").value = self.student_id
430        self.browser.getControl("Find student(s)").click()
431        self.assertTrue('No student found' in self.browser.contents)
432        return
433
434    def test_add_graduated_students(self):
435        # Managers can add search and remove students
436        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
437        self.browser.open(self.manage_container_path)
438        self.browser.getLink("Add student").click()
439        self.assertEqual(self.browser.headers['Status'], '200 Ok')
440        self.assertEqual(self.browser.url, self.add_student_path)
441        self.browser.getControl(name="form.firstname").value = 'Bob'
442        self.browser.getControl(name="form.lastname").value = 'Tester'
443        self.browser.getControl(name="form.reg_number").value = '1234'
444        self.browser.getControl("Create graduated student").click()
445        self.assertTrue('Graduated student record created' in self.browser.contents)
446        self.assertEqual(self.app['students']['K1000001'].state, 'graduated')
447        self.browser.open("http://localhost/app/students/K1000001/history")
448        self.assertTrue("State 'graduated' set by Manager" in self.browser.contents)
449        return
450
451
452class OfficerUITests(StudentsFullSetup):
453    # Tests for Student class views and pages
454
455    def setUp(self):
456        super(OfficerUITests, self).setUp()
457        self.setup_logging()
458        return
459
460    def tearDown(self):
461        super(OfficerUITests, self).tearDown()
462        self.teardown_logging()
463        return
464
465    def setup_logging(self):
466        # setup a log-handler that catches all fake mailer output
467        self.stream = StringIO()
468        handler = logging.StreamHandler(self.stream)
469        logger = logging.getLogger('test.smtp')
470        logger.addHandler(handler)
471        logger.setLevel(logging.INFO)
472        return
473
474    def get_fake_smtp_output(self):
475        # get output generated by fake mailer
476        self.stream.flush()
477        self.stream.seek(0)
478        return self.stream.read()
479
480    def teardown_logging(self):
481        # remove the log handler for fake mailer output
482        logger = logging.getLogger('test.smtp')
483        handlers = [x for x in logger.handlers]
484        for handler in handlers:
485            logger.removeHandler(handler)
486        return
487
488    def test_student_properties(self):
489        self.student['studycourse'].current_level = 100
490        self.assertEqual(self.student.current_level, 100)
491        self.student['studycourse'].current_session = 2011
492        self.assertEqual(self.student.current_session, 2011)
493        self.student['studycourse'].current_verdict = 'A'
494        self.assertEqual(self.student.current_verdict, 'A')
495        return
496
497    def test_studylevelmanagepage(self):
498        studylevel = StudentStudyLevel()
499        studylevel.level = 100
500        cert = self.app['faculties']['fac1']['dep1'].certificates['CERT1']
501        self.student['studycourse'].addStudentStudyLevel(cert, studylevel)
502        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
503        self.browser.open(self.studycourse_path + '/100/manage')
504        self.assertEqual(
505            self.browser.url, self.studycourse_path + '/100/manage')
506        self.assertEqual(self.browser.headers['Status'], '200 Ok')
507
508    def test_basic_auth(self):
509        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
510        self.browser.open('http://localhost/app')
511        self.browser.getLink("Logout").click()
512        self.assertTrue('You have been logged out' in self.browser.contents)
513        # But we are still logged in since we've used basic
514        # authentication here.  Wikipedia says: Existing browsers
515        # retain authentication information until the tab or browser
516        # is closed or the user clears the history.  HTTP does not
517        # provide a method for a server to direct clients to discard
518        # these cached credentials. This means that there is no
519        # effective way for a server to "log out" the user without
520        # closing the browser. This is a significant defect that
521        # requires browser manufacturers to support a "logout" user
522        # interface element ...
523        self.assertTrue('Manager' in self.browser.contents)
524
525    def test_basic_auth_base64(self):
526        auth_token = base64.b64encode('mgr:mgrpw')
527        self.browser.addHeader('Authorization', 'Basic %s' % auth_token)
528        self.browser.open(self.manage_container_path)
529        self.assertEqual(self.browser.headers['Status'], '200 Ok')
530
531    def test_manage_access(self):
532        # Managers can access the pages of students
533        # and can perform actions
534        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
535        self.browser.open(self.student_path)
536        self.assertEqual(self.browser.headers['Status'], '200 Ok')
537        self.assertEqual(self.browser.url, self.student_path)
538        self.browser.getLink("Trigger").click()
539        self.assertEqual(self.browser.headers['Status'], '200 Ok')
540        # Managers can trigger transitions
541        self.browser.getControl(name="transition").value = ['admit']
542        self.browser.getControl("Save").click()
543        # Managers can edit base
544        self.browser.open(self.student_path)
545        self.browser.getLink("Manage").click()
546        self.assertEqual(self.browser.url, self.manage_student_path)
547        self.assertEqual(self.browser.headers['Status'], '200 Ok')
548        self.browser.getControl(name="form.firstname").value = 'John'
549        self.browser.getControl(name="form.lastname").value = 'Tester'
550        self.browser.getControl(name="form.reg_number").value = '345'
551        self.browser.getControl(name="password").value = 'secret'
552        self.browser.getControl(name="control_password").value = 'secret'
553        self.browser.getControl("Save").click()
554        self.assertMatches('...Form has been saved...',
555                           self.browser.contents)
556        self.browser.open(self.student_path)
557        self.browser.getLink("Clearance Data").click()
558        self.assertEqual(self.browser.headers['Status'], '200 Ok')
559        self.assertEqual(self.browser.url, self.clearance_path)
560        self.browser.getLink("Manage").click()
561        self.assertEqual(self.browser.headers['Status'], '200 Ok')
562        self.assertEqual(self.browser.url, self.manage_clearance_path)
563        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
564        self.browser.getControl("Save").click()
565        self.assertMatches('...Form has been saved...',
566                           self.browser.contents)
567
568        self.browser.open(self.student_path)
569        self.browser.getLink("Personal Data").click()
570        self.assertEqual(self.browser.headers['Status'], '200 Ok')
571        self.assertEqual(self.browser.url, self.personal_path)
572        self.browser.getLink("Manage").click()
573        self.assertEqual(self.browser.headers['Status'], '200 Ok')
574        self.assertEqual(self.browser.url, self.manage_personal_path)
575        self.browser.open(self.personal_path)
576        self.assertTrue('Updated' in self.browser.contents)
577        self.browser.getLink("Edit").click()
578        self.assertEqual(self.browser.headers['Status'], '200 Ok')
579        self.assertEqual(self.browser.url, self.edit_personal_path)
580        self.browser.getControl("Save").click()
581        # perm_address is required in IStudentPersonalEdit
582        self.assertMatches('...Required input is missing...',
583                           self.browser.contents)
584        self.browser.getControl(name="form.perm_address").value = 'My address!'
585        self.browser.getControl("Save").click()
586        self.assertMatches('...Form has been saved...',
587                           self.browser.contents)
588
589        # Managers can browse all subobjects
590        self.browser.open(self.student_path)
591        self.browser.getLink("Payments").click()
592        self.assertEqual(self.browser.headers['Status'], '200 Ok')
593        self.assertEqual(self.browser.url, self.payments_path)
594        self.browser.open(self.student_path)
595        self.browser.getLink("Accommodation").click()
596        self.assertEqual(self.browser.headers['Status'], '200 Ok')
597        # We have been redirected to the manage page
598        self.assertEqual(self.browser.url, self.acco_path + '/manage')
599        self.browser.open(self.student_path)
600        self.browser.getLink("History").click()
601        self.assertEqual(self.browser.headers['Status'], '200 Ok')
602        self.assertEqual(self.browser.url, self.history_path)
603        self.assertMatches('...Admitted by Manager...',
604                           self.browser.contents)
605        # Only the Application Slip does not exist
606        self.assertFalse('Application Slip' in self.browser.contents)
607        return
608
609    def test_flash_notice(self):
610        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
611        self.browser.open(self.student_path)
612        self.assertFalse('alert alert-warning' in self.browser.contents)
613        self.student.flash_notice = u'Happy Birthday!'
614        self.browser.open(self.student_path)
615        self.assertTrue(
616            '<div><div class="alert alert-warning">Happy Birthday!</div>'
617            in self.browser.contents)
618        return
619
620    def test_manage_contact_student(self):
621        # Managers can contact student
622        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
623        # Remove required FieldProperty attribute first ...
624        delattr(Student, 'email')
625        # ... and replace by None
626        self.student.email = None
627        # Now we have to add  the FieldProperty attribute again. Otherwise
628        # many other tests below will fail.
629        iface = list(implementedBy(Student))[0]
630        field_property = FieldProperty(iface['email'])
631        setattr(Student, 'email', field_property)
632        self.browser.open(self.student_path)
633        self.browser.getLink("Send email").click()
634        self.browser.getControl(
635            name="form.subject").value = 'Important subject'
636        self.browser.getControl(name="form.body").value = 'Hello!'
637        self.browser.getControl("Send message now").click()
638        self.assertTrue(
639            'An smtp server error occurred' in self.browser.contents)
640        self.student.email = 'xx@yy.zz'
641        self.browser.getControl("Send message now").click()
642        self.assertTrue('Your message has been sent' in self.browser.contents)
643        self.assertMatches(
644            'Sending email from no-reply@waeup.org to xx@yy.zz:'
645            '\nMessage:'
646            '\nmsg: MIME-Version: 1.0'
647            '\nmsg: Content-Type: text/plain; charset="us-ascii"'
648            '\nmsg: Content-Transfer-Encoding: 7bit'
649            '\nmsg: From: Manager <no-reply@waeup.org>'
650            '\nmsg: To: Anna Tester <xx@yy.zz>'
651            '\nmsg: Reply-To: Manager <contact@waeup.org>'
652            '\nmsg: Subject: Important subject'
653            '\nmsg:'
654            '\nmsg: Hello!'
655            '\nmsg:'
656            '\nmsg: ---'
657            '\nmsg: Manager (id: zope.mgr)'
658            '\nmsg: Sample University'
659            '\nmsg:',
660            self.get_fake_smtp_output()
661            )
662        return
663
664    def test_manage_remove_department(self):
665        # Lazy student is studying CERT1
666        lazystudent = Student()
667        lazystudent.firstname = u'Lazy'
668        lazystudent.lastname = u'Student'
669        self.app['students'].addStudent(lazystudent)
670        student_id = lazystudent.student_id
671        student_path = self.container_path + '/' + student_id
672        lazystudent['studycourse'].certificate = self.certificate
673        notify(grok.ObjectModifiedEvent(lazystudent))
674        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
675        self.browser.open(student_path + '/studycourse')
676        self.assertTrue('CERT1' in self.browser.contents)
677        # After some years the department is removed
678        del self.app['faculties']['fac1']['dep1']
679        # So CERT1 does no longer exist and lazy student's
680        # certificate reference is removed too
681        self.browser.open(student_path + '/studycourse')
682        self.assertEqual(self.browser.headers['Status'], '200 Ok')
683        self.assertEqual(self.browser.url, student_path + '/studycourse')
684        self.assertFalse('CERT1' in self.browser.contents)
685        self.assertMatches('...<div>--</div>...',
686                           self.browser.contents)
687
688    def test_manage_upload_file(self):
689        # Managers can upload a file via the StudentClearanceManageFormPage
690        # The image is stored even if form has errors
691        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
692        self.browser.open(self.manage_clearance_path)
693        # No birth certificate has been uploaded yet
694        # Browsing the link shows a placerholder image
695        self.browser.open('birth_certificate')
696        self.assertEqual(
697            self.browser.headers['content-type'], 'image/jpeg')
698        self.assertEqual(len(self.browser.contents), PH_LEN)
699        # Create a pseudo image file and select it to be uploaded in form
700        # as birth certificate
701        self.browser.open(self.manage_clearance_path)
702        image = open(SAMPLE_IMAGE, 'rb')
703        ctrl = self.browser.getControl(name='birthcertificateupload')
704        file_ctrl = ctrl.mech_control
705        file_ctrl.add_file(image, filename='my_birth_certificate.jpg')
706        # The Save action does not upload files
707        self.browser.getControl("Save").click()  # submit form
708        self.assertFalse(
709            '<a target="image" href="birth_certificate">'
710            in self.browser.contents)
711        # ... but the correct upload submit button does
712        image = open(SAMPLE_IMAGE)
713        ctrl = self.browser.getControl(name='birthcertificateupload')
714        file_ctrl = ctrl.mech_control
715        file_ctrl.add_file(image, filename='my_birth_certificate.jpg')
716        self.browser.getControl(
717            name='upload_birthcertificateupload').click()
718        # There is a correct <img> link included
719        self.assertTrue(
720            'href="http://localhost/app/students/K1000000/birth_certificate"'
721            in self.browser.contents)
722        # Browsing the link shows a real image
723        self.browser.open('birth_certificate')
724        self.assertEqual(
725            self.browser.headers['content-type'], 'image/jpeg')
726        self.assertEqual(len(self.browser.contents), 2787)
727        # We can't reupload a file. The existing file must be deleted first.
728        self.browser.open(self.manage_clearance_path)
729        self.assertFalse(
730            'upload_birthcertificateupload' in self.browser.contents)
731        # File must be deleted first
732        self.browser.getControl(name='delete_birthcertificateupload').click()
733        self.assertTrue(
734            'birth_certificate deleted' in self.browser.contents)
735        # Uploading a file which is bigger than 250k will raise an error
736        big_image = StringIO(open(SAMPLE_IMAGE, 'rb').read() * 100)
737        ctrl = self.browser.getControl(name='birthcertificateupload')
738        file_ctrl = ctrl.mech_control
739        file_ctrl.add_file(big_image, filename='my_birth_certificate.jpg')
740        self.browser.getControl(
741            name='upload_birthcertificateupload').click()
742        self.assertTrue(
743            'Uploaded file is too big' in self.browser.contents)
744        # we do not rely on filename extensions given by uploaders
745        image = open(SAMPLE_IMAGE, 'rb')  # a jpg-file
746        ctrl = self.browser.getControl(name='birthcertificateupload')
747        file_ctrl = ctrl.mech_control
748        # tell uploaded file is bmp
749        file_ctrl.add_file(image, filename='my_birth_certificate.bmp')
750        self.browser.getControl(
751            name='upload_birthcertificateupload').click()
752        self.assertTrue(
753            # jpg file was recognized
754            'File birth_certificate.jpg uploaded.' in self.browser.contents)
755        # Delete file again
756        self.browser.getControl(name='delete_birthcertificateupload').click()
757        self.assertTrue(
758            'birth_certificate deleted' in self.browser.contents)
759        # File names must meet several conditions
760        bmp_image = open(SAMPLE_IMAGE_BMP, 'rb')
761        ctrl = self.browser.getControl(name='birthcertificateupload')
762        file_ctrl = ctrl.mech_control
763        file_ctrl.add_file(bmp_image, filename='my_birth_certificate.bmp')
764        self.browser.getControl(
765            name='upload_birthcertificateupload').click()
766        self.assertTrue(
767            'Only the following extensions are allowed'
768            in self.browser.contents)
769
770        # Managers can upload a file via the StudentBaseManageFormPage
771        self.browser.open(self.manage_student_path)
772        image = open(SAMPLE_IMAGE_BMP, 'rb')
773        ctrl = self.browser.getControl(name='passportuploadmanage')
774        file_ctrl = ctrl.mech_control
775        file_ctrl.add_file(image, filename='my_photo.bmp')
776        self.browser.getControl(
777            name='upload_passportuploadmanage').click()
778        self.assertTrue(
779            'jpg file format expected' in self.browser.contents)
780        ctrl = self.browser.getControl(name='passportuploadmanage')
781        file_ctrl = ctrl.mech_control
782        image = open(SAMPLE_IMAGE, 'rb')
783        file_ctrl.add_file(image, filename='my_photo.jpg')
784        self.browser.getControl(
785            name='upload_passportuploadmanage').click()
786        self.assertTrue(
787            'src="http://localhost/app/students/K1000000/passport.jpg"'
788            in self.browser.contents)
789        # We remove the passport file again
790        self.browser.open(self.manage_student_path)
791        self.browser.getControl('Delete').click()
792        self.browser.open(self.student_path + '/clearance_slip.pdf')
793        self.assertEqual(self.browser.headers['Status'], '200 Ok')
794        self.assertEqual(self.browser.headers['Content-Type'],
795                         'application/pdf')
796        # We want to see the signature fields.
797        IWorkflowState(self.student).setState('cleared')
798        self.browser.open(self.student_path + '/clearance_slip.pdf')
799        self.assertEqual(self.browser.headers['Status'], '200 Ok')
800        self.assertEqual(self.browser.headers['Content-Type'],
801                         'application/pdf')
802        path = os.path.join(samples_dir(), 'clearance_slip.pdf')
803        open(path, 'wb').write(self.browser.contents)
804        print "Sample PDF clearance_slip.pdf written to %s" % path
805
806    def test_manage_course_lists(self):
807        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
808        self.browser.open(self.student_path)
809        self.browser.getLink("Study Course").click()
810        self.assertEqual(self.browser.headers['Status'], '200 Ok')
811        self.assertEqual(self.browser.url, self.studycourse_path)
812        self.assertTrue('Undergraduate Full-Time' in self.browser.contents)
813        self.browser.getLink("Manage").click()
814        self.assertTrue('Manage study course' in self.browser.contents)
815        # Before we can select a level, the certificate must
816        # be selected and saved
817        self.browser.getControl(name="form.certificate").value = ['CERT1']
818        self.browser.getControl(name="form.current_session").value = ['2004']
819        self.browser.getControl(name="form.current_verdict").value = ['A']
820        self.browser.getControl(name="form.entry_mode").value = ['ug_ft']
821        self.browser.getControl("Save").click()
822        # Now we can save also the current level which depends on start and end
823        # level of the certificate
824        self.browser.getControl(name="form.current_level").value = ['100']
825        self.browser.getControl("Save").click()
826        # Managers can add and remove any study level (course list)
827        self.browser.getControl(name="addlevel").value = ['100']
828        self.browser.getControl("Add study level").click()
829        self.assertMatches(
830            '...You must select a session...', self.browser.contents)
831        self.browser.getControl(name="addlevel").value = ['100']
832        self.browser.getControl(name="level_session").value = ['2004']
833        self.browser.getControl("Add study level").click()
834        self.assertMatches('...<span>100</span>...', self.browser.contents)
835        self.assertEqual(self.student['studycourse']['100'].level, 100)
836        self.assertEqual(
837            self.student['studycourse']['100'].level_session, 2004)
838        self.browser.getControl(name="addlevel").value = ['100']
839        self.browser.getControl(name="level_session").value = ['2004']
840        self.browser.getControl("Add study level").click()
841        self.assertMatches('...This level exists...', self.browser.contents)
842        self.browser.getControl("Remove selected").click()
843        self.assertMatches(
844            '...No study level selected...', self.browser.contents)
845        self.browser.getControl(name="val_id").value = ['100']
846        self.browser.getControl(name="level_session").value = ['2004']
847        self.browser.getControl("Remove selected").click()
848        self.assertMatches('...Successfully removed...', self.browser.contents)
849        # Removing levels is properly logged
850        logfile = os.path.join(
851            self.app['datacenter'].storage, 'logs', 'students.log')
852        logcontent = open(logfile).read()
853        self.assertTrue(
854            'zope.mgr - students.browser.StudyCourseManageFormPage '
855            '- K1000000 - removed: 100' in logcontent)
856        # Add level again
857        self.browser.getControl(name="addlevel").value = ['100']
858        self.browser.getControl(name="level_session").value = ['2004']
859        self.browser.getControl("Add study level").click()
860
861        # Managers can view and manage course lists
862        self.browser.getLink("100").click()
863        self.assertMatches(
864            '...: 100 (Year 1)...', self.browser.contents)
865        self.browser.getLink("Manage").click()
866        self.browser.getControl(name="form.level_session").value = ['2002']
867        self.browser.getControl("Save").click()
868        self.browser.getControl("Remove selected").click()
869        self.assertMatches('...No ticket selected...', self.browser.contents)
870        ctrl = self.browser.getControl(name='val_id')
871        ctrl.getControl(value='COURSE1').selected = True
872        self.browser.getControl("Remove selected", index=0).click()
873        self.assertTrue('Successfully removed' in self.browser.contents)
874        # Removing course tickets is properly logged
875        logfile = os.path.join(
876            self.app['datacenter'].storage, 'logs', 'students.log')
877        logcontent = open(logfile).read()
878        self.assertTrue(
879            'zope.mgr - students.browser.StudyLevelManageFormPage '
880            '- K1000000 - level 100 - removed: COURSE1' in logcontent)
881        self.browser.getLink("here").click()
882        self.browser.getControl(name="form.course").value = ['COURSE1']
883        self.course.credits = 100
884        self.browser.getControl("Add course ticket").click()
885        self.assertMatches(
886            '...Maximum credits exceeded...', self.browser.contents)
887        self.course.credits = 10
888        self.browser.getControl("Add course ticket").click()
889        self.assertTrue('Successfully added' in self.browser.contents)
890        # We can do the same by adding the course on the manage page directly
891        del self.student['studycourse']['100']['COURSE1']
892        self.browser.getControl(name="course").value = 'COURSE1'
893        self.browser.getControl("Add course ticket").click()
894        self.assertTrue('Successfully added' in self.browser.contents)
895        self.browser.getLink("here").click()
896        self.browser.getControl(name="form.course").value = ['COURSE1']
897        self.browser.getControl("Add course ticket").click()
898        self.assertTrue('The ticket exists' in self.browser.contents)
899        self.browser.getControl("Cancel").click()
900        self.browser.getLink("COURSE1").click()
901        self.browser.getLink("Manage").click()
902        self.browser.getControl("Save").click()
903        self.assertTrue('Form has been saved' in self.browser.contents)
904        # Grade and weight have been determined
905        self.browser.open(self.studycourse_path + '/100/COURSE1')
906        self.assertFalse('Grade' in self.browser.contents)
907        self.assertFalse('Weight' in self.browser.contents)
908        self.student['studycourse']['100']['COURSE1'].score = 55
909        self.browser.open(self.studycourse_path + '/100/COURSE1')
910        self.assertTrue('Grade' in self.browser.contents)
911        self.assertTrue('Weight' in self.browser.contents)
912        self.assertEqual(
913            self.student['studycourse']['100']['COURSE1'].grade, 'C')
914        self.assertEqual(
915            self.student['studycourse']['100']['COURSE1'].weight, 3)
916        # We add another ticket to check if GPA will be correctly
917        # calculated (and rounded)
918        courseticket = createObject('waeup.CourseTicket')
919        courseticket.code = 'ANYCODE'
920        courseticket.title = u'Any TITLE'
921        courseticket.credits = 13
922        courseticket.score = 66
923        courseticket.semester = 1
924        courseticket.dcode = u'ANYDCODE'
925        courseticket.fcode = u'ANYFCODE'
926        self.student['studycourse']['100']['COURSE2'] = courseticket
927        self.browser.open(self.student_path + '/studycourse/100')
928        # total credits
929        self.assertEqual(
930            self.student['studycourse']['100'].gpa_params_rectified[1], 23)
931        # weigheted credits = 3 * 10 + 4 * 13
932        self.assertEqual(
933            self.student['studycourse']['100'].gpa_params_rectified[2], 82.0)
934        # sgpa = 82 / 23
935        self.assertEqual(
936            self.student['studycourse']['100'].gpa_params_rectified[0],
937            3.5652173913043477)
938        # Carry-over courses will be collected when next level is created
939        self.browser.open(self.student_path + '/studycourse/manage')
940        # Add next level
941        self.student['studycourse']['100']['COURSE1'].score = 10
942        self.browser.getControl(name="addlevel").value = ['200']
943        self.browser.getControl(name="level_session").value = ['2005']
944        self.browser.getControl("Add study level").click()
945        self.browser.getLink("200").click()
946        self.assertMatches(
947            '...: 200 (Year 2)...', self.browser.contents)
948        # Since COURSE1 has score 10 it becomes a carry-over course
949        # in level 200
950        self.assertEqual(
951            sorted(self.student['studycourse']['200'].keys()), [u'COURSE1'])
952        self.assertTrue(
953            self.student['studycourse']['200']['COURSE1'].carry_over)
954        # Passed and failed courses have been counted
955        self.assertEqual(
956            self.student['studycourse']['100'].passed_params,
957            (1, 1, 13, 10, 'COURSE1 ', ''))
958        self.assertEqual(
959            self.student['studycourse']['200'].passed_params,
960            (0, 0, 0, 0, '', 'COURSE1 '))
961        # And also cumulative params can be calculated. Meanwhile we have the
962        # following courses: COURSE1 and COURSE2 in level 100 and
963        # COURSE1 as carry-over course in level 200.
964        self.assertEqual(
965            self.student['studycourse']['100'].cumulative_params,
966            (2.260869565217391, 23, 52.0, 23, 13))
967        # COURSE1 in level 200 is not taken into consideration
968        # when calculating the gpa.
969        self.assertEqual(
970            self.student['studycourse']['200'].cumulative_params,
971            (2.260869565217391, 23, 52.0, 33, 13))
972        return
973
974    def test_gpa_calculation_with_carryover(self):
975        studylevel = createObject(u'waeup.StudentStudyLevel')
976        studylevel.level = 100
977        studylevel.level_session = 2005
978        self.student['studycourse'].entry_mode = 'ug_ft'
979        self.student['studycourse'].addStudentStudyLevel(
980            self.certificate, studylevel)
981        # First course has been added automatically.
982        # Set score above passmark.
983        studylevel['COURSE1'].score = studylevel['COURSE1'].passmark + 1
984        # GPA is 1.
985        self.assertEqual(
986            self.student['studycourse']['100'].gpa_params_rectified[0], 1.0)
987        # Set score below passmark.
988        studylevel['COURSE1'].score = studylevel['COURSE1'].passmark - 1
989        # GPA is still 0.
990        self.assertEqual(
991            self.student['studycourse']['100'].gpa_params_rectified[0], 0.0)
992        studylevel2 = createObject(u'waeup.StudentStudyLevel')
993        studylevel2.level = 200
994        studylevel2.level_session = 2006
995        self.student['studycourse'].addStudentStudyLevel(
996            self.certificate, studylevel2)
997        # Carry-over course has been autonatically added.
998        studylevel2['COURSE1'].score = 66
999        # The score of the carry-over course is now used for calculation of the
1000        # GPA at level 100 ...
1001        self.assertEqual(
1002            self.student['studycourse']['100'].gpa_params_rectified[0], 4.0)
1003        # ... but not at level 200
1004        self.assertEqual(
1005            self.student['studycourse']['200'].gpa_params_rectified[0], 0.0)
1006        return
1007
1008    def test_manage_payments(self):
1009        # Managers can add online school fee payment tickets
1010        # if certain requirements are met
1011        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1012        self.browser.open(self.payments_path)
1013        IWorkflowState(self.student).setState('cleared')
1014        self.browser.getLink("Add current session payment ticket").click()
1015        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1016        self.browser.getControl("Create ticket").click()
1017        self.assertMatches('...ticket created...',
1018                           self.browser.contents)
1019        ctrl = self.browser.getControl(name='val_id')
1020        value = ctrl.options[0]
1021        self.browser.getLink(value).click()
1022        self.assertMatches('...Amount Authorized...',
1023                           self.browser.contents)
1024        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
1025        payment_url = self.browser.url
1026        logfile = os.path.join(
1027            self.app['datacenter'].storage, 'logs', 'students.log')
1028        logcontent = open(logfile).read()
1029        self.assertTrue(
1030            ' zope.mgr - students.browser.OnlinePaymentAddFormPage - '
1031            'K1000000 - added: %s' % value
1032            in logcontent)
1033        # The pdf payment slip can't yet be opened
1034        #self.browser.open(payment_url + '/payment_slip.pdf')
1035        #self.assertMatches('...Ticket not yet paid...',
1036        #                   self.browser.contents)
1037
1038        # The same payment (with same p_item, p_session and
1039        # p_category) can be initialized a second time if the former
1040        # ticket is not yet paid.
1041        self.browser.open(self.payments_path)
1042        self.browser.getLink("Add current session payment ticket").click()
1043        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1044        self.browser.getControl("Create ticket").click()
1045        self.assertMatches('...Payment ticket created...',
1046                           self.browser.contents)
1047
1048        # The ticket can be found in the payments_catalog
1049        cat = queryUtility(ICatalog, name='payments_catalog')
1050        results = list(cat.searchResults(p_state=('unpaid', 'unpaid')))
1051        self.assertEqual(len(results), 2)
1052        self.assertTrue(results[0] is self.student['payments'][value])
1053        # Managers can approve the payment
1054        # If, by some reason, the state has already changed,
1055        # an access code is created after approval.
1056        IWorkflowState(self.student).setState('school fee paid')
1057        self.assertEqual(len(self.app['accesscodes']['SFE-0']), 0)
1058        self.browser.open(payment_url)
1059        self.browser.getLink("Approve payment").click()
1060        self.assertMatches(
1061            '...Payment approved...', self.browser.contents)
1062        # Approval is logged in students.log ...
1063        logcontent = open(logfile).read()
1064        self.assertTrue(
1065            'zope.mgr - students.browser.OnlinePaymentApproveView '
1066            '- K1000000 - schoolfee payment approved'
1067            in logcontent)
1068        # ... and in payments.log
1069        logfile = os.path.join(
1070            self.app['datacenter'].storage, 'logs', 'payments.log')
1071        logcontent = open(logfile).read()
1072        self.assertTrue(
1073            '"zope.mgr",K1000000,%s,schoolfee,40000.0,AP,,,,,,\n' % value
1074            in logcontent)
1075        # The authorized amount has been stored in the new access code
1076        self.assertEqual(
1077            self.app['accesscodes']['SFE-0'].values()[0].cost, 40000.0)
1078
1079        # The catalog has been updated
1080        results = list(cat.searchResults(p_state=('unpaid', 'unpaid')))
1081        self.assertTrue(len(results), 0)
1082        results = list(cat.searchResults(p_state=('paid', 'paid')))
1083        self.assertTrue(len(results), 1)
1084        self.assertTrue(results[0] is self.student['payments'][value])
1085
1086        # Payments can't be approved twice
1087        self.browser.open(payment_url + '/approve')
1088        self.assertMatches('...This ticket has already been paid...',
1089                          self.browser.contents)
1090
1091        # Now the first ticket is paid and no more ticket of same type
1092        # (with same p_item, p_session and p_category) can be added.
1093        # First we have to reset the workflow state.
1094        IWorkflowState(self.student).setState('cleared')
1095        self.browser.open(self.payments_path)
1096        self.browser.getLink("Add current session payment ticket").click()
1097        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1098        self.browser.getControl("Create ticket").click()
1099        self.assertMatches(
1100            '...This type of payment has already been made...',
1101            self.browser.contents)
1102
1103        # Managers can open the pdf payment slip
1104        self.browser.open(payment_url)
1105        self.browser.getLink("Download payment slip").click()
1106        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1107        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1108
1109        # Managers can remove online school fee payment tickets
1110        self.browser.open(self.payments_path)
1111        self.browser.getControl("Remove selected").click()
1112        self.assertMatches('...No payment selected...', self.browser.contents)
1113        ctrl = self.browser.getControl(name='val_id')
1114        value = ctrl.options[0]
1115        ctrl.getControl(value=value).selected = True
1116        self.browser.getControl("Remove selected", index=0).click()
1117        self.assertTrue('Successfully removed' in self.browser.contents)
1118
1119        # Managers can add online clearance payment tickets
1120        self.browser.open(self.payments_path + '/addop')
1121        self.browser.getControl(name="form.p_category").value = ['clearance']
1122        self.browser.getControl("Create ticket").click()
1123        self.assertMatches('...ticket created...',
1124                           self.browser.contents)
1125
1126        # Managers can approve the payment
1127        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
1128        ctrl = self.browser.getControl(name='val_id')
1129        value = ctrl.options[1] # The clearance payment is the second in the table
1130        self.browser.getLink(value).click()
1131        self.browser.open(self.browser.url + '/approve')
1132        self.assertMatches('...Payment approved...',
1133                          self.browser.contents)
1134        expected = '''...
1135        <td>
1136          <span>Paid</span>
1137        </td>...'''
1138        self.assertMatches(expected,self.browser.contents)
1139        # The new CLR-0 pin has been created
1140        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
1141        pin = self.app['accesscodes']['CLR-0'].keys()[0]
1142        ac = self.app['accesscodes']['CLR-0'][pin]
1143        self.assertEqual(ac.owner, self.student_id)
1144        self.assertEqual(ac.cost, 3456.0)
1145
1146        # Managers can add online transcript payment tickets
1147        self.browser.open(self.payments_path + '/addop')
1148        self.browser.getControl(name="form.p_category").value = ['transcript']
1149        self.browser.getControl("Create ticket").click()
1150        self.assertMatches('...ticket created...',
1151                           self.browser.contents)
1152
1153        # Managers can approve the payment
1154        self.assertEqual(len(self.app['accesscodes']['TSC-0']),0)
1155        ctrl = self.browser.getControl(name='val_id')
1156        value = ctrl.options[2] # The clearance payment is the third in the table
1157        self.browser.getLink(value).click()
1158        self.browser.open(self.browser.url + '/approve')
1159        self.assertMatches('...Payment approved...',
1160                          self.browser.contents)
1161        expected = '''...
1162        <td>
1163          <span>Paid</span>
1164        </td>...'''
1165        self.assertMatches(expected,self.browser.contents)
1166        # The new CLR-0 pin has been created
1167        self.assertEqual(len(self.app['accesscodes']['TSC-0']),1)
1168        pin = self.app['accesscodes']['TSC-0'].keys()[0]
1169        ac = self.app['accesscodes']['TSC-0'][pin]
1170        self.assertEqual(ac.owner, self.student_id)
1171        self.assertEqual(ac.cost, 4567.0)
1172        return
1173
1174    def test_add_transfer_payment(self):
1175        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1176        self.browser.open(self.payments_path)
1177        self.browser.getLink("Add current session payment ticket").click()
1178        self.browser.getControl(name="form.p_category").value = ['transfer']
1179        self.browser.getControl(name="new_programme").value = 'my new study course'
1180        self.browser.getControl("Create ticket").click()
1181        self.assertMatches('...ticket created...',
1182                           self.browser.contents)
1183        ctrl = self.browser.getControl(name='val_id')
1184        value = ctrl.options[0]
1185        self.browser.getLink(value).click()
1186        self.assertMatches('...my new study course...',
1187                           self.browser.contents)
1188        self.assertEqual(self.student['payments'][value].p_item, u'my new study course')
1189
1190    def test_manage_payments_bypass_ac_creation(self):
1191        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1192        self.browser.open(self.payments_path)
1193        IWorkflowState(self.student).setState('cleared')
1194        self.browser.getLink("Add current session payment ticket").click()
1195        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1196        self.browser.getControl("Create ticket").click()
1197        ctrl = self.browser.getControl(name='val_id')
1198        value = ctrl.options[0]
1199        self.browser.getLink(value).click()
1200        payment_url = self.browser.url
1201        logfile = os.path.join(
1202            self.app['datacenter'].storage, 'logs', 'students.log')
1203        # The ticket can be found in the payments_catalog
1204        cat = queryUtility(ICatalog, name='payments_catalog')
1205        results = list(cat.searchResults(p_state=('unpaid', 'unpaid')))
1206        self.assertTrue(len(results), 1)
1207        self.assertTrue(results[0] is self.student['payments'][value])
1208        # Managers can approve the payment
1209        self.browser.open(payment_url)
1210        self.browser.getLink("Approve payment").click()
1211        self.assertMatches('...Payment approved...',
1212                          self.browser.contents)
1213        # Approval is logged in students.log ...
1214        logcontent = open(logfile).read()
1215        self.assertTrue(
1216            'zope.mgr - students.browser.OnlinePaymentApproveView '
1217            '- K1000000 - schoolfee payment approved'
1218            in logcontent)
1219        # ... and in payments.log
1220        logfile = os.path.join(
1221            self.app['datacenter'].storage, 'logs', 'payments.log')
1222        logcontent = open(logfile).read()
1223        self.assertTrue(
1224            '"zope.mgr",K1000000,%s,schoolfee,40000.0,AP,,,,,,\n' % value
1225            in logcontent)
1226        # Student is in state school fee paid, no activation
1227        # code was necessary.
1228        self.assertEqual(self.student.state, 'school fee paid')
1229        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
1230        return
1231
1232    def test_payment_disabled(self):
1233        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1234        self.browser.open(self.payments_path)
1235        IWorkflowState(self.student).setState('cleared')
1236        self.browser.getLink("Add current session payment ticket").click()
1237        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1238        self.browser.getControl("Create ticket").click()
1239        self.assertMatches('...ticket created...',
1240                           self.browser.contents)
1241        self.app['configuration']['2004'].payment_disabled = ['sf_all']
1242        self.browser.getLink("Add current session payment ticket").click()
1243        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1244        self.browser.getControl("Create ticket").click()
1245        self.assertMatches('...This category of payments has been disabled...',
1246                           self.browser.contents)
1247        return
1248
1249    def test_manage_balance_payments(self):
1250
1251        # Login
1252        #self.browser.open(self.login_path)
1253        #self.browser.getControl(name="form.login").value = self.student_id
1254        #self.browser.getControl(name="form.password").value = 'spwd'
1255        #self.browser.getControl("Login").click()
1256
1257        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1258        self.browser.open(self.payments_path)
1259
1260        # Managers can add balance school fee payment tickets in any state.
1261        IWorkflowState(self.student).setState('courses registered')
1262        self.browser.open(self.payments_path)
1263        self.browser.getLink("Add balance payment ticket").click()
1264
1265        # Balance payment form is provided
1266        self.assertEqual(self.student.current_session, 2004)
1267        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1268        self.browser.getControl(name="form.balance_session").value = ['2004']
1269        self.browser.getControl(name="form.balance_level").value = ['300']
1270        self.browser.getControl(name="form.balance_amount").value = '-567.8'
1271        self.browser.getControl("Create ticket").click()
1272        self.assertMatches('...Amount must be greater than 0...',
1273                           self.browser.contents)
1274        self.browser.getControl(name="form.balance_amount").value = '0'
1275        self.browser.getControl("Create ticket").click()
1276        self.assertMatches('...Amount must be greater than 0...',
1277                           self.browser.contents)
1278        self.browser.getControl(name="form.balance_amount").value = '567.8'
1279        self.browser.getControl("Create ticket").click()
1280        self.assertMatches('...ticket created...',
1281                           self.browser.contents)
1282        ctrl = self.browser.getControl(name='val_id')
1283        value = ctrl.options[0]
1284        self.browser.getLink(value).click()
1285        self.assertMatches('...Amount Authorized...',
1286                           self.browser.contents)
1287        self.assertEqual(self.student['payments'][value].amount_auth, 567.8)
1288        # Payment attributes are properly set
1289        self.assertEqual(self.student['payments'][value].p_session, 2004)
1290        self.assertEqual(self.student['payments'][value].p_level, 300)
1291        self.assertEqual(self.student['payments'][value].p_item, u'Balance')
1292        self.assertEqual(self.student['payments'][value].p_category, 'schoolfee')
1293        # Adding payment tickets is logged.
1294        logfile = os.path.join(
1295            self.app['datacenter'].storage, 'logs', 'students.log')
1296        logcontent = open(logfile).read()
1297        self.assertTrue('zope.mgr - students.browser.BalancePaymentAddFormPage '
1298                        '- K1000000 - added: %s' % value in logcontent)
1299
1300    def test_manage_accommodation(self):
1301        logfile = os.path.join(
1302            self.app['datacenter'].storage, 'logs', 'students.log')
1303        # Managers can add online booking fee payment tickets and open the
1304        # callback view (see test_manage_payments)
1305        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1306        self.browser.open(self.payments_path)
1307        self.browser.getLink("Add current session payment ticket").click()
1308        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1309        # If student is not in accommodation session, payment cannot be processed
1310        self.app['hostels'].accommodation_session = 2011
1311        self.browser.getControl("Create ticket").click()
1312        self.assertMatches('...Your current session does not match...',
1313                           self.browser.contents)
1314        self.app['hostels'].accommodation_session = 2004
1315        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1316        self.browser.getControl("Create ticket").click()
1317        ctrl = self.browser.getControl(name='val_id')
1318        value = ctrl.options[0]
1319        self.browser.getLink(value).click()
1320        self.browser.open(self.browser.url + '/approve')
1321        # The new HOS-0 pin has been created
1322        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
1323        pin = self.app['accesscodes']['HOS-0'].keys()[0]
1324        ac = self.app['accesscodes']['HOS-0'][pin]
1325        self.assertEqual(ac.owner, self.student_id)
1326        parts = pin.split('-')[1:]
1327        sfeseries, sfenumber = parts
1328        # Managers can use HOS code and book a bed space with it
1329        self.browser.open(self.acco_path)
1330        self.browser.getControl("Book accommodation").click()
1331        self.assertMatches('...You are in the wrong...',
1332                           self.browser.contents)
1333        IWorkflowInfo(self.student).fireTransition('admit')
1334        # An existing HOS code can only be used if students
1335        # are in accommodation session
1336        self.student['studycourse'].current_session = 2003
1337        self.browser.getControl("Book accommodation").click()
1338        self.assertMatches('...Your current session does not match...',
1339                           self.browser.contents)
1340        self.student['studycourse'].current_session = 2004
1341        # All requirements are met and ticket can be created
1342        self.browser.getControl("Book accommodation").click()
1343        self.assertMatches('...Activation Code:...',
1344                           self.browser.contents)
1345        self.browser.getControl(name="ac_series").value = sfeseries
1346        self.browser.getControl(name="ac_number").value = sfenumber
1347        self.browser.getControl("Create bed ticket").click()
1348        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1349                           self.browser.contents)
1350        # Bed has been allocated
1351        bed1 = self.app['hostels']['hall-1']['hall-1_A_101_A']
1352        self.assertTrue(bed1.owner == self.student_id)
1353        # BedTicketAddPage is now blocked
1354        self.browser.getControl("Book accommodation").click()
1355        self.assertMatches('...You already booked a bed space...',
1356            self.browser.contents)
1357        # The bed ticket displays the data correctly
1358        self.browser.open(self.acco_path + '/2004')
1359        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1360                           self.browser.contents)
1361        self.assertMatches('...2004/2005...', self.browser.contents)
1362        self.assertMatches('...regular_male_fr...', self.browser.contents)
1363        self.assertMatches('...%s...' % pin, self.browser.contents)
1364        # Booking is properly logged
1365        logcontent = open(logfile).read()
1366        self.assertTrue('zope.mgr - students.browser.BedTicketAddPage '
1367            '- K1000000 - booked: hall-1_A_101_A' in logcontent)
1368        # Managers can relocate students if the student's bed_type has changed
1369        self.browser.getLink("Relocate student").click()
1370        self.assertMatches(
1371            "...Student can't be relocated...", self.browser.contents)
1372        self.student.sex = u'f'
1373        self.browser.getLink("Relocate student").click()
1374        self.assertMatches(
1375            "...Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
1376        self.assertTrue(bed1.owner == NOT_OCCUPIED)
1377        bed2 = self.app['hostels']['hall-1']['hall-1_A_101_B']
1378        self.assertTrue(bed2.owner == self.student_id)
1379        self.assertTrue(self.student['accommodation'][
1380            '2004'].bed_type == u'regular_female_fr')
1381        # Relocation is properly logged
1382        logcontent = open(logfile).read()
1383        self.assertTrue('zope.mgr - students.accommodation.BedTicket '
1384            '- K1000000 - relocated: hall-1_A_101_B' in logcontent)
1385        # The payment object still shows the original payment item
1386        payment_id = self.student['payments'].keys()[0]
1387        payment = self.student['payments'][payment_id]
1388        self.assertTrue(payment.p_item == u'regular_male_fr')
1389        # Managers can relocate students if the bed's bed_type has changed
1390        bed1.bed_type = u'regular_female_fr'
1391        bed2.bed_type = u'regular_male_fr'
1392        notify(grok.ObjectModifiedEvent(bed1))
1393        notify(grok.ObjectModifiedEvent(bed2))
1394        self.browser.getLink("Relocate student").click()
1395        self.assertMatches(
1396            "...Student relocated...", self.browser.contents)
1397        self.assertMatches(
1398            "... Hall 1, Block A, Room 101, Bed A...", self.browser.contents)
1399        self.assertMatches(bed1.owner, self.student_id)
1400        self.assertMatches(bed2.owner, NOT_OCCUPIED)
1401        # Managers can't relocate students if bed is reserved
1402        self.student.sex = u'm'
1403        bed1.bed_type = u'regular_female_reserved'
1404        notify(grok.ObjectModifiedEvent(bed1))
1405        self.browser.getLink("Relocate student").click()
1406        self.assertMatches(
1407            "...Students in reserved beds can't be relocated...",
1408            self.browser.contents)
1409        # Managers can relocate students if booking has been cancelled but
1410        # other bed space has been manually allocated after cancellation
1411        old_owner = bed1.releaseBed()
1412        self.assertMatches(old_owner, self.student_id)
1413        bed2.owner = self.student_id
1414        self.browser.open(self.acco_path + '/2004')
1415        self.assertMatches(
1416            "...booking cancelled...", self.browser.contents)
1417        self.browser.getLink("Relocate student").click()
1418        # We didn't informed the catalog therefore the new owner is not found
1419        self.assertMatches(
1420            "...There is no free bed in your category regular_male_fr...",
1421            self.browser.contents)
1422        # Now we fire the event properly
1423        notify(grok.ObjectModifiedEvent(bed2))
1424        self.browser.getLink("Relocate student").click()
1425        self.assertMatches(
1426            "...Student relocated...", self.browser.contents)
1427        self.assertMatches(
1428            "... Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
1429          # Managers can delete bed tickets
1430        self.browser.open(self.acco_path)
1431        ctrl = self.browser.getControl(name='val_id')
1432        value = ctrl.options[0]
1433        ctrl.getControl(value=value).selected = True
1434        self.browser.getControl("Remove selected", index=0).click()
1435        self.assertMatches('...Successfully removed...', self.browser.contents)
1436        # The bed has been properly released by the event handler
1437        self.assertMatches(bed1.owner, NOT_OCCUPIED)
1438        self.assertMatches(bed2.owner, NOT_OCCUPIED)
1439        return
1440
1441    def test_manage_workflow(self):
1442        # Managers can pass through the whole workflow
1443        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1444        student = self.app['students'][self.student_id]
1445        self.browser.open(self.trigtrans_path)
1446        self.assertTrue(student.clearance_locked)
1447        self.browser.getControl(name="transition").value = ['admit']
1448        self.browser.getControl("Save").click()
1449        self.assertTrue(student.clearance_locked)
1450        self.browser.getControl(name="transition").value = ['start_clearance']
1451        self.browser.getControl("Save").click()
1452        self.assertFalse(student.clearance_locked)
1453        self.browser.getControl(name="transition").value = ['request_clearance']
1454        self.browser.getControl("Save").click()
1455        self.assertTrue(student.clearance_locked)
1456        self.browser.getControl(name="transition").value = ['clear']
1457        self.browser.getControl("Save").click()
1458        # Managers approve payment, they do not pay
1459        self.assertFalse('pay_first_school_fee' in self.browser.contents)
1460        self.browser.getControl(
1461            name="transition").value = ['approve_first_school_fee']
1462        self.browser.getControl("Save").click()
1463        self.browser.getControl(name="transition").value = ['reset6']
1464        self.browser.getControl("Save").click()
1465        # In state returning the pay_school_fee transition triggers some
1466        # changes of attributes
1467        self.browser.getControl(name="transition").value = ['approve_school_fee']
1468        self.browser.getControl("Save").click()
1469        self.assertEqual(student['studycourse'].current_session, 2005) # +1
1470        self.assertEqual(student['studycourse'].current_level, 200) # +100
1471        self.assertEqual(student['studycourse'].current_verdict, '0') # 0 = Zero = not set
1472        self.assertEqual(student['studycourse'].previous_verdict, 'A')
1473        self.browser.getControl(name="transition").value = ['register_courses']
1474        self.browser.getControl("Save").click()
1475        self.browser.getControl(name="transition").value = ['validate_courses']
1476        self.browser.getControl("Save").click()
1477        self.browser.getControl(name="transition").value = ['return']
1478        self.browser.getControl("Save").click()
1479        return
1480
1481    def test_manage_pg_workflow(self):
1482        # Managers can pass through the whole workflow
1483        IWorkflowState(self.student).setState('school fee paid')
1484        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1485        self.browser.open(self.trigtrans_path)
1486        self.assertTrue('<option value="reset6">' in self.browser.contents)
1487        self.assertTrue('<option value="register_courses">' in self.browser.contents)
1488        self.assertTrue('<option value="reset5">' in self.browser.contents)
1489        self.certificate.study_mode = 'pg_ft'
1490        self.browser.open(self.trigtrans_path)
1491        self.assertFalse('<option value="reset6">' in self.browser.contents)
1492        self.assertFalse('<option value="register_courses">' in self.browser.contents)
1493        self.assertTrue('<option value="reset5">' in self.browser.contents)
1494        return
1495
1496    def test_manage_import(self):
1497        # Managers can import student data files
1498        datacenter_path = 'http://localhost/app/datacenter'
1499        # Prepare a csv file for students
1500        open('students.csv', 'wb').write(
1501"""firstname,lastname,reg_number,date_of_birth,matric_number,email,phone,sex,password
1502Aaren,Pieri,1,1990-01-02,100000,aa@aa.ng,1234,m,mypwd1
1503Claus,Finau,2,1990-01-03,100001,aa@aa.ng,1234,m,mypwd1
1504Brit,Berson,3,1990-01-04,100001,aa@aa.ng,1234,m,mypwd1
1505""")
1506        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1507        self.browser.open(datacenter_path)
1508        self.browser.getLink('Upload data').click()
1509        filecontents = StringIO(open('students.csv', 'rb').read())
1510        filewidget = self.browser.getControl(name='uploadfile:file')
1511        filewidget.add_file(filecontents, 'text/plain', 'students.csv')
1512        self.browser.getControl(name='SUBMIT').click()
1513        self.browser.getLink('Process data').click()
1514        self.browser.getLink("Switch maintenance mode").click()
1515        button = lookup_submit_value(
1516            'select', 'students_zope.mgr.csv', self.browser)
1517        button.click()
1518        importerselect = self.browser.getControl(name='importer')
1519        modeselect = self.browser.getControl(name='mode')
1520        importerselect.getControl('Student Processor').selected = True
1521        modeselect.getControl(value='create').selected = True
1522        self.browser.getControl('Proceed to step 3').click()
1523        self.assertTrue('Header fields OK' in self.browser.contents)
1524        self.browser.getControl('Perform import').click()
1525        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
1526        self.assertTrue('Successfully processed 2 rows' in self.browser.contents)
1527        self.assertTrue('Batch processing finished' in self.browser.contents)
1528        open('studycourses.csv', 'wb').write(
1529"""reg_number,matric_number,certificate,current_session,current_level
15301,,CERT1,2008,100
1531,100001,CERT1,2008,100
1532,100002,CERT1,2008,100
1533""")
1534        self.browser.open(datacenter_path)
1535        self.browser.getLink('Upload data').click()
1536        filecontents = StringIO(open('studycourses.csv', 'rb').read())
1537        filewidget = self.browser.getControl(name='uploadfile:file')
1538        filewidget.add_file(filecontents, 'text/plain', 'studycourses.csv')
1539        self.browser.getControl(name='SUBMIT').click()
1540        self.browser.getLink('Process data').click()
1541        # Meanwhile maintenance mode is disabled again.
1542        self.browser.getLink("Switch maintenance mode").click()
1543        button = lookup_submit_value(
1544            'select', 'studycourses_zope.mgr.csv', self.browser)
1545        button.click()
1546        importerselect = self.browser.getControl(name='importer')
1547        modeselect = self.browser.getControl(name='mode')
1548        importerselect.getControl(
1549            'StudentStudyCourse Processor (update only)').selected = True
1550        modeselect.getControl(value='create').selected = True
1551        self.browser.getControl('Proceed to step 3').click()
1552        self.assertTrue('Update mode only' in self.browser.contents)
1553        self.browser.getControl('Proceed to step 3').click()
1554        self.assertTrue('Header fields OK' in self.browser.contents)
1555        self.browser.getControl('Perform import').click()
1556        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
1557        self.assertTrue('Successfully processed 2 rows'
1558                        in self.browser.contents)
1559        # The students are properly indexed and we can
1560        # thus find a student in  the department
1561        self.browser.open(self.manage_container_path)
1562        self.browser.getControl(name="searchtype").value = ['depcode']
1563        self.browser.getControl(name="searchterm").value = 'dep1'
1564        self.browser.getControl("Find student(s)").click()
1565        self.assertTrue('Aaren Pieri' in self.browser.contents)
1566        # We can search for a new student by name ...
1567        self.browser.getControl(name="searchtype").value = ['fullname']
1568        self.browser.getControl(name="searchterm").value = 'Claus'
1569        self.browser.getControl("Find student(s)").click()
1570        self.assertTrue('Claus Finau' in self.browser.contents)
1571        # ... and check if the imported password has been properly set
1572        ctrl = self.browser.getControl(name='entries')
1573        value = ctrl.options[0]
1574        claus = self.app['students'][value]
1575        self.assertTrue(IUserAccount(claus).checkPassword('mypwd1'))
1576        return
1577
1578    def init_clearance_officer(self):
1579        # Create clearance officer
1580        self.app['users'].addUser('mrclear', SECRET)
1581        self.app['users']['mrclear'].email = 'mrclear@foo.ng'
1582        self.app['users']['mrclear'].title = 'Carlo Pitter'
1583        # Clearance officers need not necessarily to get
1584        # the StudentsOfficer site role
1585        #prmglobal = IPrincipalRoleManager(self.app)
1586        #prmglobal.assignRoleToPrincipal('waeup.StudentsOfficer', 'mrclear')
1587        # Assign local ClearanceOfficer role
1588        self.department = self.app['faculties']['fac1']['dep1']
1589        prmlocal = IPrincipalRoleManager(self.department)
1590        prmlocal.assignRoleToPrincipal('waeup.local.ClearanceOfficer', 'mrclear')
1591        IWorkflowState(self.student).setState('clearance started')
1592        # Add another student for testing
1593        other_student = Student()
1594        other_student.firstname = u'Dep2'
1595        other_student.lastname = u'Student'
1596        self.app['students'].addStudent(other_student)
1597        self.other_student_path = (
1598            'http://localhost/app/students/%s' % other_student.student_id)
1599        # Login as clearance officer
1600        self.browser.open(self.login_path)
1601        self.browser.getControl(name="form.login").value = 'mrclear'
1602        self.browser.getControl(name="form.password").value = SECRET
1603        self.browser.getControl("Login").click()
1604
1605    def test_handle_clearance_by_co(self):
1606        self.init_clearance_officer()
1607        self.assertMatches('...You logged in...', self.browser.contents)
1608        # CO is landing on index page.
1609        self.assertEqual(self.browser.url, 'http://localhost/app/index')
1610        # CO can see his roles
1611        self.browser.getLink("My Roles").click()
1612        self.assertMatches(
1613            '...<div>Academics Officer (view only)</div>...',
1614            self.browser.contents)
1615        # But not his local role ...
1616        self.assertFalse('Clearance Officer' in self.browser.contents)
1617        # ... because we forgot to notify the department that the local role
1618        # has changed.
1619        notify(LocalRoleSetEvent(
1620            self.department, 'waeup.local.ClearanceOfficer', 'mrclear',
1621            granted=True))
1622        self.browser.open('http://localhost/app/users/mrclear/my_roles')
1623        self.assertTrue('Clearance Officer' in self.browser.contents)
1624        self.assertMatches(
1625            '...<a href="http://localhost/app/faculties/fac1/dep1">...',
1626            self.browser.contents)
1627        # CO can view the student ...
1628        self.browser.open(self.clearance_path)
1629        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1630        self.assertEqual(self.browser.url, self.clearance_path)
1631        # ... but not other students.
1632        self.assertRaises(
1633            Unauthorized, self.browser.open, self.other_student_path)
1634        # Clearance is disabled for this session.
1635        self.browser.open(self.clearance_path)
1636        self.assertFalse('Clear student' in self.browser.contents)
1637        self.browser.open(self.student_path + '/clear')
1638        self.assertTrue('Clearance is disabled for this session'
1639            in self.browser.contents)
1640        self.app['configuration']['2004'].clearance_enabled = True
1641        # Only in state clearance requested the CO does see the 'Clear' button.
1642        self.browser.open(self.clearance_path)
1643        self.assertFalse('Clear student' in self.browser.contents)
1644        self.browser.open(self.student_path + '/clear')
1645        self.assertTrue('Student is in wrong state.'
1646            in self.browser.contents)
1647        IWorkflowInfo(self.student).fireTransition('request_clearance')
1648        self.browser.open(self.clearance_path)
1649        self.assertTrue('Clear student' in self.browser.contents)
1650        self.browser.getLink("Clear student").click()
1651        self.assertTrue('Student has been cleared' in self.browser.contents)
1652        self.assertTrue('cleared' in self.browser.contents)
1653        self.browser.open(self.history_path)
1654        self.assertTrue('Cleared by Carlo Pitter' in self.browser.contents)
1655        # Hide real name.
1656        self.app['users']['mrclear'].public_name = 'My Public Name'
1657        self.browser.open(self.clearance_path)
1658        self.browser.getLink("Reject clearance").click()
1659        self.assertEqual(
1660            self.browser.url, self.student_path + '/reject_clearance')
1661        # Type comment why.
1662        self.browser.getControl(name="form.officer_comment").value = (
1663            'Dear Student,\n'
1664            'You did not fill properly.')
1665        self.browser.getControl("Save comment").click()
1666        self.assertTrue('Clearance has been annulled' in self.browser.contents)
1667        url = ('http://localhost/app/students/K1000000/'
1668              'contactstudent?body=Dear+Student%2C%0AYou+did+not+fill+properly.'
1669              '&subject=Clearance+has+been+annulled.')
1670        # CO does now see the prefilled contact form and can send a message.
1671        self.assertEqual(self.browser.url, url)
1672        self.assertTrue('clearance started' in self.browser.contents)
1673        self.assertTrue('name="form.subject" size="20" type="text" '
1674            'value="Clearance has been annulled."'
1675            in self.browser.contents)
1676        self.assertTrue('name="form.body" rows="10" >Dear Student,'
1677            in self.browser.contents)
1678        self.browser.getControl("Send message now").click()
1679        self.assertTrue('Your message has been sent' in self.browser.contents)
1680        # The comment has been stored ...
1681        self.assertEqual(self.student.officer_comment,
1682            u'Dear Student,\nYou did not fill properly.')
1683        # ... and logged.
1684        logfile = os.path.join(
1685            self.app['datacenter'].storage, 'logs', 'students.log')
1686        logcontent = open(logfile).read()
1687        self.assertTrue(
1688            'INFO - mrclear - students.browser.StudentRejectClearancePage - '
1689            'K1000000 - comment: Dear Student,<br>You did not fill '
1690            'properly.\n' in logcontent)
1691        self.browser.open(self.history_path)
1692        self.assertTrue("Reset to 'clearance started' by My Public Name" in
1693            self.browser.contents)
1694        IWorkflowInfo(self.student).fireTransition('request_clearance')
1695        self.browser.open(self.clearance_path)
1696        self.browser.getLink("Reject clearance").click()
1697        self.browser.getControl("Save comment").click()
1698        self.assertTrue('Clearance request has been rejected'
1699            in self.browser.contents)
1700        self.assertTrue('clearance started' in self.browser.contents)
1701        # The CO can't clear students if not in state
1702        # clearance requested.
1703        self.browser.open(self.student_path + '/clear')
1704        self.assertTrue('Student is in wrong state'
1705            in self.browser.contents)
1706        # The CO can go to his department throug the my_roles page ...
1707        self.browser.open('http://localhost/app/users/mrclear/my_roles')
1708        self.browser.getLink("http://localhost/app/faculties/fac1/dep1").click()
1709        # ... and view the list of students.
1710        self.browser.getLink("Show students").click()
1711        self.browser.getControl(name="session").value = ['2004']
1712        self.browser.getControl(name="level").value = ['200']
1713        self.browser.getControl("Show").click()
1714        self.assertFalse(self.student_id in self.browser.contents)
1715        self.browser.getControl(name="session").value = ['2004']
1716        self.browser.getControl(name="level").value = ['100']
1717        self.browser.getControl("Show").click()
1718        self.assertTrue(self.student_id in self.browser.contents)
1719        # The comment is indicated by 'yes'.
1720        self.assertTrue('<td><span>yes</span></td>' in self.browser.contents)
1721        # Check if the enquiries form is not pre-filled with officer_comment
1722        # (regression test).
1723        self.browser.getLink("Logout").click()
1724        self.browser.open('http://localhost/app/enquiries')
1725        self.assertFalse(
1726            'You did not fill properly'
1727            in self.browser.contents)
1728        # When a student is cleared the comment is automatically deleted
1729        IWorkflowInfo(self.student).fireTransition('request_clearance')
1730        IWorkflowInfo(self.student).fireTransition('clear')
1731        self.assertEqual(self.student.officer_comment, None)
1732        return
1733
1734    def test_handle_temp_clearance_by_co(self):
1735        self.app['configuration']['2004'].clearance_enabled = True
1736        self.init_clearance_officer()
1737        # Hide real name.
1738        self.app['users']['mrclear'].public_name = 'My Public Name'
1739        IWorkflowInfo(self.student).fireTransition('request_clearance')
1740        #notify(LocalRoleSetEvent(
1741        #    self.department, 'waeup.local.ClearanceOfficer', 'mrclear',
1742        #    granted=True))
1743        # CO can view the student ...
1744        self.browser.open(self.clearance_path)
1745        self.browser.getLink("Clear student temporarily").click()
1746        self.assertEqual(
1747            self.browser.url, self.student_path + '/temp_clearance')
1748        # Type comment why.
1749        self.browser.getControl(name="form.officer_comment").value = (
1750            'Dear Student,\n'
1751            'Your birth cert is missing.')
1752        self.browser.getControl(
1753            'Save comment and clear student temporarily now').click()
1754        self.assertTrue('Student has been temporarily cleared.'
1755            in self.browser.contents)
1756        url = ('http://localhost/app/students/K1000000/'
1757              'contactstudent?body=Dear+Student%2C%0AYour+birth+cert+is+missing.'
1758              '&subject=You+have+been+temporarily+cleared.')
1759        # CO does now see the prefilled contact form and can send a message.
1760        self.assertEqual(self.browser.url, url)
1761        self.assertTrue('clearance requested' in self.browser.contents)
1762        self.assertTrue('name="form.subject" size="20" type="text" '
1763            'value="You have been temporarily cleared."'
1764            in self.browser.contents)
1765        self.assertTrue('name="form.body" rows="10" >Dear Student,'
1766            in self.browser.contents)
1767        self.browser.getControl("Send message now").click()
1768        self.assertTrue('Your message has been sent' in self.browser.contents)
1769        # The comment has been stored ...
1770        self.assertEqual(self.student.officer_comment,
1771            'Temporarily cleared by My Public Name. Officer\'s comment:\n'
1772            'Dear Student,\nYour birth cert is missing.')
1773        # ... and logged.
1774        logfile = os.path.join(
1775            self.app['datacenter'].storage, 'logs', 'students.log')
1776        logcontent = open(logfile).read()
1777        self.assertTrue(
1778            'INFO - mrclear - students.browser.StudentTempClearancePage - '
1779            'K1000000 - comment: Dear Student,<br>Your birth cert is '
1780            'missing.\n' in logcontent)
1781        # CO can view the student bus no longer see the temp clearance button ...
1782        self.browser.open(self.clearance_path)
1783        self.assertFalse('Clear student temporarily' in self.browser.contents)
1784        return
1785
1786    def test_handle_mass_clearance_by_co(self):
1787        self.init_clearance_officer()
1788        # Additional setups according to test above
1789        notify(LocalRoleSetEvent(
1790            self.department, 'waeup.local.ClearanceOfficer', 'mrclear',
1791            granted=True))
1792        self.app['configuration']['2004'].clearance_enabled = True
1793        IWorkflowState(self.student).setState('clearance requested')
1794        # Update the catalog
1795        notify(grok.ObjectModifiedEvent(self.student))
1796        # The CO can go to the department and clear all students in department
1797        self.browser.open('http://localhost/app/faculties/fac1/dep1')
1798        self.browser.getLink("Clear all students").click()
1799        self.assertTrue('1 students have been cleared' in self.browser.contents)
1800        self.browser.open(self.history_path)
1801        self.assertTrue('Cleared by Carlo Pitter' in self.browser.contents)
1802        logfile = os.path.join(
1803            self.app['datacenter'].storage, 'logs', 'students.log')
1804        logcontent = open(logfile).read()
1805        self.assertTrue(
1806            'INFO - mrclear - K1000000 - Cleared' in logcontent)
1807        self.browser.open('http://localhost/app/faculties/fac1/dep1')
1808        self.browser.getLink("Clear all students").click()
1809        self.assertTrue('0 students have been cleared' in self.browser.contents)
1810        return
1811
1812    def test_handle_courses_by_ca(self):
1813        self.app['users'].addUser('mrsadvise', SECRET)
1814        self.app['users']['mrsadvise'].email = 'mradvise@foo.ng'
1815        self.app['users']['mrsadvise'].title = u'Helen Procter'
1816        # Assign local CourseAdviser100 role for a certificate
1817        cert = self.app['faculties']['fac1']['dep1'].certificates['CERT1']
1818        prmlocal = IPrincipalRoleManager(cert)
1819        prmlocal.assignRoleToPrincipal('waeup.local.CourseAdviser100', 'mrsadvise')
1820        IWorkflowState(self.student).setState('school fee paid')
1821        # Login as course adviser.
1822        self.browser.open(self.login_path)
1823        self.browser.getControl(name="form.login").value = 'mrsadvise'
1824        self.browser.getControl(name="form.password").value = SECRET
1825        self.browser.getControl("Login").click()
1826        self.assertMatches('...You logged in...', self.browser.contents)
1827        # CO can see his roles.
1828        self.browser.getLink("My Roles").click()
1829        self.assertMatches(
1830            '...<div>Academics Officer (view only)</div>...',
1831            self.browser.contents)
1832        # But not his local role ...
1833        self.assertFalse('Course Adviser' in self.browser.contents)
1834        # ... because we forgot to notify the certificate that the local role
1835        # has changed.
1836        notify(LocalRoleSetEvent(
1837            cert, 'waeup.local.CourseAdviser100', 'mrsadvise', granted=True))
1838        self.browser.open('http://localhost/app/users/mrsadvise/my_roles')
1839        self.assertTrue('Course Adviser 100L' in self.browser.contents)
1840        self.assertMatches(
1841            '...<a href="http://localhost/app/faculties/fac1/dep1/certificates/CERT1">...',
1842            self.browser.contents)
1843        # CA can view the student ...
1844        self.browser.open(self.student_path)
1845        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1846        self.assertEqual(self.browser.url, self.student_path)
1847        # ... but not other students.
1848        other_student = Student()
1849        other_student.firstname = u'Dep2'
1850        other_student.lastname = u'Student'
1851        self.app['students'].addStudent(other_student)
1852        other_student_path = (
1853            'http://localhost/app/students/%s' % other_student.student_id)
1854        self.assertRaises(
1855            Unauthorized, self.browser.open, other_student_path)
1856        # We add study level 110 to the student's studycourse.
1857        studylevel = StudentStudyLevel()
1858        studylevel.level = 110
1859        self.student['studycourse'].addStudentStudyLevel(
1860            cert,studylevel)
1861        L110_student_path = self.studycourse_path + '/110'
1862        # The CA can neither see the Validate nor the Edit button.
1863        self.browser.open(L110_student_path)
1864        self.assertFalse('Validate courses' in self.browser.contents)
1865        self.assertFalse('Edit' in self.browser.contents)
1866        IWorkflowInfo(self.student).fireTransition('register_courses')
1867        self.browser.open(L110_student_path)
1868        self.assertFalse('Validate courses' in self.browser.contents)
1869        self.assertFalse('Edit' in self.browser.contents)
1870        # Only in state courses registered and only if the current level
1871        # corresponds with the name of the study level object
1872        # the 100L CA does see the 'Validate' button but not
1873        # the edit button.
1874        self.student['studycourse'].current_level = 110
1875        self.browser.open(L110_student_path)
1876        self.assertFalse('Edit' in self.browser.contents)
1877        self.assertTrue('Validate courses' in self.browser.contents)
1878        # But a 100L CA does not see the button at other levels.
1879        studylevel2 = StudentStudyLevel()
1880        studylevel2.level = 200
1881        self.student['studycourse'].addStudentStudyLevel(
1882            cert,studylevel2)
1883        L200_student_path = self.studycourse_path + '/200'
1884        self.browser.open(L200_student_path)
1885        self.assertFalse('Edit' in self.browser.contents)
1886        self.assertFalse('Validate courses' in self.browser.contents)
1887        self.browser.open(L110_student_path)
1888        self.browser.getLink("Validate courses").click()
1889        self.assertTrue('Course list has been validated' in self.browser.contents)
1890        self.assertTrue('courses validated' in self.browser.contents)
1891        self.assertEqual(self.student['studycourse']['110'].validated_by,
1892            'Helen Procter')
1893        self.assertMatches(
1894            '<YYYY-MM-DD hh:mm:ss>',
1895            self.student['studycourse']['110'].validation_date.strftime(
1896                "%Y-%m-%d %H:%M:%S"))
1897        self.browser.getLink("Reject courses").click()
1898        self.assertTrue('Course list request has been annulled.'
1899            in self.browser.contents)
1900        urlmessage = 'Course+list+request+has+been+annulled.'
1901        self.assertEqual(self.browser.url, self.student_path +
1902            '/contactstudent?subject=%s' % urlmessage)
1903        self.assertTrue('school fee paid' in self.browser.contents)
1904        self.assertTrue(self.student['studycourse']['110'].validated_by is None)
1905        self.assertTrue(self.student['studycourse']['110'].validation_date is None)
1906        IWorkflowInfo(self.student).fireTransition('register_courses')
1907        self.browser.open(L110_student_path)
1908        self.browser.getLink("Reject courses").click()
1909        self.assertTrue('Course list has been unregistered'
1910            in self.browser.contents)
1911        self.assertTrue('school fee paid' in self.browser.contents)
1912        # CA does now see the contact form and can send a message.
1913        self.browser.getControl(name="form.subject").value = 'Important subject'
1914        self.browser.getControl(name="form.body").value = 'Course list rejected'
1915        self.browser.getControl("Send message now").click()
1916        self.assertTrue('Your message has been sent' in self.browser.contents)
1917        # The CA does now see the Edit button and can edit
1918        # current study level.
1919        self.browser.open(L110_student_path)
1920        self.browser.getLink("Edit").click()
1921        self.assertTrue('Edit course list of 100 (Year 1) on 1st probation'
1922            in self.browser.contents)
1923        # The CA can't validate courses if not in state
1924        # courses registered.
1925        self.browser.open(L110_student_path + '/validate_courses')
1926        self.assertTrue('Student is in the wrong state'
1927            in self.browser.contents)
1928        # The CA can go to his certificate through the my_roles page ...
1929        self.browser.open('http://localhost/app/users/mrsadvise/my_roles')
1930        self.browser.getLink(
1931            "http://localhost/app/faculties/fac1/dep1/certificates/CERT1").click()
1932        # ... and view the list of students.
1933        self.browser.getLink("Show students").click()
1934        self.browser.getControl(name="session").value = ['2004']
1935        self.browser.getControl(name="level").value = ['100']
1936        self.browser.getControl("Show").click()
1937        self.assertTrue(self.student_id in self.browser.contents)
1938
1939    def test_change_current_mode(self):
1940        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1941        self.browser.open(self.clearance_path)
1942        self.assertFalse('Employer' in self.browser.contents)
1943        self.browser.open(self.manage_clearance_path)
1944        self.assertFalse('Employer' in self.browser.contents)
1945        self.browser.open(self.edit_clearance_path)
1946        self.assertFalse('Employer' in self.browser.contents)
1947        # Now we change the study mode of the certificate and a different
1948        # interface is used by clearance views.
1949        self.certificate.study_mode = 'pg_ft'
1950        # Invariants are not being checked here?!
1951        self.certificate.end_level = 100
1952        self.browser.open(self.clearance_path)
1953        self.assertTrue('Employer' in self.browser.contents)
1954        self.browser.open(self.manage_clearance_path)
1955        self.assertTrue('Employer' in self.browser.contents)
1956        IWorkflowState(self.student).setState('clearance started')
1957        self.browser.open(self.edit_clearance_path)
1958        self.assertTrue('Employer' in self.browser.contents)
1959
1960    def test_find_students_in_faculties(self):
1961        # Create local students manager in faculty
1962        self.app['users'].addUser('mrmanager', SECRET)
1963        self.app['users']['mrmanager'].email = 'mrmanager@foo.ng'
1964        self.app['users']['mrmanager'].title = u'Volk Wagen'
1965        # Assign LocalStudentsManager role for faculty
1966        fac = self.app['faculties']['fac1']
1967        prmlocal = IPrincipalRoleManager(fac)
1968        prmlocal.assignRoleToPrincipal(
1969            'waeup.local.LocalStudentsManager', 'mrmanager')
1970        notify(LocalRoleSetEvent(
1971            fac, 'waeup.local.LocalStudentsManager', 'mrmanager',
1972            granted=True))
1973        # Login as manager
1974        self.browser.open(self.login_path)
1975        self.browser.getControl(name="form.login").value = 'mrmanager'
1976        self.browser.getControl(name="form.password").value = SECRET
1977        self.browser.getControl("Login").click()
1978        self.assertMatches('...You logged in...', self.browser.contents)
1979        # Manager can see his roles
1980        self.browser.getLink("My Roles").click()
1981        self.assertMatches(
1982            '...<span>Students Manager</span>...',
1983            self.browser.contents)
1984        # The manager can go to his faculty
1985        self.browser.getLink(
1986            "http://localhost/app/faculties/fac1").click()
1987        # and find students
1988        self.browser.getLink("Find students").click()
1989        self.browser.getControl("Find student").click()
1990        self.assertTrue('Empty search string' in self.browser.contents)
1991        self.browser.getControl(name="searchtype").value = ['student_id']
1992        self.browser.getControl(name="searchterm").value = self.student_id
1993        self.browser.getControl("Find student").click()
1994        self.assertTrue('Anna Tester' in self.browser.contents)
1995
1996    def test_activate_deactivate_buttons(self):
1997        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1998        self.browser.open(self.student_path)
1999        self.browser.getLink("Deactivate").click()
2000        self.assertTrue(
2001            'Student account has been deactivated.' in self.browser.contents)
2002        self.assertTrue(
2003            'Base Data (account deactivated)' in self.browser.contents)
2004        self.assertTrue(self.student.suspended)
2005        self.browser.getLink("Activate").click()
2006        self.assertTrue(
2007            'Student account has been activated.' in self.browser.contents)
2008        self.assertFalse(
2009            'Base Data (account deactivated)' in self.browser.contents)
2010        self.assertFalse(self.student.suspended)
2011        # History messages have been added ...
2012        self.browser.getLink("History").click()
2013        self.assertTrue(
2014            'Student account deactivated by Manager<br />' in self.browser.contents)
2015        self.assertTrue(
2016            'Student account activated by Manager<br />' in self.browser.contents)
2017        # ... and actions have been logged.
2018        logfile = os.path.join(
2019            self.app['datacenter'].storage, 'logs', 'students.log')
2020        logcontent = open(logfile).read()
2021        self.assertTrue('zope.mgr - students.browser.StudentDeactivateView - '
2022                        'K1000000 - account deactivated' in logcontent)
2023        self.assertTrue('zope.mgr - students.browser.StudentActivateView - '
2024                        'K1000000 - account activated' in logcontent)
2025
2026    def test_manage_student_transfer(self):
2027        # Add second certificate
2028        self.certificate2 = createObject('waeup.Certificate')
2029        self.certificate2.code = u'CERT2'
2030        self.certificate2.study_mode = 'ug_ft'
2031        self.certificate2.start_level = 999
2032        self.certificate2.end_level = 999
2033        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
2034            self.certificate2)
2035
2036        # Add study level to old study course
2037        studylevel = createObject(u'waeup.StudentStudyLevel')
2038        studylevel.level = 200
2039        self.student['studycourse'].addStudentStudyLevel(
2040            self.certificate, studylevel)
2041        studylevel = createObject(u'waeup.StudentStudyLevel')
2042        studylevel.level = 999
2043        self.student['studycourse'].addStudentStudyLevel(
2044            self.certificate, studylevel)
2045
2046        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
2047        self.browser.open(self.student_path)
2048        self.browser.getLink("Transfer").click()
2049        self.browser.getControl(name="form.certificate").value = ['CERT2']
2050        self.browser.getControl(name="form.current_session").value = ['2011']
2051        self.browser.getControl(name="form.current_level").value = ['200']
2052        self.browser.getControl("Transfer").click()
2053        self.assertTrue(
2054            'Current level does not match certificate levels'
2055            in self.browser.contents)
2056        self.browser.getControl(name="form.current_level").value = ['999']
2057        self.browser.getControl("Transfer").click()
2058        self.assertTrue('Successfully transferred' in self.browser.contents)
2059        # The catalog has been updated
2060        cat = queryUtility(ICatalog, name='students_catalog')
2061        results = list(
2062            cat.searchResults(
2063            certcode=('CERT2', 'CERT2')))
2064        self.assertTrue(results[0] is self.student)
2065        results = list(
2066            cat.searchResults(
2067            current_session=(2011, 2011)))
2068        self.assertTrue(results[0] is self.student)
2069        # Add study level to new study course
2070        studylevel = createObject(u'waeup.StudentStudyLevel')
2071        studylevel.level = 999
2072        self.student['studycourse'].addStudentStudyLevel(
2073            self.certificate, studylevel)
2074
2075        # Edit and add pages are locked for old study courses
2076        self.browser.open(self.student_path + '/studycourse/manage')
2077        self.assertFalse('The requested form is locked' in self.browser.contents)
2078        self.browser.open(self.student_path + '/studycourse_1/manage')
2079        self.assertTrue('The requested form is locked' in self.browser.contents)
2080
2081        self.browser.open(self.student_path + '/studycourse/start_session')
2082        self.assertFalse('The requested form is locked' in self.browser.contents)
2083        self.browser.open(self.student_path + '/studycourse_1/start_session')
2084        self.assertTrue('The requested form is locked' in self.browser.contents)
2085
2086        IWorkflowState(self.student).setState('school fee paid')
2087        self.browser.open(self.student_path + '/studycourse/add')
2088        self.assertFalse('The requested form is locked' in self.browser.contents)
2089        self.browser.open(self.student_path + '/studycourse_1/add')
2090        self.assertTrue('The requested form is locked' in self.browser.contents)
2091
2092        self.browser.open(self.student_path + '/studycourse/999/manage')
2093        self.assertFalse('The requested form is locked' in self.browser.contents)
2094        self.browser.open(self.student_path + '/studycourse_1/999/manage')
2095        self.assertTrue('The requested form is locked' in self.browser.contents)
2096
2097        self.browser.open(self.student_path + '/studycourse/999/validate_courses')
2098        self.assertFalse('The requested form is locked' in self.browser.contents)
2099        self.browser.open(self.student_path + '/studycourse_1/999/validate_courses')
2100        self.assertTrue('The requested form is locked' in self.browser.contents)
2101
2102        self.browser.open(self.student_path + '/studycourse/999/reject_courses')
2103        self.assertFalse('The requested form is locked' in self.browser.contents)
2104        self.browser.open(self.student_path + '/studycourse_1/999/reject_courses')
2105        self.assertTrue('The requested form is locked' in self.browser.contents)
2106
2107        self.browser.open(self.student_path + '/studycourse/999/add')
2108        self.assertFalse('The requested form is locked' in self.browser.contents)
2109        self.browser.open(self.student_path + '/studycourse_1/999/add')
2110        self.assertTrue('The requested form is locked' in self.browser.contents)
2111
2112        self.browser.open(self.student_path + '/studycourse/999/edit')
2113        self.assertFalse('The requested form is locked' in self.browser.contents)
2114        self.browser.open(self.student_path + '/studycourse_1/999/edit')
2115        self.assertTrue('The requested form is locked' in self.browser.contents)
2116
2117        # Revert transfer
2118        self.browser.open(self.student_path + '/studycourse_1')
2119        self.browser.getLink("Reactivate").click()
2120        self.browser.getControl("Revert now").click()
2121        self.assertTrue('Previous transfer reverted' in self.browser.contents)
2122        results = list(
2123            cat.searchResults(
2124            certcode=('CERT1', 'CERT1')))
2125        self.assertTrue(results[0] is self.student)
2126        self.assertEqual([i for i in self.student.keys()],
2127            [u'accommodation', u'payments', u'studycourse'])
2128
2129    def test_login_as_student(self):
2130        # StudentImpersonators can login as student
2131        # Create clearance officer
2132        self.app['users'].addUser('mrofficer', SECRET)
2133        self.app['users']['mrofficer'].email = 'mrofficer@foo.ng'
2134        self.app['users']['mrofficer'].title = 'Harry Actor'
2135        prmglobal = IPrincipalRoleManager(self.app)
2136        prmglobal.assignRoleToPrincipal('waeup.StudentImpersonator', 'mrofficer')
2137        prmglobal.assignRoleToPrincipal('waeup.StudentsManager', 'mrofficer')
2138        # Login as student impersonator
2139        self.browser.open(self.login_path)
2140        self.browser.getControl(name="form.login").value = 'mrofficer'
2141        self.browser.getControl(name="form.password").value = SECRET
2142        self.browser.getControl("Login").click()
2143        self.assertMatches('...You logged in...', self.browser.contents)
2144        self.browser.open(self.student_path)
2145        self.browser.getLink("Login as").click()
2146        self.browser.getControl("Set password now").click()
2147        temp_password = self.browser.getControl(name='form.password').value
2148        self.browser.getControl("Login now").click()
2149        self.assertMatches(
2150            '...You successfully logged in as...', self.browser.contents)
2151        # We are logged in as student and can see the 'My Data' tab
2152        self.assertMatches(
2153            '...<a href="#" class="dropdown-toggle" data-toggle="dropdown">...',
2154            self.browser.contents)
2155        self.assertMatches(
2156            '...My Data...',
2157            self.browser.contents)
2158        self.browser.getLink("Logout").click()
2159        # The student can't login with the original password ...
2160        self.browser.open(self.login_path)
2161        self.browser.getControl(name="form.login").value = self.student_id
2162        self.browser.getControl(name="form.password").value = 'spwd'
2163        self.browser.getControl("Login").click()
2164        self.assertMatches(
2165            '...Your account has been temporarily deactivated...',
2166            self.browser.contents)
2167        # ... but with the temporary password
2168        self.browser.open(self.login_path)
2169        self.browser.getControl(name="form.login").value = self.student_id
2170        self.browser.getControl(name="form.password").value = temp_password
2171        self.browser.getControl("Login").click()
2172        self.assertMatches('...You logged in...', self.browser.contents)
2173        # Creation of temp_password is properly logged
2174        logfile = os.path.join(
2175            self.app['datacenter'].storage, 'logs', 'students.log')
2176        logcontent = open(logfile).read()
2177        self.assertTrue(
2178            'mrofficer - students.browser.LoginAsStudentStep1 - K1000000 - '
2179            'temp_password generated: %s' % temp_password in logcontent)
2180
2181    def test_transcripts(self):
2182        studylevel = createObject(u'waeup.StudentStudyLevel')
2183        studylevel.level = 100
2184        studylevel.level_session = 2005
2185        self.student['studycourse'].entry_mode = 'ug_ft'
2186        self.student['studycourse'].addStudentStudyLevel(
2187            self.certificate, studylevel)
2188        studylevel2 = createObject(u'waeup.StudentStudyLevel')
2189        studylevel2.level = 110
2190        studylevel2.level_session = 2006
2191        self.student['studycourse'].addStudentStudyLevel(
2192            self.certificate, studylevel2)
2193        # Add second course (COURSE has been added automatically)
2194        courseticket = createObject('waeup.CourseTicket')
2195        courseticket.code = 'ANYCODE'
2196        courseticket.title = u'Any TITLE'
2197        courseticket.credits = 13
2198        courseticket.score = 66
2199        courseticket.semester = 1
2200        courseticket.dcode = u'ANYDCODE'
2201        courseticket.fcode = u'ANYFCODE'
2202        self.student['studycourse']['110']['COURSE2'] = courseticket
2203        self.student['studycourse']['100']['COURSE1'].score = 55
2204        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[0], 3.0)
2205        self.assertEqual(self.student['studycourse']['110'].gpa_params_rectified[0], 4.0)
2206        # Get transcript data
2207        td = self.student['studycourse'].getTranscriptData()
2208        self.assertEqual(td[0][0]['level_key'], '100')
2209        self.assertEqual(td[0][0]['sgpa'], 3.0)
2210        self.assertEqual(td[0][0]['level'].level, 100)
2211        self.assertEqual(td[0][0]['level'].level_session, 2005)
2212        self.assertEqual(td[0][0]['tickets_1'][0].code, 'COURSE1')
2213        self.assertEqual(td[0][1]['level_key'], '110')
2214        self.assertEqual(td[0][1]['sgpa'], 4.0)
2215        self.assertEqual(td[0][1]['level'].level, 110)
2216        self.assertEqual(td[0][1]['level'].level_session, 2006)
2217        self.assertEqual(td[0][1]['tickets_1'][0].code, 'ANYCODE')
2218        self.assertEqual(td[1], 3.5652173913043477)
2219        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
2220        self.browser.open(self.student_path + '/studycourse/transcript')
2221        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2222        self.assertTrue('Transcript' in self.browser.contents)
2223        # Officers can open the pdf transcript
2224        self.browser.open(self.student_path + '/studycourse/transcript.pdf')
2225        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2226        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2227        path = os.path.join(samples_dir(), 'transcript.pdf')
2228        open(path, 'wb').write(self.browser.contents)
2229        print "Sample PDF transcript.pdf written to %s" % path
2230
2231    def test_process_transcript(self):
2232        IWorkflowState(self.student).setState('graduated')
2233        notify(grok.ObjectModifiedEvent(self.student))
2234        self.student['studycourse'].transcript_comment = (
2235            u'On 07/08/2013 08:59:54 UTC K1000000 wrote:\n\nComment line 1 \n'
2236            'Comment line2\n\nDispatch Address:\nAddress line 1 \n'
2237            'Address line2\n\n')
2238        # Create officer with both roles
2239        self.app['users'].addUser('mrtranscript', SECRET)
2240        self.app['users']['mrtranscript'].email = 'mrtranscript@foo.ng'
2241        self.app['users']['mrtranscript'].title = 'Ruth Gordon'
2242        prmglobal = IPrincipalRoleManager(self.app)
2243        prmglobal.assignRoleToPrincipal('waeup.TranscriptOfficer', 'mrtranscript')
2244        prmglobal.assignRoleToPrincipal('waeup.StudentsManager', 'mrtranscript')
2245        prmglobal.assignRoleToPrincipal('waeup.TranscriptSignee', 'mrtranscript')
2246        # Login
2247        self.browser.open(self.login_path)
2248        self.browser.getControl(name="form.login").value = 'mrtranscript'
2249        self.browser.getControl(name="form.password").value = SECRET
2250        self.browser.getControl("Login").click()
2251        self.assertMatches('...You logged in...', self.browser.contents)
2252        # Officer can request transcript for students
2253        self.browser.open(self.student_path)
2254        self.browser.getLink("Request transcript for student").click()
2255        self.browser.getControl("Request now").click()
2256        self.assertEqual(self.student.state, 'transcript requested')
2257        # Officer can see his roles
2258        self.browser.getLink("My Roles").click()
2259        self.assertMatches(
2260            '...<div>Transcript Officer</div>...',
2261            self.browser.contents)
2262        # Officer can search for students in state 'transcript requested'
2263        self.browser.open(self.container_path)
2264        self.browser.getControl(name="searchtype").value = [
2265            'transcript requested']
2266        self.browser.getControl("Find student(s)").click()
2267        self.assertTrue('Anna Tester' in self.browser.contents)
2268        self.browser.getLink("K1000000").click()
2269        self.assertFalse('Release transcript request' in self.browser.contents)
2270        # Officers can still edit studycourse, studylevel and course tickets.
2271        self.browser.open(self.studycourse_path + '/manage')
2272        self.assertTrue('Undergraduate Full-Time</option>'
2273            in self.browser.contents)
2274        self.browser.getControl(name="form.certificate").value = ['CERT1']
2275        self.browser.getControl(name="form.current_session").value = ['2004']
2276        self.browser.getControl(name="form.current_verdict").value = ['A']
2277        self.browser.getControl(name="form.entry_mode").value = ['ug_ft']
2278        self.browser.getControl("Save").click()
2279        self.browser.getControl(name="form.current_level").value = ['100']
2280        self.browser.getControl("Save").click()
2281        self.browser.getControl(name="addlevel").value = ['100']
2282        self.browser.getControl(name="level_session").value = ['2004']
2283        self.browser.getControl("Add study level").click()
2284        self.browser.getLink("100").click()
2285        self.browser.getLink("Manage").click()
2286        self.browser.getControl(name="form.level_session").value = ['2002']
2287        self.browser.getControl("Save").click()
2288        self.browser.getLink("COURSE1").click()
2289        self.browser.getLink("Manage").click()
2290        self.browser.getControl("Save").click()
2291        self.assertTrue('Form has been saved' in self.browser.contents)
2292        # Officer can edit transcript remarks and validate the transcript
2293        self.browser.open(self.studycourse_path + '/transcript')
2294        self.browser.getLink("Validate transcript").click()
2295        self.browser.getLink("Edit").click()
2296        self.assertEqual(
2297            self.browser.url, self.studycourse_path + '/100/remark')
2298        self.browser.getControl(
2299            name="form.transcript_remark").value = 'Oh, the student failed'
2300        self.browser.getControl(
2301            "Save remark and go and back to transcript validation page").click()
2302        self.assertEqual(
2303            self.browser.url,
2304            self.studycourse_path + '/validate_transcript#tab4')
2305        self.assertEqual(self.student['studycourse']['100'].transcript_remark,
2306            'Oh, the student failed')
2307        self.browser.getControl("Save comment and validate transcript").click()
2308        # After validation all manage forms are locked.
2309        self.browser.open(self.studycourse_path + '/manage')
2310        self.assertTrue('The requested form is locked' in self.browser.contents)
2311        self.assertFalse('Undergraduate Full-Time</option>'
2312            in self.browser.contents)
2313        self.browser.open(self.studycourse_path + '/100/manage')
2314        self.assertTrue('The requested form is locked' in self.browser.contents)
2315        self.browser.open(self.studycourse_path + '/100/COURSE1/manage')
2316        self.assertTrue('The requested form is locked' in self.browser.contents)
2317        self.browser.open(self.studycourse_path + '/100/remark')
2318        self.assertTrue('The requested form is locked' in self.browser.contents)
2319
2320        # Transcript can be signed if officer has the permission to sign
2321        #self.browser.open(self.studycourse_path + '/transcript')
2322        #self.assertFalse('Sign transcript' in self.browser.contents)
2323        #prmglobal = IPrincipalRoleManager(self.app)
2324        #prmglobal.assignRoleToPrincipal('waeup.TranscriptSignee', 'mrtranscript')
2325
2326        self.browser.open(self.studycourse_path + '/transcript')
2327        self.browser.getLink("Sign transcript electronically").click()
2328        # Transcript signing has been logged ...
2329        logfile = os.path.join(
2330            self.app['datacenter'].storage, 'logs', 'students.log')
2331        logcontent = open(logfile).read()
2332        self.assertTrue(
2333            'mrtranscript - students.browser.StudentTranscriptSignView - '
2334            'K1000000 - Transcript signed' in logcontent)
2335        # ... appears in the student's history ...
2336        self.browser.open(self.history_path)
2337        self.assertTrue('Transcript signed by Ruth Gordon'
2338            in self.browser.contents)
2339        # ... and is also stored in the transcript_signee attribute.
2340        self.assertTrue(
2341            u'Electronically signed by Ruth Gordon (mrtranscript) on '
2342            in self.student['studycourse'].transcript_signees)
2343        # Officer can release the transcript
2344        self.browser.open(self.studycourse_path + '/transcript')
2345        self.browser.getLink("Release transcript").click()
2346        self.assertTrue(' UTC K1000000 wrote:<br><br>Comment line 1 <br>'
2347        'Comment line2<br><br>Dispatch Address:<br>Address line 1 <br>'
2348        'Address line2<br><br></p>' in self.browser.contents)
2349        self.browser.getControl(name="comment").value = (
2350            'Hello,\nYour transcript has been sent to the address provided.')
2351        self.browser.getControl("Save comment and release transcript").click()
2352        self.assertTrue(
2353            'UTC mrtranscript wrote:\n\nHello,\nYour transcript has '
2354            'been sent to the address provided.\n\n'
2355            in self.student['studycourse'].transcript_comment)
2356        # The comment has been logged
2357        logfile = os.path.join(
2358            self.app['datacenter'].storage, 'logs', 'students.log')
2359        logcontent = open(logfile).read()
2360        self.assertTrue(
2361            'mrtranscript - students.browser.StudentTranscriptReleaseFormPage - '
2362            'K1000000 - comment: Hello,<br>'
2363            'Your transcript has been sent to the address provided'
2364            in logcontent)
2365        # File has been stored in the file system
2366        # Check if transcript exists in the file system and is a PDF file
2367        storage = getUtility(IExtFileStore)
2368        file_id = IFileStoreNameChooser(
2369            self.student).chooseName(attr='final_transcript.pdf')
2370        pdf = storage.getFile(file_id).read()
2371        self.assertTrue(len(pdf) > 0)
2372        self.assertEqual(pdf[:8], '%PDF-1.4')
2373        # Copy the file to samples_dir
2374        path = os.path.join(samples_dir(), 'final_transcript.pdf')
2375        open(path, 'wb').write(pdf)
2376        print "Sample PDF final_transcript.pdf written to %s" % path
2377        # Check if there is an transcript pdf link in UI
2378        self.browser.open(self.student_path)
2379        self.assertTrue('Final Transcript' in self.browser.contents)
2380        self.browser.getLink("Final Transcript").click()
2381        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2382        self.assertEqual(self.browser.headers['Content-Type'],
2383                         'application/pdf')
2384        # Transcript views are no longer accessible
2385        self.browser.open(self.studycourse_path)
2386        self.assertFalse('studycourse/transcript' in self.browser.contents)
2387        self.browser.open(self.studycourse_path + '/transcript')
2388        self.assertTrue('Forbidden!' in self.browser.contents)
2389        self.browser.open(self.studycourse_path + '/transcript.pdf')
2390        self.assertTrue('Forbidden!' in self.browser.contents)
2391        # If we reset the transcript process
2392        # (can't be done by transcript officer), the file will be deleted
2393        IWorkflowInfo(self.student).fireTransition('reset11')
2394        self.browser.open(self.student_path)
2395        self.assertFalse('Final Transcript' in self.browser.contents)
2396        # ... and transcript process information has been removed
2397        self.assertEqual(self.student['studycourse'].transcript_comment, None)
2398        self.assertEqual(self.student['studycourse'].transcript_signees, None)
2399
2400    def test_landingpage_transcript_officer(self):
2401        IWorkflowState(self.student).setState('transcript requested')
2402        notify(grok.ObjectModifiedEvent(self.student))
2403        # Create transcript officer
2404        self.app['users'].addUser('mrtranscript', SECRET)
2405        self.app['users']['mrtranscript'].email = 'mrtranscript@foo.ng'
2406        self.app['users']['mrtranscript'].title = 'Ruth Gordon'
2407        # We assign transcript officer role at faculty level
2408        fac = self.app['faculties']['fac1']
2409        prmlocal = IPrincipalRoleManager(fac)
2410        prmlocal.assignRoleToPrincipal(
2411            'waeup.local.TranscriptOfficer', 'mrtranscript')
2412        notify(LocalRoleSetEvent(
2413            fac, 'waeup.local.TranscriptOfficer', 'mrtranscript', granted=True))
2414        # Login as transcript officer
2415        self.browser.open(self.login_path)
2416        self.browser.getControl(name="form.login").value = 'mrtranscript'
2417        self.browser.getControl(name="form.password").value = SECRET
2418        self.browser.getControl("Login").click()
2419        self.assertMatches('...You logged in...', self.browser.contents)
2420        # Officer is on landing page and does see the transcript link
2421        self.assertTrue(
2422            'http://localhost/app/students/K1000000/studycourse/transcript'
2423            in self.browser.contents)
2424        self.browser.getLink("K1000000").click()
2425        self.assertTrue(
2426            'Anna Tester: Transcript Data' in self.browser.contents)
2427        # Officer is on transcript page and can validate the transcript
2428        self.browser.getLink("Validate transcript").click()
2429        self.browser.getControl("Save comment and validate transcript").click()
2430        self.assertTrue(
2431            '<div class="alert alert-success">Transcript validated.</div>'
2432            in self.browser.contents)
2433        # Officer is still on transcript page and can release the transcript
2434        self.browser.getLink("Release transcript").click()
2435        self.browser.getControl("Save comment and release transcript").click()
2436        self.assertTrue(
2437            '<div class="alert alert-success">'
2438            'Transcript released and final transcript file saved.</div>'
2439            in self.browser.contents)
2440
2441    def test_landingpage_transcript_signee(self):
2442        IWorkflowState(self.student).setState('transcript validated')
2443        notify(grok.ObjectModifiedEvent(self.student))
2444        # Create transcript signee
2445        self.app['users'].addUser('mrtranscript', SECRET)
2446        self.app['users']['mrtranscript'].email = 'mrtranscript@foo.ng'
2447        self.app['users']['mrtranscript'].title = 'Ruth Gordon'
2448        # We assign transcript officer role at faculty level
2449        fac = self.app['faculties']['fac1']
2450        prmlocal = IPrincipalRoleManager(fac)
2451        prmlocal.assignRoleToPrincipal(
2452            'waeup.local.TranscriptSignee', 'mrtranscript')
2453        notify(LocalRoleSetEvent(
2454            fac, 'waeup.local.TranscriptSignee', 'mrtranscript', granted=True))
2455        # Login as transcript officer
2456        self.browser.open(self.login_path)
2457        self.browser.getControl(name="form.login").value = 'mrtranscript'
2458        self.browser.getControl(name="form.password").value = SECRET
2459        self.browser.getControl("Login").click()
2460        self.assertMatches('...You logged in...', self.browser.contents)
2461        # Officer is on landing page and does see the transcript link
2462        self.assertTrue(
2463            'http://localhost/app/students/K1000000/studycourse/transcript'
2464            in self.browser.contents)
2465        self.browser.getLink("K1000000").click()
2466        self.assertTrue(
2467            'Anna Tester: Transcript Data' in self.browser.contents)
2468        # Officer is on transcript page and can sign the transcript
2469        self.browser.getLink("Sign transcript").click()
2470        self.assertTrue(
2471            '<div class="alert alert-success">Transcript signed.</div>'
2472            in self.browser.contents)
2473        # Officer is still on transcript page
2474        self.assertTrue(
2475            'Anna Tester: Transcript Data' in self.browser.contents)
2476        # Officer can sign the transcript only once
2477        self.browser.getLink("Sign transcript").click()
2478        self.assertTrue(
2479            '<div class="alert alert-warning">'
2480            'You have already signed this transcript.</div>'
2481            in self.browser.contents)
2482        # Signature can be seen on transcript page
2483        self.assertTrue(
2484            'Electronically signed by Ruth Gordon (mrtranscript) on'
2485            in self.browser.contents)
2486
2487    def test_update_coursetickets(self):
2488        IWorkflowState(self.student).setState('school fee paid')
2489        studylevel = createObject(u'waeup.StudentStudyLevel')
2490        studylevel.level = 100
2491        studylevel.level_session = 2015
2492        self.student['studycourse'].entry_mode = 'ug_ft'
2493        self.student['studycourse'].addStudentStudyLevel(
2494            self.certificate, studylevel)
2495        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
2496        self.browser.open(
2497            'http://localhost/app/faculties/fac1/dep1/courses/COURSE1/')
2498        self.assertFalse(
2499            'Update session 2015/2016 credits' in self.browser.contents)
2500        self.app['configuration'].current_academic_session = 2015
2501        self.browser.open(
2502            'http://localhost/app/faculties/fac1/dep1/courses/COURSE1/')
2503        self.browser.getLink("Update session 2015/2016 credits").click()
2504        self.assertTrue(
2505            'No course ticket found.' in self.browser.contents)
2506        logfile = os.path.join(
2507            self.app['datacenter'].storage, 'logs', 'main.log')
2508        logcontent = open(logfile).read()
2509        self.assertTrue(
2510            'zope.mgr - browser.pages.UpdateCourseTicketsView - '
2511            'course tickets updated: COURSE1' in logcontent)
2512        studylevel['COURSE1'].credits = 12
2513        self.browser.getLink("Update session 2015/2016 credits").click()
2514        self.assertTrue(
2515            'No course ticket found.' in self.browser.contents)
2516        studylevel.level_session = 2015
2517        self.student['studycourse'].current_session = 2015
2518        self.browser.getLink("Update session 2015/2016 credits").click()
2519        self.assertTrue(
2520            '1 course ticket(s) updated.' in self.browser.contents)
2521        logfile = os.path.join(
2522            self.app['datacenter'].storage, 'logs', 'students.log')
2523        logcontent = open(logfile).read()
2524        self.assertTrue(
2525            'zope.mgr - students.utils.StudentsUtils - '
2526            'K1000000 100/COURSE1 credits updated (10->12)' in logcontent)
2527        return
2528
2529
2530class StudentUITests(StudentsFullSetup):
2531    # Tests for Student class views and pages
2532
2533    def test_student_change_password(self):
2534        # Students can change the password
2535        self.student.personal_updated = datetime.utcnow()
2536        self.browser.open(self.login_path)
2537        self.browser.getControl(name="form.login").value = self.student_id
2538        self.browser.getControl(name="form.password").value = 'spwd'
2539        self.browser.getControl("Login").click()
2540        self.assertEqual(self.browser.url, self.student_path)
2541        self.assertTrue('You logged in' in self.browser.contents)
2542        # Change password
2543        self.browser.getLink("Change password").click()
2544        self.browser.getControl(name="change_password").value = 'pw'
2545        self.browser.getControl(
2546            name="change_password_repeat").value = 'pw'
2547        self.browser.getControl("Save").click()
2548        self.assertTrue('Password must have at least' in self.browser.contents)
2549        self.browser.getControl(name="change_password").value = 'new_password'
2550        self.browser.getControl(
2551            name="change_password_repeat").value = 'new_passssword'
2552        self.browser.getControl("Save").click()
2553        self.assertTrue('Passwords do not match' in self.browser.contents)
2554        self.browser.getControl(name="change_password").value = 'new_password'
2555        self.browser.getControl(
2556            name="change_password_repeat").value = 'new_password'
2557        self.browser.getControl("Save").click()
2558        self.assertTrue('Password changed' in self.browser.contents)
2559        # We are still logged in. Changing the password hasn't thrown us out.
2560        self.browser.getLink("Base Data").click()
2561        self.assertEqual(self.browser.url, self.student_path)
2562        # We can logout
2563        self.browser.getLink("Logout").click()
2564        self.assertTrue('You have been logged out' in self.browser.contents)
2565        self.assertEqual(self.browser.url, 'http://localhost/app/index')
2566        # We can login again with the new password
2567        self.browser.getLink("Login").click()
2568        self.browser.open(self.login_path)
2569        self.browser.getControl(name="form.login").value = self.student_id
2570        self.browser.getControl(name="form.password").value = 'new_password'
2571        self.browser.getControl("Login").click()
2572        self.assertEqual(self.browser.url, self.student_path)
2573        self.assertTrue('You logged in' in self.browser.contents)
2574        return
2575
2576    def test_forbidden_name(self):
2577        self.student.lastname = u'<TAG>Tester</TAG>'
2578        self.browser.open(self.login_path)
2579        self.browser.getControl(name="form.login").value = self.student_id
2580        self.browser.getControl(name="form.password").value = 'spwd'
2581        self.browser.getControl("Login").click()
2582        self.assertTrue('XXX: Base Data' in self.browser.contents)
2583        self.assertTrue('&lt;TAG&gt;Tester&lt;/TAG&gt;' in self.browser.contents)
2584        self.assertFalse('<TAG>Tester</TAG>' in self.browser.contents)
2585        return
2586
2587    def test_setpassword(self):
2588        # Set password for first-time access
2589        student = Student()
2590        student.reg_number = u'123456'
2591        student.firstname = u'Klaus'
2592        student.lastname = u'Tester'
2593        self.app['students'].addStudent(student)
2594        setpassword_path = 'http://localhost/app/setpassword'
2595        student_path = 'http://localhost/app/students/%s' % student.student_id
2596        self.browser.open(setpassword_path)
2597        self.browser.getControl(name="ac_series").value = self.existing_pwdseries
2598        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
2599        self.browser.getControl(name="reg_number").value = '223456'
2600        self.browser.getControl("Set").click()
2601        self.assertMatches('...No student found...',
2602                           self.browser.contents)
2603        self.browser.getControl(name="reg_number").value = '123456'
2604        self.browser.getControl(name="ac_number").value = '999999'
2605        self.browser.getControl("Set").click()
2606        self.assertMatches('...Access code is invalid...',
2607                           self.browser.contents)
2608        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
2609        self.browser.getControl("Set").click()
2610        self.assertMatches('...Password has been set. Your Student Id is...',
2611                           self.browser.contents)
2612        self.browser.getControl("Set").click()
2613        self.assertMatches(
2614            '...Password has already been set. Your Student Id is...',
2615            self.browser.contents)
2616        existing_pwdpin = self.pwdpins[1]
2617        parts = existing_pwdpin.split('-')[1:]
2618        existing_pwdseries, existing_pwdnumber = parts
2619        self.browser.getControl(name="ac_series").value = existing_pwdseries
2620        self.browser.getControl(name="ac_number").value = existing_pwdnumber
2621        self.browser.getControl(name="reg_number").value = '123456'
2622        self.browser.getControl("Set").click()
2623        self.assertMatches(
2624            '...You are using the wrong Access Code...',
2625            self.browser.contents)
2626        # The student can login with the new credentials
2627        self.browser.open(self.login_path)
2628        self.browser.getControl(name="form.login").value = student.student_id
2629        self.browser.getControl(
2630            name="form.password").value = self.existing_pwdnumber
2631        self.browser.getControl("Login").click()
2632        self.assertEqual(self.browser.url, student_path)
2633        self.assertTrue('You logged in' in self.browser.contents)
2634        return
2635
2636    def test_student_login(self):
2637        # Student cant login if their password is not set
2638        self.student.password = None
2639        self.browser.open(self.login_path)
2640        self.browser.getControl(name="form.login").value = self.student_id
2641        self.browser.getControl(name="form.password").value = 'spwd'
2642        self.browser.getControl("Login").click()
2643        self.assertTrue(
2644            'You entered invalid credentials.' in self.browser.contents)
2645        # We set the password again
2646        IUserAccount(
2647            self.app['students'][self.student_id]).setPassword('spwd')
2648        # Students can't login if their account is suspended/deactivated
2649        self.student.suspended = True
2650        self.browser.open(self.login_path)
2651        self.browser.getControl(name="form.login").value = self.student_id
2652        self.browser.getControl(name="form.password").value = 'spwd'
2653        self.browser.getControl("Login").click()
2654        self.assertMatches(
2655            '...<div class="alert alert-warning">'
2656            'Your account has been deactivated.</div>...', self.browser.contents)
2657        # If suspended_comment is set this message will be flashed instead
2658        self.student.suspended_comment = u'Aetsch baetsch!'
2659        self.browser.getControl(name="form.login").value = self.student_id
2660        self.browser.getControl(name="form.password").value = 'spwd'
2661        self.browser.getControl("Login").click()
2662        self.assertMatches(
2663            '...<div class="alert alert-warning">Aetsch baetsch!</div>...',
2664            self.browser.contents)
2665        self.student.suspended = False
2666        # Students can't login if a temporary password has been set and
2667        # is not expired
2668        self.app['students'][self.student_id].setTempPassword(
2669            'anybody', 'temp_spwd')
2670        self.browser.open(self.login_path)
2671        self.browser.getControl(name="form.login").value = self.student_id
2672        self.browser.getControl(name="form.password").value = 'spwd'
2673        self.browser.getControl("Login").click()
2674        self.assertMatches(
2675            '...Your account has been temporarily deactivated...',
2676            self.browser.contents)
2677        # The student can login with the temporary password
2678        self.browser.open(self.login_path)
2679        self.browser.getControl(name="form.login").value = self.student_id
2680        self.browser.getControl(name="form.password").value = 'temp_spwd'
2681        self.browser.getControl("Login").click()
2682        self.assertMatches(
2683            '...You logged in...', self.browser.contents)
2684        # Student can view the base data
2685        self.browser.open(self.student_path)
2686        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2687        self.assertEqual(self.browser.url, self.student_path)
2688        # When the password expires ...
2689        delta = timedelta(minutes=11)
2690        self.app['students'][self.student_id].temp_password[
2691            'timestamp'] = datetime.utcnow() - delta
2692        self.app['students'][self.student_id]._p_changed = True
2693        # ... the student will be automatically logged out
2694        self.assertRaises(
2695            Unauthorized, self.browser.open, self.student_path)
2696        # Then the student can login with the original password
2697        self.browser.open(self.login_path)
2698        self.browser.getControl(name="form.login").value = self.student_id
2699        self.browser.getControl(name="form.password").value = 'spwd'
2700        self.browser.getControl("Login").click()
2701        self.assertMatches(
2702            '...You logged in...', self.browser.contents)
2703
2704    def test_maintenance_mode(self):
2705        config = grok.getSite()['configuration']
2706        self.browser.open(self.login_path)
2707        self.browser.getControl(name="form.login").value = self.student_id
2708        self.browser.getControl(name="form.password").value = 'spwd'
2709        self.browser.getControl("Login").click()
2710        # Student logged in.
2711        self.assertTrue('You logged in' in self.browser.contents)
2712        self.assertTrue("Anna Tester" in self.browser.contents)
2713        # If maintenance mode is enabled, student is immediately logged out.
2714        config.maintmode_enabled_by = u'any_user'
2715        self.assertRaises(
2716            Unauthorized, self.browser.open, 'http://localhost/app/faculties')
2717        self.browser.open('http://localhost/app/login')
2718        self.assertTrue('The portal is in maintenance mode' in self.browser.contents)
2719        # Student really can't login if maintenance mode is enabled.
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        # A second warning is raised.
2725        self.assertTrue(
2726            'The portal is in maintenance mode. You can\'t login!'
2727            in self.browser.contents)
2728        return
2729
2730    def test_student_basedata_slip(self):
2731        IWorkflowState(self.student).setState('school fee paid')
2732        self.browser.open(self.login_path)
2733        self.browser.getControl(name="form.login").value = self.student_id
2734        self.browser.getControl(name="form.password").value = 'spwd'
2735        self.browser.getControl("Login").click()
2736        # Students can open base data slip
2737        # (no button available in base package)
2738        pdf_url = '%s/basedata_slip.pdf' % self.studycourse_path
2739        self.browser.open(pdf_url)
2740        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2741        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2742        path = os.path.join(samples_dir(), 'basedata_slip.pdf')
2743        open(path, 'wb').write(self.browser.contents)
2744        print "Sample PDF basedata_slip.pdf written to %s" % path
2745
2746    def test_student_clearance(self):
2747        # Student cant login if their password is not set
2748        IWorkflowInfo(self.student).fireTransition('admit')
2749        self.browser.open(self.login_path)
2750        self.browser.getControl(name="form.login").value = self.student_id
2751        self.browser.getControl(name="form.password").value = 'spwd'
2752        self.browser.getControl("Login").click()
2753        self.assertMatches(
2754            '...You logged in...', self.browser.contents)
2755        # Admitted student can upload a passport picture
2756        self.browser.open(self.student_path + '/change_portrait')
2757        ctrl = self.browser.getControl(name='passportuploadedit')
2758        file_obj = open(SAMPLE_IMAGE, 'rb')
2759        file_ctrl = ctrl.mech_control
2760        file_ctrl.add_file(file_obj, filename='my_photo.jpg')
2761        self.browser.getControl(
2762            name='upload_passportuploadedit').click()
2763        self.assertTrue(
2764            'src="http://localhost/app/students/K1000000/passport.jpg"'
2765            in self.browser.contents)
2766        # Students can open admission letter
2767        self.browser.getLink("Base Data").click()
2768        self.browser.getLink("Download admission letter").click()
2769        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2770        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2771        path = os.path.join(samples_dir(), 'admission_slip.pdf')
2772        open(path, 'wb').write(self.browser.contents)
2773        print "Sample PDF admission_slip.pdf written to %s" % path
2774        # Student can view the clearance data
2775        self.browser.open(self.student_path)
2776        self.browser.getLink("Clearance Data").click()
2777        # Student can't open clearance edit form before starting clearance
2778        self.browser.open(self.student_path + '/cedit')
2779        self.assertMatches('...The requested form is locked...',
2780                           self.browser.contents)
2781        self.browser.getLink("Clearance Data").click()
2782        self.browser.getLink("Start clearance").click()
2783        self.student.phone = None
2784        # Uups, we forgot to fill the phone fields
2785        self.browser.getControl("Start clearance").click()
2786        self.assertMatches('...Phone number is missing...',
2787                           self.browser.contents)
2788        self.browser.open(self.student_path + '/edit_base')
2789        self.browser.getControl(name="form.phone.ext").value = '12345'
2790        self.browser.getControl("Save").click()
2791        self.browser.open(self.student_path + '/start_clearance')
2792        self.browser.getControl(name="ac_series").value = '3'
2793        self.browser.getControl(name="ac_number").value = '4444444'
2794        self.browser.getControl("Start clearance now").click()
2795        self.assertMatches('...Activation code is invalid...',
2796                           self.browser.contents)
2797        self.browser.getControl(name="ac_series").value = self.existing_clrseries
2798        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
2799        # Owner is Hans Wurst, AC can't be invalidated
2800        self.browser.getControl("Start clearance now").click()
2801        self.assertMatches('...You are not the owner of this access code...',
2802                           self.browser.contents)
2803        # Set the correct owner
2804        self.existing_clrac.owner = self.student_id
2805        # clr_code might be set (and thus returns None) due importing
2806        # an empty clr_code column.
2807        self.student.clr_code = None
2808        self.browser.getControl("Start clearance now").click()
2809        self.assertMatches('...Clearance process has been started...',
2810                           self.browser.contents)
2811        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
2812        self.browser.getControl("Save", index=0).click()
2813        # Student can view the clearance data
2814        self.browser.getLink("Clearance Data").click()
2815        # and go back to the edit form
2816        self.browser.getLink("Edit").click()
2817        # Students can upload documents
2818        ctrl = self.browser.getControl(name='birthcertificateupload')
2819        file_obj = open(SAMPLE_IMAGE, 'rb')
2820        file_ctrl = ctrl.mech_control
2821        file_ctrl.add_file(file_obj, filename='my_birth_certificate.jpg')
2822        self.browser.getControl(
2823            name='upload_birthcertificateupload').click()
2824        self.assertTrue(
2825            'href="http://localhost/app/students/K1000000/birth_certificate"'
2826            in self.browser.contents)
2827        # Students can open clearance slip
2828        self.browser.getLink("View").click()
2829        self.browser.getLink("Download clearance slip").click()
2830        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2831        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2832        # Students can request clearance
2833        self.browser.open(self.edit_clearance_path)
2834        self.browser.getControl("Save and request clearance").click()
2835        self.browser.getControl(name="ac_series").value = self.existing_clrseries
2836        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
2837        self.browser.getControl("Request clearance now").click()
2838        self.assertMatches('...Clearance has been requested...',
2839                           self.browser.contents)
2840        # Student can't reopen clearance form after requesting clearance
2841        self.browser.open(self.student_path + '/cedit')
2842        self.assertMatches('...The requested form is locked...',
2843                           self.browser.contents)
2844
2845    def test_student_course_registration(self):
2846        # Student cant login if their password is not set
2847        IWorkflowInfo(self.student).fireTransition('admit')
2848        self.browser.open(self.login_path)
2849        self.browser.getControl(name="form.login").value = self.student_id
2850        self.browser.getControl(name="form.password").value = 'spwd'
2851        self.browser.getControl("Login").click()
2852        # Student can't add study level if not in state 'school fee paid'
2853        self.browser.open(self.student_path + '/studycourse/add')
2854        self.assertMatches('...The requested form is locked...',
2855                           self.browser.contents)
2856        # ... and must be transferred first
2857        IWorkflowState(self.student).setState('school fee paid')
2858        # Now students can add the current study level
2859        self.browser.getLink("Study Course").click()
2860        self.student['studycourse'].current_level = None
2861        self.browser.getLink("Add course list").click()
2862        self.assertMatches('...Your data are incomplete...',
2863                           self.browser.contents)
2864        self.student['studycourse'].current_level = 100
2865        self.browser.getLink("Add course list").click()
2866        self.assertMatches('...Add current level 100 (Year 1)...',
2867                           self.browser.contents)
2868        self.browser.getControl("Create course list now").click()
2869        # A level with one course ticket was created
2870        self.assertEqual(self.student['studycourse']['100'].number_of_tickets, 1)
2871        self.browser.getLink("100").click()
2872        self.browser.getLink("Edit course list").click()
2873        self.browser.getLink("here").click()
2874        self.browser.getControl(name="form.course").value = ['COURSE1']
2875        self.browser.getControl("Add course ticket").click()
2876        self.assertMatches('...The ticket exists...',
2877                           self.browser.contents)
2878        self.student['studycourse'].current_level = 200
2879        self.browser.getLink("Study Course").click()
2880        self.browser.getLink("Add course list").click()
2881        self.assertMatches('...Add current level 200 (Year 2)...',
2882                           self.browser.contents)
2883        self.browser.getControl("Create course list now").click()
2884        self.browser.getLink("200").click()
2885        self.browser.getLink("Edit course list").click()
2886        self.browser.getLink("here").click()
2887        self.browser.getControl(name="form.course").value = ['COURSE1']
2888        self.course.credits = 100
2889        self.browser.getControl("Add course ticket").click()
2890        self.assertMatches(
2891            '...Maximum credits exceeded...', self.browser.contents)
2892        self.course.credits = 10
2893        self.browser.getControl("Add course ticket").click()
2894        self.assertMatches('...The ticket exists...',
2895                           self.browser.contents)
2896        # Indeed the ticket exists as carry-over course from level 100
2897        # since its score was 0
2898        self.assertTrue(
2899            self.student['studycourse']['200']['COURSE1'].carry_over is True)
2900        self.assertTrue(
2901            self.student['studycourse']['200']['COURSE1'].course_category is None)
2902        # Students can open the pdf course registration slip
2903        self.browser.open(self.student_path + '/studycourse/200')
2904        self.browser.getLink("Download course registration slip").click()
2905        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2906        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2907        path = os.path.join(samples_dir(), 'course_registration_slip.pdf')
2908        open(path, 'wb').write(self.browser.contents)
2909        print "Sample PDF course_registration_slip.pdf written to %s" % path
2910        # Students can remove course tickets
2911        self.browser.open(self.student_path + '/studycourse/200/edit')
2912        self.browser.getControl("Remove selected", index=0).click()
2913        self.assertTrue('No ticket selected' in self.browser.contents)
2914        # No ticket can be selected since the carry-over course is a core course
2915        self.assertRaises(
2916            LookupError, self.browser.getControl, name='val_id')
2917        self.student['studycourse']['200']['COURSE1'].mandatory = False
2918        self.browser.open(self.student_path + '/studycourse/200/edit')
2919        # Course list can't be registered if total_credits exceeds max_credits
2920        self.student['studycourse']['200']['COURSE1'].credits = 60
2921        self.browser.getControl("Register course list").click()
2922        self.assertTrue('Maximum credits exceeded' in self.browser.contents)
2923        # Student can now remove the ticket
2924        ctrl = self.browser.getControl(name='val_id')
2925        ctrl.getControl(value='COURSE1').selected = True
2926        self.browser.getControl("Remove selected", index=0).click()
2927        self.assertTrue('Successfully removed' in self.browser.contents)
2928        # Removing course tickets is properly logged
2929        logfile = os.path.join(
2930            self.app['datacenter'].storage, 'logs', 'students.log')
2931        logcontent = open(logfile).read()
2932        self.assertTrue('K1000000 - students.browser.StudyLevelEditFormPage '
2933        '- K1000000 - level 200 - removed: COURSE1' in logcontent)
2934        # They can add the same ticket using the edit page directly.
2935        # We can do the same by adding the course on the manage page directly
2936        self.browser.getControl(name="course").value = 'COURSE1'
2937        self.browser.getControl("Add course ticket").click()
2938        # Adding course tickets is logged
2939        logfile = os.path.join(
2940            self.app['datacenter'].storage, 'logs', 'students.log')
2941        logcontent = open(logfile).read()
2942        self.assertTrue('K1000000 - students.browser.StudyLevelEditFormPage - '
2943            'K1000000 - level 200 - added: COURSE1|200|2004' in logcontent)
2944        # Course list can be registered
2945        self.browser.getControl("Register course list").click()
2946        self.assertTrue('Course list has been registered' in self.browser.contents)
2947        self.assertEqual(self.student.state, 'courses registered')
2948        # Course list can be unregistered
2949        self.browser.getLink("Unregister courses").click()
2950        self.assertEqual(self.student.state, 'school fee paid')
2951        self.assertTrue('Course list has been unregistered' in self.browser.contents)
2952        self.browser.open(self.student_path + '/studycourse/200/unregister_courses')
2953        self.assertTrue('You are in the wrong state' in self.browser.contents)
2954        # Students can view the transcript
2955        #self.browser.open(self.studycourse_path)
2956        #self.browser.getLink("Transcript").click()
2957        #self.browser.getLink("Academic Transcript").click()
2958        #self.assertEqual(self.browser.headers['Status'], '200 Ok')
2959        #self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2960        return
2961
2962    def test_student_ticket_update(self):
2963        IWorkflowState(self.student).setState('school fee paid')
2964        self.student['studycourse'].current_level = 100
2965        self.browser.open(self.login_path)
2966        self.browser.getControl(name="form.login").value = self.student_id
2967        self.browser.getControl(name="form.password").value = 'spwd'
2968        self.browser.getControl("Login").click()
2969        # Now students can add the current study level
2970        self.browser.getLink("Study Course").click()
2971        self.browser.getLink("Add course list").click()
2972        self.assertMatches('...Add current level 100 (Year 1)...',
2973                           self.browser.contents)
2974        self.browser.getControl("Create course list now").click()
2975        # A level with one course ticket was created
2976        self.assertEqual(
2977            self.student['studycourse']['100'].number_of_tickets, 1)
2978        self.browser.getLink("100").click()
2979        self.assertTrue('<td>Unnamed Course</td>' in self.browser.contents)
2980        self.browser.getLink("Edit course list").click()
2981        self.browser.getControl("Update all tickets").click()
2982        self.assertTrue('All course tickets updated.' in self.browser.contents)
2983        # ... nothing has changed
2984        self.assertTrue('<td>Unnamed Course</td>' in self.browser.contents)
2985        # We change the title of the course
2986        self.course.title = u'New Title'
2987        self.browser.getControl("Update all tickets").click()
2988        self.assertTrue('<td>New Title</td>' in self.browser.contents)
2989        # We remove the course
2990        del self.app['faculties']['fac1']['dep1'].courses['COURSE1']
2991        self.browser.getControl("Update all tickets").click()
2992        self.assertTrue(' <td>New Title (course cancelled)</td>'
2993            in self.browser.contents)
2994        # Course ticket invalidation has been logged
2995        logfile = os.path.join(
2996            self.app['datacenter'].storage, 'logs', 'students.log')
2997        logcontent = open(logfile).read()
2998        self.assertTrue(
2999            'K1000000 - students.browser.StudyLevelEditFormPage - '
3000            'K1000000 - level 100 - course tickets invalidated: COURSE1'
3001            in logcontent)
3002        return
3003
3004    def test_student_course_already_passed(self):
3005        IWorkflowState(self.student).setState('school fee paid')
3006        self.student['studycourse'].current_level = 100
3007        self.browser.open(self.login_path)
3008        self.browser.getControl(name="form.login").value = self.student_id
3009        self.browser.getControl(name="form.password").value = 'spwd'
3010        self.browser.getControl("Login").click()
3011        # Now students can add the current study level
3012        self.browser.getLink("Study Course").click()
3013        self.browser.getLink("Add course list").click()
3014        self.assertMatches('...Add current level 100 (Year 1)...',
3015                           self.browser.contents)
3016        self.browser.getControl("Create course list now").click()
3017        # A level with one course ticket was created
3018        self.assertEqual(self.student['studycourse']['100'].number_of_tickets, 1)
3019        # We set the score above the passmark
3020        self.student['studycourse']['100'][
3021            'COURSE1'].score = self.student['studycourse']['100'][
3022                'COURSE1'].passmark + 1
3023        # We add a second level
3024        self.student['studycourse'].current_level = 200
3025        self.browser.getLink("Study Course").click()
3026        self.browser.getLink("Add course list").click()
3027        self.assertMatches('...Add current level 200 (Year 2)...',
3028                           self.browser.contents)
3029        self.browser.getControl("Create course list now").click()
3030        self.browser.getLink("200").click()
3031        self.browser.getLink("Edit course list").click()
3032        self.browser.getLink("here").click()
3033        self.browser.getControl(name="form.course").value = ['COURSE1']
3034        self.browser.getControl("Add course ticket").click()
3035        self.assertTrue(
3036            'Course has already been passed at previous level'
3037            in self.browser.contents)
3038        self.assertEqual(self.student['studycourse']['200'].number_of_tickets, 0)
3039        # We set the score below the passmark
3040        self.student['studycourse']['100'][
3041            'COURSE1'].score = self.student['studycourse']['100'][
3042                'COURSE1'].passmark - 1
3043        self.browser.getControl("Add course ticket").click()
3044        self.assertTrue(
3045            'Successfully added COURSE1' in self.browser.contents)
3046        self.assertEqual(self.student['studycourse']['200'].number_of_tickets, 1)
3047        return
3048
3049    def test_student_course_registration_outstanding(self):
3050        self.course = createObject('waeup.Course')
3051        self.course.code = 'COURSE2'
3052        self.course.semester = 1
3053        self.course.credits = 45
3054        self.course.passmark = 40
3055        self.app['faculties']['fac1']['dep1'].courses.addCourse(
3056            self.course)
3057        IWorkflowState(self.student).setState('school fee paid')
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.browser.open(self.student_path + '/studycourse/add')
3063        self.browser.getControl("Create course list now").click()
3064        self.assertEqual(self.student['studycourse']['100'].number_of_tickets, 1)
3065        self.student['studycourse'].current_level = 200
3066        self.browser.getLink("Study Course").click()
3067        self.browser.getLink("Add course list").click()
3068        self.assertMatches('...Add current level 200 (Year 2)...',
3069                           self.browser.contents)
3070        self.browser.getControl("Create course list now").click()
3071        self.browser.getLink("200").click()
3072        self.browser.getLink("Edit course list").click()
3073        self.browser.getLink("here").click()
3074        self.browser.getControl(name="form.course").value = ['COURSE2']
3075        self.browser.getControl("Add course ticket").click()
3076        # Carryover COURSE1 in level 200 already has 10 credits
3077        self.assertMatches(
3078            '...Maximum credits exceeded...', self.browser.contents)
3079        # If COURSE1 is outstanding, its credits won't be considered
3080        self.student['studycourse']['200']['COURSE1'].outstanding = True
3081        self.browser.getControl("Add course ticket").click()
3082        self.assertMatches(
3083            '...Successfully added COURSE2...', self.browser.contents)
3084        return
3085
3086    def test_postgraduate_student_access(self):
3087        self.certificate.study_mode = 'pg_ft'
3088        self.certificate.start_level = 999
3089        self.certificate.end_level = 999
3090        self.student['studycourse'].current_level = 999
3091        IWorkflowState(self.student).setState('school fee paid')
3092        self.browser.open(self.login_path)
3093        self.browser.getControl(name="form.login").value = self.student_id
3094        self.browser.getControl(name="form.password").value = 'spwd'
3095        self.browser.getControl("Login").click()
3096        self.assertTrue(
3097            'You logged in.' in self.browser.contents)
3098        # Now students can add the current study level
3099        self.browser.getLink("Study Course").click()
3100        self.browser.getLink("Add course list").click()
3101        self.assertMatches('...Add current level Postgraduate Level...',
3102                           self.browser.contents)
3103        self.browser.getControl("Create course list now").click()
3104        self.assertTrue("You successfully created a new course list"
3105            in self.browser.contents)
3106        # A level with one course ticket was created
3107        self.assertEqual(self.student['studycourse']['999'].number_of_tickets, 0)
3108        self.browser.getLink("Edit course list").click()
3109        self.browser.getLink("here").click()
3110        self.browser.getControl(name="form.course").value = ['COURSE1']
3111        self.browser.getControl("Add course ticket").click()
3112        self.assertMatches('...Successfully added COURSE1...',
3113                           self.browser.contents)
3114        # Postgraduate students can't register course lists
3115        self.browser.getControl("Register course list").click()
3116        self.assertTrue("your course list can't bee registered"
3117            in self.browser.contents)
3118        self.assertEqual(self.student.state, 'school fee paid')
3119        return
3120
3121    def test_student_clearance_wo_clrcode(self):
3122        IWorkflowState(self.student).setState('clearance started')
3123        self.browser.open(self.login_path)
3124        self.browser.getControl(name="form.login").value = self.student_id
3125        self.browser.getControl(name="form.password").value = 'spwd'
3126        self.browser.getControl("Login").click()
3127        self.browser.open(self.edit_clearance_path)
3128        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
3129        self.browser.getControl("Save and request clearance").click()
3130        self.assertMatches('...Clearance has been requested...',
3131                           self.browser.contents)
3132
3133    def test_student_clearance_payment(self):
3134        # Login
3135        self.browser.open(self.login_path)
3136        self.browser.getControl(name="form.login").value = self.student_id
3137        self.browser.getControl(name="form.password").value = 'spwd'
3138        self.browser.getControl("Login").click()
3139
3140        # Students can add online clearance payment tickets
3141        self.browser.open(self.payments_path + '/addop')
3142        self.browser.getControl(name="form.p_category").value = ['clearance']
3143        self.browser.getControl("Create ticket").click()
3144        self.assertMatches('...ticket created...',
3145                           self.browser.contents)
3146
3147        # Students can't approve the payment
3148        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
3149        ctrl = self.browser.getControl(name='val_id')
3150        value = ctrl.options[0]
3151        self.browser.getLink(value).click()
3152        payment_url = self.browser.url
3153        self.assertRaises(
3154            Unauthorized, self.browser.open, payment_url + '/approve')
3155        # In the base package they can 'use' a fake approval view.
3156        # XXX: I tried to use
3157        # self.student['payments'][value].approveStudentPayment() instead.
3158        # But this function fails in
3159        # w.k.accesscodes.accesscode.create_accesscode.
3160        # grok.getSite returns None in tests.
3161        self.browser.open(payment_url + '/fake_approve')
3162        self.assertMatches('...Payment approved...',
3163                          self.browser.contents)
3164        expected = '''...
3165        <td>
3166          <span>Paid</span>
3167        </td>...'''
3168        expected = '''...
3169        <td>
3170          <span>Paid</span>
3171        </td>...'''
3172        self.assertMatches(expected,self.browser.contents)
3173        payment_id = self.student['payments'].keys()[0]
3174        payment = self.student['payments'][payment_id]
3175        self.assertEqual(payment.p_state, 'paid')
3176        self.assertEqual(payment.r_amount_approved, 3456.0)
3177        self.assertEqual(payment.r_code, 'AP')
3178        self.assertEqual(payment.r_desc, u'Payment approved by Anna Tester')
3179        # The new CLR-0 pin has been created
3180        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
3181        pin = self.app['accesscodes']['CLR-0'].keys()[0]
3182        ac = self.app['accesscodes']['CLR-0'][pin]
3183        self.assertEqual(ac.owner, self.student_id)
3184        self.assertEqual(ac.cost, 3456.0)
3185
3186        # Students can open the pdf payment slip
3187        self.browser.open(payment_url + '/payment_slip.pdf')
3188        self.assertEqual(self.browser.headers['Status'], '200 Ok')
3189        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
3190
3191        # The new CLR-0 pin can be used for starting clearance
3192        # but they have to upload a passport picture first
3193        # which is only possible in state admitted
3194        self.browser.open(self.student_path + '/change_portrait')
3195        self.assertMatches('...form is locked...',
3196                          self.browser.contents)
3197        IWorkflowInfo(self.student).fireTransition('admit')
3198        self.browser.open(self.student_path + '/change_portrait')
3199        image = open(SAMPLE_IMAGE, 'rb')
3200        ctrl = self.browser.getControl(name='passportuploadedit')
3201        file_ctrl = ctrl.mech_control
3202        file_ctrl.add_file(image, filename='my_photo.jpg')
3203        self.browser.getControl(
3204            name='upload_passportuploadedit').click()
3205        self.browser.open(self.student_path + '/start_clearance')
3206        parts = pin.split('-')[1:]
3207        clrseries, clrnumber = parts
3208        self.browser.getControl(name="ac_series").value = clrseries
3209        self.browser.getControl(name="ac_number").value = clrnumber
3210        self.browser.getControl("Start clearance now").click()
3211        self.assertMatches('...Clearance process has been started...',
3212                           self.browser.contents)
3213
3214    def test_student_schoolfee_payment(self):
3215        configuration = createObject('waeup.SessionConfiguration')
3216        configuration.academic_session = 2005
3217        self.app['configuration'].addSessionConfiguration(configuration)
3218        # Login
3219        self.browser.open(self.login_path)
3220        self.browser.getControl(name="form.login").value = self.student_id
3221        self.browser.getControl(name="form.password").value = 'spwd'
3222        self.browser.getControl("Login").click()
3223
3224        # Students can add online school fee payment tickets.
3225        IWorkflowState(self.student).setState('returning')
3226        self.browser.open(self.payments_path)
3227        self.assertRaises(
3228            LookupError, self.browser.getControl, name='val_id')
3229        self.browser.getLink("Add current session payment ticket").click()
3230        self.browser.getControl(name="form.p_category").value = ['schoolfee']
3231        self.browser.getControl("Create ticket").click()
3232        self.assertMatches('...ticket created...',
3233                           self.browser.contents)
3234        ctrl = self.browser.getControl(name='val_id')
3235        value = ctrl.options[0]
3236        self.browser.getLink(value).click()
3237        self.assertMatches('...Amount Authorized...',
3238                           self.browser.contents)
3239        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
3240        # Payment session and will be calculated as defined
3241        # in w.k.students.utils because we set changed the state
3242        # to returning
3243        self.assertEqual(self.student['payments'][value].p_session, 2005)
3244        self.assertEqual(self.student['payments'][value].p_level, 200)
3245
3246        # Student is the payer of the payment ticket.
3247        payer = IPayer(self.student['payments'][value])
3248        self.assertEqual(payer.display_fullname, 'Anna Tester')
3249        self.assertEqual(payer.id, self.student_id)
3250        self.assertEqual(payer.faculty, 'fac1')
3251        self.assertEqual(payer.department, 'dep1')
3252
3253        # We simulate the approval
3254        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
3255        self.browser.open(self.browser.url + '/fake_approve')
3256        self.assertMatches('...Payment approved...',
3257                          self.browser.contents)
3258
3259        ## The new SFE-0 pin can be used for starting new session
3260        #self.browser.open(self.studycourse_path)
3261        #self.browser.getLink('Start new session').click()
3262        #pin = self.app['accesscodes']['SFE-0'].keys()[0]
3263        #parts = pin.split('-')[1:]
3264        #sfeseries, sfenumber = parts
3265        #self.browser.getControl(name="ac_series").value = sfeseries
3266        #self.browser.getControl(name="ac_number").value = sfenumber
3267        #self.browser.getControl("Start now").click()
3268        #self.assertMatches('...Session started...',
3269        #                   self.browser.contents)
3270
3271        self.assertTrue(self.student.state == 'school fee paid')
3272        return
3273
3274    def test_student_bedallocation_payment(self):
3275        # Login
3276        self.browser.open(self.login_path)
3277        self.browser.getControl(name="form.login").value = self.student_id
3278        self.browser.getControl(name="form.password").value = 'spwd'
3279        self.browser.getControl("Login").click()
3280        self.browser.open(self.payments_path)
3281        self.browser.open(self.payments_path + '/addop')
3282        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
3283        self.browser.getControl("Create ticket").click()
3284        self.assertMatches('...ticket created...',
3285                           self.browser.contents)
3286        # Students can remove only online payment tickets which have
3287        # not received a valid callback
3288        self.browser.open(self.payments_path)
3289        ctrl = self.browser.getControl(name='val_id')
3290        value = ctrl.options[0]
3291        ctrl.getControl(value=value).selected = True
3292        self.browser.getControl("Remove selected", index=0).click()
3293        self.assertTrue('Successfully removed' in self.browser.contents)
3294
3295    def test_student_maintenance_payment(self):
3296        # Login
3297        self.browser.open(self.login_path)
3298        self.browser.getControl(name="form.login").value = self.student_id
3299        self.browser.getControl(name="form.password").value = 'spwd'
3300        self.browser.getControl("Login").click()
3301        self.browser.open(self.payments_path)
3302        self.browser.open(self.payments_path + '/addop')
3303        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
3304        self.browser.getControl("Create ticket").click()
3305        self.assertMatches('...You have not yet booked accommodation...',
3306                           self.browser.contents)
3307        # We continue this test in test_student_accommodation
3308
3309    def test_student_previous_payments(self):
3310        configuration = createObject('waeup.SessionConfiguration')
3311        configuration.academic_session = 2000
3312        configuration.clearance_fee = 3456.0
3313        configuration.booking_fee = 123.4
3314        self.app['configuration'].addSessionConfiguration(configuration)
3315        configuration2 = createObject('waeup.SessionConfiguration')
3316        configuration2.academic_session = 2003
3317        configuration2.clearance_fee = 3456.0
3318        configuration2.booking_fee = 123.4
3319        self.app['configuration'].addSessionConfiguration(configuration2)
3320        configuration3 = createObject('waeup.SessionConfiguration')
3321        configuration3.academic_session = 2005
3322        configuration3.clearance_fee = 3456.0
3323        configuration3.booking_fee = 123.4
3324        self.app['configuration'].addSessionConfiguration(configuration3)
3325        self.student['studycourse'].entry_session = 2002
3326
3327        # Login
3328        self.browser.open(self.login_path)
3329        self.browser.getControl(name="form.login").value = self.student_id
3330        self.browser.getControl(name="form.password").value = 'spwd'
3331        self.browser.getControl("Login").click()
3332
3333        # Students can add previous school fee payment tickets in any state.
3334        IWorkflowState(self.student).setState('courses registered')
3335        self.browser.open(self.payments_path)
3336        self.browser.getLink("Add previous session payment ticket").click()
3337
3338        # Previous session payment form is provided
3339        self.assertEqual(self.student.current_session, 2004)
3340        self.browser.getControl(name="form.p_category").value = ['schoolfee']
3341        self.browser.getControl(name="form.p_session").value = ['2000']
3342        self.browser.getControl(name="form.p_level").value = ['300']
3343        self.browser.getControl("Create ticket").click()
3344        self.assertMatches('...The previous session must not fall below...',
3345                           self.browser.contents)
3346        self.browser.getControl(name="form.p_category").value = ['schoolfee']
3347        self.browser.getControl(name="form.p_session").value = ['2005']
3348        self.browser.getControl(name="form.p_level").value = ['300']
3349        self.browser.getControl("Create ticket").click()
3350        self.assertMatches('...This is not a previous session...',
3351                           self.browser.contents)
3352
3353        # Students can pay current session school fee.
3354        self.browser.getControl(name="form.p_category").value = ['schoolfee']
3355        self.browser.getControl(name="form.p_session").value = ['2004']
3356        self.browser.getControl(name="form.p_level").value = ['300']
3357        self.browser.getControl("Create ticket").click()
3358        self.assertMatches('...ticket created...',
3359                           self.browser.contents)
3360        ctrl = self.browser.getControl(name='val_id')
3361        value = ctrl.options[0]
3362        self.browser.getLink(value).click()
3363        self.assertMatches('...Amount Authorized...',
3364                           self.browser.contents)
3365        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
3366
3367        # Ticket creation is logged.
3368        logfile = os.path.join(
3369            self.app['datacenter'].storage, 'logs', 'students.log')
3370        logcontent = open(logfile).read()
3371        self.assertTrue(
3372            'K1000000 - students.browser.PreviousPaymentAddFormPage - '
3373            'K1000000 - added: %s' % value
3374            in logcontent)
3375
3376        # Payment session is properly set
3377        self.assertEqual(self.student['payments'][value].p_session, 2004)
3378        self.assertEqual(self.student['payments'][value].p_level, 300)
3379
3380        # We simulate the approval
3381        self.browser.open(self.browser.url + '/fake_approve')
3382        self.assertMatches('...Payment approved...',
3383                          self.browser.contents)
3384
3385        # No AC has been created
3386        self.assertEqual(len(self.app['accesscodes']['SFE-0'].keys()), 0)
3387        self.assertTrue(self.student['payments'][value].ac is None)
3388
3389        # Current payment flag is set False
3390        self.assertFalse(self.student['payments'][value].p_current)
3391
3392        # Button and form are not available for students who are in
3393        # states up to cleared
3394        self.student['studycourse'].entry_session = 2004
3395        IWorkflowState(self.student).setState('cleared')
3396        self.browser.open(self.payments_path)
3397        self.assertFalse(
3398            "Add previous session payment ticket" in self.browser.contents)
3399        self.browser.open(self.payments_path + '/addpp')
3400        self.assertTrue(
3401            "No previous payment to be made" in self.browser.contents)
3402        return
3403
3404    def test_postgraduate_student_payments(self):
3405        configuration = createObject('waeup.SessionConfiguration')
3406        configuration.academic_session = 2005
3407        self.app['configuration'].addSessionConfiguration(configuration)
3408        self.certificate.study_mode = 'pg_ft'
3409        self.certificate.start_level = 999
3410        self.certificate.end_level = 999
3411        self.student['studycourse'].current_level = 999
3412        # Login
3413        self.browser.open(self.login_path)
3414        self.browser.getControl(name="form.login").value = self.student_id
3415        self.browser.getControl(name="form.password").value = 'spwd'
3416        self.browser.getControl("Login").click()
3417        # Students can add online school fee payment tickets.
3418        IWorkflowState(self.student).setState('cleared')
3419        self.browser.open(self.payments_path)
3420        self.browser.getLink("Add current session payment ticket").click()
3421        self.browser.getControl(name="form.p_category").value = ['schoolfee']
3422        self.browser.getControl("Create ticket").click()
3423        self.assertMatches('...ticket created...',
3424                           self.browser.contents)
3425        ctrl = self.browser.getControl(name='val_id')
3426        value = ctrl.options[0]
3427        self.browser.getLink(value).click()
3428        self.assertMatches('...Amount Authorized...',
3429                           self.browser.contents)
3430        # Payment session and level are current ones.
3431        # Postgrads have to pay school_fee_1.
3432        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
3433        self.assertEqual(self.student['payments'][value].p_session, 2004)
3434        self.assertEqual(self.student['payments'][value].p_level, 999)
3435
3436        # We simulate the approval
3437        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
3438        self.browser.open(self.browser.url + '/fake_approve')
3439        self.assertMatches('...Payment approved...',
3440                          self.browser.contents)
3441
3442        ## The new SFE-0 pin can be used for starting session
3443        #self.browser.open(self.studycourse_path)
3444        #self.browser.getLink('Start new session').click()
3445        #pin = self.app['accesscodes']['SFE-0'].keys()[0]
3446        #parts = pin.split('-')[1:]
3447        #sfeseries, sfenumber = parts
3448        #self.browser.getControl(name="ac_series").value = sfeseries
3449        #self.browser.getControl(name="ac_number").value = sfenumber
3450        #self.browser.getControl("Start now").click()
3451        #self.assertMatches('...Session started...',
3452        #                   self.browser.contents)
3453
3454        self.assertTrue(self.student.state == 'school fee paid')
3455
3456        # Postgrad students do not need to register courses the
3457        # can just pay for the next session.
3458        self.browser.open(self.payments_path)
3459        # Remove first payment to be sure that we access the right ticket
3460        del self.student['payments'][value]
3461        self.browser.getLink("Add current session payment ticket").click()
3462        self.browser.getControl(name="form.p_category").value = ['schoolfee']
3463        self.browser.getControl("Create ticket").click()
3464        ctrl = self.browser.getControl(name='val_id')
3465        value = ctrl.options[0]
3466        self.browser.getLink(value).click()
3467        # Payment session has increased by one, payment level remains the same.
3468        # Returning Postgraduates have to pay school_fee_2.
3469        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
3470        self.assertEqual(self.student['payments'][value].p_session, 2005)
3471        self.assertEqual(self.student['payments'][value].p_level, 999)
3472
3473        # Student is still in old session
3474        self.assertEqual(self.student.current_session, 2004)
3475
3476        # We do not need to pay the ticket if any other
3477        # SFE pin is provided
3478        pin_container = self.app['accesscodes']
3479        pin_container.createBatch(
3480            datetime.utcnow(), 'some_userid', 'SFE', 9.99, 1)
3481        pin = pin_container['SFE-1'].values()[0].representation
3482        sfeseries, sfenumber = pin.split('-')[1:]
3483        # The new SFE-1 pin can be used for starting new session
3484        self.browser.open(self.studycourse_path)
3485        self.browser.getLink('Start new session').click()
3486        self.browser.getControl(name="ac_series").value = sfeseries
3487        self.browser.getControl(name="ac_number").value = sfenumber
3488        self.browser.getControl("Start now").click()
3489        self.assertMatches('...Session started...',
3490                           self.browser.contents)
3491        self.assertTrue(self.student.state == 'school fee paid')
3492        # Student is in new session
3493        self.assertEqual(self.student.current_session, 2005)
3494        self.assertEqual(self.student['studycourse'].current_level, 999)
3495        return
3496
3497    def test_student_accommodation(self):
3498        # Create a second hostel with one bed
3499        hostel = Hostel()
3500        hostel.hostel_id = u'hall-2'
3501        hostel.hostel_name = u'Hall 2'
3502        self.app['hostels'].addHostel(hostel)
3503        bed = Bed()
3504        bed.bed_id = u'hall-2_A_101_A'
3505        bed.bed_number = 1
3506        bed.owner = NOT_OCCUPIED
3507        bed.bed_type = u'regular_female_fr'
3508        self.app['hostels'][hostel.hostel_id].addBed(bed)
3509        self.app['hostels'].allocation_expiration = 7
3510
3511        self.browser.open(self.login_path)
3512        self.browser.getControl(name="form.login").value = self.student_id
3513        self.browser.getControl(name="form.password").value = 'spwd'
3514        self.browser.getControl("Login").click()
3515        # Students can add online booking fee payment tickets and open the
3516        # callback view (see test_manage_payments).
3517        self.browser.getLink("Payments").click()
3518        self.browser.getLink("Add current session payment ticket").click()
3519        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
3520        self.browser.getControl("Create ticket").click()
3521        ctrl = self.browser.getControl(name='val_id')
3522        value = ctrl.options[0]
3523        self.browser.getLink(value).click()
3524        self.browser.open(self.browser.url + '/fake_approve')
3525        # The new HOS-0 pin has been created.
3526        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
3527        pin = self.app['accesscodes']['HOS-0'].keys()[0]
3528        ac = self.app['accesscodes']['HOS-0'][pin]
3529        parts = pin.split('-')[1:]
3530        sfeseries, sfenumber = parts
3531        # Students can use HOS code and book a bed space with it ...
3532        self.browser.open(self.acco_path)
3533        # ... but not if booking period has expired ...
3534        self.app['hostels'].enddate = datetime.now(pytz.utc)
3535        self.browser.getControl("Book accommodation").click()
3536        self.assertMatches('...Outside booking period: ...',
3537                           self.browser.contents)
3538        self.app['hostels'].enddate = datetime.now(pytz.utc) + timedelta(days=10)
3539        # ... or student data are incomplete ...
3540        self.student['studycourse'].current_level = None
3541        self.browser.getControl("Book accommodation").click()
3542        self.assertMatches('...Your data are incomplete...',
3543            self.browser.contents)
3544        self.student['studycourse'].current_level = 100
3545        # ... or student is not the an allowed state ...
3546        self.browser.getControl("Book accommodation").click()
3547        self.assertMatches('...You are in the wrong...',
3548                           self.browser.contents)
3549        # Students can still not see the disired hostel selector.
3550        self.assertFalse('desired hostel' in self.browser.contents)
3551        IWorkflowInfo(self.student).fireTransition('admit')
3552        # Students can now see the disired hostel selector.
3553        self.browser.reload()
3554        self.browser.open(self.acco_path)
3555        self.assertTrue('desired hostel' in self.browser.contents)
3556        self.browser.getControl(name="hostel").value = ['hall-2']
3557        self.browser.getControl("Save").click()
3558        self.assertTrue('selection has been saved' in self.browser.contents)
3559        self.assertTrue('<option selected="selected" value="hall-2">'
3560            in self.browser.contents)
3561        self.browser.getControl("Book accommodation").click()
3562        self.assertMatches('...Activation Code:...',
3563                           self.browser.contents)
3564        # Student can't use faked ACs ...
3565        self.browser.getControl(name="ac_series").value = u'nonsense'
3566        self.browser.getControl(name="ac_number").value = sfenumber
3567        self.browser.getControl("Create bed ticket").click()
3568        self.assertMatches('...Activation code is invalid...',
3569                           self.browser.contents)
3570        # ... or ACs owned by somebody else.
3571        ac.owner = u'Anybody'
3572        self.browser.getControl(name="ac_series").value = sfeseries
3573        self.browser.getControl(name="ac_number").value = sfenumber
3574        # There is no free bed space and the bed selector does not appear
3575        self.assertFalse('<option value="hall-1_A_101_A">'
3576            in self.browser.contents)
3577        self.browser.getControl("Create bed ticket").click()
3578        # Hostel 2 has only a bed for women.
3579        self.assertTrue('There is no free bed in your category regular_male_fr.'
3580            in self.browser.contents)
3581        self.browser.getControl(name="hostel").value = ['hall-1']
3582        self.browser.getControl("Save").click()
3583        self.browser.getControl("Book accommodation").click()
3584        # Student can't use faked ACs ...
3585        self.browser.getControl(name="ac_series").value = sfeseries
3586        self.browser.getControl(name="ac_number").value = sfenumber
3587        self.browser.getControl("Create bed ticket").click()
3588        self.assertMatches('...You are not the owner of this access code...',
3589                           self.browser.contents)
3590        # The bed remains empty.
3591        bed = self.app['hostels']['hall-1']['hall-1_A_101_A']
3592        self.assertTrue(bed.owner == NOT_OCCUPIED)
3593        ac.owner = self.student_id
3594        self.browser.open(self.acco_path + '/add')
3595        self.browser.getControl(name="ac_series").value = sfeseries
3596        self.browser.getControl(name="ac_number").value = sfenumber
3597        # Bed can be selected
3598        self.browser.getControl(name="bed").value = ['hall-1_A_101_A']
3599        self.browser.getControl("Create bed ticket").click()
3600        self.assertTrue('Bed ticket created and bed booked'
3601            in self.browser.contents)
3602        # Bed has been allocated.
3603        self.assertTrue(bed.owner == self.student_id)
3604        # BedTicketAddPage is now blocked.
3605        self.browser.getControl("Book accommodation").click()
3606        self.assertMatches('...You already booked a bed space...',
3607            self.browser.contents)
3608        # The bed ticket displays the data correctly.
3609        self.browser.open(self.acco_path + '/2004')
3610        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
3611                           self.browser.contents)
3612        self.assertMatches('...2004/2005...', self.browser.contents)
3613        self.assertMatches('...regular_male_fr...', self.browser.contents)
3614        self.assertMatches('...%s...' % pin, self.browser.contents)
3615        # Students can open the pdf slip.
3616        self.browser.open(self.browser.url + '/bed_allocation_slip.pdf')
3617        self.assertEqual(self.browser.headers['Status'], '200 Ok')
3618        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
3619        path = os.path.join(samples_dir(), 'bed_allocation_slip.pdf')
3620        open(path, 'wb').write(self.browser.contents)
3621        print "Sample PDF bed_allocation_slip.pdf written to %s" % path
3622        # Students can't relocate themselves.
3623        self.assertFalse('Relocate' in self.browser.contents)
3624        relocate_path = self.acco_path + '/2004/relocate'
3625        self.assertRaises(
3626            Unauthorized, self.browser.open, relocate_path)
3627        # Students can't see the Remove button and check boxes.
3628        self.browser.open(self.acco_path)
3629        self.assertFalse('Remove' in self.browser.contents)
3630        self.assertFalse('val_id' in self.browser.contents)
3631        # Students can pay maintenance fee now.
3632        self.browser.open(self.payments_path)
3633        self.browser.open(self.payments_path + '/addop')
3634        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
3635        self.browser.getControl("Create ticket").click()
3636        self.assertMatches('...Payment ticket created...',
3637                           self.browser.contents)
3638        ctrl = self.browser.getControl(name='val_id')
3639        value = ctrl.options[0]
3640        # Maintennace fee is taken from the hostel object.
3641        self.assertEqual(self.student['payments'][value].amount_auth, 876.0)
3642        # If the hostel's maintenance fee isn't set, the fee is
3643        # taken from the session configuration object.
3644        self.app['hostels']['hall-1'].maint_fee = 0.0
3645        self.browser.open(self.payments_path + '/addop')
3646        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
3647        self.browser.getControl("Create ticket").click()
3648        ctrl = self.browser.getControl(name='val_id')
3649        value = ctrl.options[1]
3650        self.assertEqual(self.student['payments'][value].amount_auth, 987.0)
3651        # The bedticket is aware of successfull maintenance fee payment
3652        bedticket = self.student['accommodation']['2004']
3653        self.assertFalse(bedticket.maint_payment_made)
3654        self.student['payments'][value].approve()
3655        self.assertTrue(bedticket.maint_payment_made)
3656        return
3657
3658    def test_change_password_request(self):
3659        self.browser.open('http://localhost/app/changepw')
3660        self.browser.getControl(name="form.identifier").value = '123'
3661        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
3662        self.browser.getControl("Send login credentials").click()
3663        self.assertTrue('An email with' in self.browser.contents)
3664
3665    def test_student_expired_personal_data(self):
3666        # Login
3667        IWorkflowState(self.student).setState('school fee paid')
3668        delta = timedelta(days=180)
3669        self.student.personal_updated = datetime.utcnow() - delta
3670        self.browser.open(self.login_path)
3671        self.browser.getControl(name="form.login").value = self.student_id
3672        self.browser.getControl(name="form.password").value = 'spwd'
3673        self.browser.getControl("Login").click()
3674        self.assertEqual(self.browser.url, self.student_path)
3675        self.assertTrue(
3676            'You logged in' in self.browser.contents)
3677        # Students don't see personal_updated field in edit form
3678        self.browser.open(self.edit_personal_path)
3679        self.assertFalse('Updated' in self.browser.contents)
3680        self.browser.open(self.personal_path)
3681        self.assertTrue('Updated' in self.browser.contents)
3682        self.browser.getLink("Logout").click()
3683        delta = timedelta(days=181)
3684        self.student.personal_updated = datetime.utcnow() - delta
3685        self.browser.open(self.login_path)
3686        self.browser.getControl(name="form.login").value = self.student_id
3687        self.browser.getControl(name="form.password").value = 'spwd'
3688        self.browser.getControl("Login").click()
3689        self.assertEqual(self.browser.url, self.edit_personal_path)
3690        self.assertTrue(
3691            'Your personal data record is outdated.' in self.browser.contents)
3692
3693    def test_request_transcript(self):
3694        IWorkflowState(self.student).setState('graduated')
3695        self.browser.open(self.login_path)
3696        self.browser.getControl(name="form.login").value = self.student_id
3697        self.browser.getControl(name="form.password").value = 'spwd'
3698        self.browser.getControl("Login").click()
3699        self.assertMatches(
3700            '...You logged in...', self.browser.contents)
3701        # Create payment ticket
3702        self.browser.open(self.payments_path)
3703        self.browser.open(self.payments_path + '/addop')
3704        self.browser.getControl(name="form.p_category").value = ['transcript']
3705        self.browser.getControl("Create ticket").click()
3706        ctrl = self.browser.getControl(name='val_id')
3707        value = ctrl.options[0]
3708        self.browser.getLink(value).click()
3709        self.assertMatches('...Amount Authorized...',
3710                           self.browser.contents)
3711        self.assertEqual(self.student['payments'][value].amount_auth, 4567.0)
3712        # Student is the payer of the payment ticket.
3713        payer = IPayer(self.student['payments'][value])
3714        self.assertEqual(payer.display_fullname, 'Anna Tester')
3715        self.assertEqual(payer.id, self.student_id)
3716        self.assertEqual(payer.faculty, 'fac1')
3717        self.assertEqual(payer.department, 'dep1')
3718        # We simulate the approval and fetch the pin
3719        self.assertEqual(len(self.app['accesscodes']['TSC-0']),0)
3720        self.browser.open(self.browser.url + '/fake_approve')
3721        self.assertMatches('...Payment approved...',
3722                          self.browser.contents)
3723        pin = self.app['accesscodes']['TSC-0'].keys()[0]
3724        parts = pin.split('-')[1:]
3725        tscseries, tscnumber = parts
3726        # Student can use the pin to send the transcript request
3727        self.browser.open(self.student_path)
3728        self.browser.getLink("Request transcript").click()
3729        self.browser.getControl(name="ac_series").value = tscseries
3730        self.browser.getControl(name="ac_number").value = tscnumber
3731        self.browser.getControl(name="comment").value = 'Comment line 1 \nComment line2'
3732        self.browser.getControl(name="address").value = 'Address line 1 \nAddress line2'
3733        self.browser.getControl("Request now").click()
3734        self.assertMatches('...Transcript processing has been started...',
3735                          self.browser.contents)
3736        self.assertEqual(self.student.state, 'transcript requested')
3737        self.assertMatches(
3738            '... UTC K1000000 wrote:\n\nComment line 1 \n'
3739            'Comment line2\n\nDispatch Address:\nAddress line 1 \n'
3740            'Address line2\n\n', self.student['studycourse'].transcript_comment)
3741        # The comment has been logged
3742        logfile = os.path.join(
3743            self.app['datacenter'].storage, 'logs', 'students.log')
3744        logcontent = open(logfile).read()
3745        self.assertTrue(
3746            'K1000000 - students.browser.StudentTranscriptRequestPage - '
3747            'K1000000 - comment: Comment line 1 <br>Comment line2\n'
3748            in logcontent)
3749
3750    def test_late_registration(self):
3751        # Login
3752        delta = timedelta(days=10)
3753        self.app['configuration'][
3754            '2004'].coursereg_deadline = datetime.now(pytz.utc) - delta
3755        IWorkflowState(self.student).setState('school fee paid')
3756        self.browser.open(self.login_path)
3757        self.browser.getControl(name="form.login").value = self.student_id
3758        self.browser.getControl(name="form.password").value = 'spwd'
3759        self.browser.getControl("Login").click()
3760        self.browser.open(self.payments_path)
3761        self.browser.open(self.payments_path + '/addop')
3762        self.browser.getControl(name="form.p_category").value = ['late_registration']
3763        self.browser.getControl("Create ticket").click()
3764        self.assertMatches('...ticket created...',
3765                           self.browser.contents)
3766        self.browser.open(self.payments_path)
3767        ctrl = self.browser.getControl(name='val_id')
3768        value = ctrl.options[0]
3769        self.browser.getLink("Study Course").click()
3770        self.browser.getLink("Add course list").click()
3771        self.assertMatches('...Add current level 100 (Year 1)...',
3772                           self.browser.contents)
3773        self.browser.getControl("Create course list now").click()
3774        self.browser.getLink("100").click()
3775        self.browser.getLink("Edit course list").click()
3776        self.browser.getControl("Register course list").click()
3777        self.assertTrue('Course registration has ended. Please pay' in self.browser.contents)
3778        self.student['payments'][value].approve()
3779        self.browser.getControl("Register course list").click()
3780        self.assertTrue('Course list has been registered' in self.browser.contents)
3781        self.assertEqual(self.student.state, 'courses registered')
3782
3783    def test_former_course(self):
3784        IWorkflowState(self.student).setState('school fee paid')
3785        self.student['studycourse'].current_level = 100
3786        self.browser.open(self.login_path)
3787        self.browser.getControl(name="form.login").value = self.student_id
3788        self.browser.getControl(name="form.password").value = 'spwd'
3789        self.browser.getControl("Login").click()
3790        # Now students can add the current study level
3791        self.browser.getLink("Study Course").click()
3792        self.browser.getLink("Add course list").click()
3793        self.assertMatches('...Add current level 100 (Year 1)...',
3794                           self.browser.contents)
3795        self.browser.getControl("Create course list now").click()
3796        # A level with one course ticket was created
3797        self.assertEqual(
3798            self.student['studycourse']['100'].number_of_tickets, 1)
3799        self.browser.getLink("100").click()
3800        self.assertTrue('<td>Unnamed Course</td>' in self.browser.contents)
3801        self.browser.getLink("Edit course list").click()
3802        del self.student['studycourse']['100']['COURSE1']
3803        # Course can be added again via CourseTicketAddFormPage2
3804        self.browser.getLink("here").click()
3805        self.browser.getControl(name="form.course").value = ['COURSE1']
3806        self.browser.getControl("Add course ticket").click()
3807        self.assertTrue('Successfully added COURSE1' in self.browser.contents)
3808        self.assertEqual(len(self.student['studycourse']['100'].keys()),1)
3809        del self.student['studycourse']['100']['COURSE1']
3810        self.course.former_course = True
3811        self.browser.getLink("here").click()
3812        self.browser.getControl(name="form.course").value = ['COURSE1']
3813        self.browser.getControl("Add course ticket").click()
3814        self.assertTrue('Former courses can\'t be added.' in self.browser.contents)
3815        self.assertEqual(len(self.student['studycourse']['100'].keys()),0)
3816        # Course can be added again via StudyLevelEditFormPage
3817        self.browser.getLink("100").click()
3818        self.browser.getLink("Edit course list").click()
3819        self.browser.getControl(name="course").value = 'COURSE1'
3820        self.browser.getControl("Add course ticket").click()
3821        self.assertTrue('Former courses can\'t be added.' in
3822            self.browser.contents)
3823        self.assertEqual(len(self.student['studycourse']['100'].keys()),0)
3824        # but added if current course
3825        self.course.former_course = False
3826        self.browser.getControl(name="course").value = 'COURSE1'
3827        self.browser.getControl("Add course ticket").click()
3828        self.assertTrue('Successfully added COURSE1' in self.browser.contents)
3829        self.assertEqual(len(self.student['studycourse']['100'].keys()),1)
3830        return
3831
3832class StudentRequestPWTests(StudentsFullSetup):
3833    # Tests for student registration
3834
3835    layer = FunctionalLayer
3836
3837    def test_request_pw(self):
3838        # Student with wrong number can't be found.
3839        self.browser.open('http://localhost/app/requestpw')
3840        self.browser.getControl(name="form.lastname").value = 'Tester'
3841        self.browser.getControl(name="form.number").value = 'anynumber'
3842        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
3843        self.browser.getControl("Send login credentials").click()
3844        self.assertTrue('No student record found.'
3845            in self.browser.contents)
3846        # Anonymous is not informed that lastname verification failed.
3847        # It seems that the record doesn't exist.
3848        self.browser.open('http://localhost/app/requestpw')
3849        self.browser.getControl(name="form.lastname").value = 'Johnny'
3850        self.browser.getControl(name="form.number").value = '123'
3851        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
3852        self.browser.getControl("Send login credentials").click()
3853        self.assertTrue('No student record found.'
3854            in self.browser.contents)
3855        # Even with the correct lastname we can't register if a
3856        # password has been set and used.
3857        self.browser.getControl(name="form.lastname").value = 'Tester'
3858        self.browser.getControl(name="form.number").value = '123'
3859        self.browser.getControl("Send login credentials").click()
3860        self.assertTrue('Your password has already been set and used.'
3861            in self.browser.contents)
3862        self.browser.open('http://localhost/app/requestpw')
3863        self.app['students'][self.student_id].password = None
3864        # The lastname field, used for verification, is not case-sensitive.
3865        self.browser.getControl(name="form.lastname").value = 'tESTer'
3866        self.browser.getControl(name="form.number").value = '123'
3867        self.browser.getControl(name="form.email").value = 'new@yy.zz'
3868        self.browser.getControl("Send login credentials").click()
3869        # Yeah, we succeded ...
3870        self.assertTrue('Your password request was successful.'
3871            in self.browser.contents)
3872        # We can also use the matric_number instead.
3873        self.browser.open('http://localhost/app/requestpw')
3874        self.browser.getControl(name="form.lastname").value = 'tESTer'
3875        self.browser.getControl(name="form.number").value = '234'
3876        self.browser.getControl(name="form.email").value = 'new@yy.zz'
3877        self.browser.getControl("Send login credentials").click()
3878        self.assertTrue('Your password request was successful.'
3879            in self.browser.contents)
3880        # ... and  student can be found in the catalog via the email address
3881        cat = queryUtility(ICatalog, name='students_catalog')
3882        results = list(
3883            cat.searchResults(
3884            email=('new@yy.zz', 'new@yy.zz')))
3885        self.assertEqual(self.student,results[0])
3886        logfile = os.path.join(
3887            self.app['datacenter'].storage, 'logs', 'main.log')
3888        logcontent = open(logfile).read()
3889        self.assertTrue('zope.anybody - students.browser.StudentRequestPasswordPage - '
3890                        '234 (K1000000) - new@yy.zz' in logcontent)
3891        return
3892
3893    def test_student_locked_level_forms(self):
3894
3895        # Add two study levels, one current and one previous
3896        studylevel = createObject(u'waeup.StudentStudyLevel')
3897        studylevel.level = 100
3898        self.student['studycourse'].addStudentStudyLevel(
3899            self.certificate, studylevel)
3900        studylevel = createObject(u'waeup.StudentStudyLevel')
3901        studylevel.level = 200
3902        self.student['studycourse'].addStudentStudyLevel(
3903            self.certificate, studylevel)
3904        IWorkflowState(self.student).setState('school fee paid')
3905        self.student['studycourse'].current_level = 200
3906
3907        self.browser.open(self.login_path)
3908        self.browser.getControl(name="form.login").value = self.student_id
3909        self.browser.getControl(name="form.password").value = 'spwd'
3910        self.browser.getControl("Login").click()
3911
3912        self.browser.open(self.student_path + '/studycourse/200/edit')
3913        self.assertFalse('The requested form is locked' in self.browser.contents)
3914        self.browser.open(self.student_path + '/studycourse/100/edit')
3915        self.assertTrue('The requested form is locked' in self.browser.contents)
3916
3917        self.browser.open(self.student_path + '/studycourse/200/ctadd')
3918        self.assertFalse('The requested form is locked' in self.browser.contents)
3919        self.browser.open(self.student_path + '/studycourse/100/ctadd')
3920        self.assertTrue('The requested form is locked' in self.browser.contents)
3921
3922        IWorkflowState(self.student).setState('courses registered')
3923        self.browser.open(self.student_path + '/studycourse/200/edit')
3924        self.assertTrue('The requested form is locked' in self.browser.contents)
3925        self.browser.open(self.student_path + '/studycourse/200/ctadd')
3926        self.assertTrue('The requested form is locked' in self.browser.contents)
3927
3928
3929class PublicPagesTests(StudentsFullSetup):
3930    # Tests for simple webservices
3931
3932    layer = FunctionalLayer
3933
3934    def test_paymentrequest(self):
3935        payment = createObject('waeup.StudentOnlinePayment')
3936        payment.p_category = u'schoolfee'
3937        payment.p_session = self.student.current_session
3938        payment.p_item = u'My Certificate'
3939        payment.p_id = u'anyid'
3940        self.student['payments']['anykey'] = payment
3941        # Request information about unpaid payment ticket
3942        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
3943        self.assertEqual(self.browser.contents, '-1')
3944        # Request information about paid payment ticket
3945        payment.p_state = u'paid'
3946        notify(grok.ObjectModifiedEvent(payment))
3947        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
3948        self.assertEqual(self.browser.contents,
3949            'FULL_NAME=Anna Tester&FACULTY=fac1&DEPARTMENT=dep1'
3950            '&PAYMENT_ITEM=My Certificate&PAYMENT_CATEGORY=School Fee'
3951            '&ACADEMIC_SESSION=2004/2005&MATRIC_NUMBER=234&REG_NUMBER=123'
3952            '&FEE_AMOUNT=0.0')
3953        self.browser.open('http://localhost/app/paymentrequest?NONSENSE=nonsense')
3954        self.assertEqual(self.browser.contents, '-1')
3955        self.browser.open('http://localhost/app/paymentrequest?P_ID=nonsense')
3956        self.assertEqual(self.browser.contents, '-1')
3957
3958class StudentDataExportTests(StudentsFullSetup, FunctionalAsyncTestCase):
3959    # Tests for StudentsContainer class views and pages
3960
3961    layer = FunctionalLayer
3962
3963    def wait_for_export_job_completed(self):
3964        # helper function waiting until the current export job is completed
3965        manager = getUtility(IJobManager)
3966        job_id = self.app['datacenter'].running_exports[0][0]
3967        job = manager.get(job_id)
3968        wait_for_result(job)
3969        return job_id
3970
3971    def add_payment(self, student):
3972        # get a payment with all fields set
3973        payment = StudentOnlinePayment()
3974        payment.creation_date = datetime(2012, 12, 13)
3975        payment.p_id = 'my-id'
3976        payment.p_category = u'schoolfee'
3977        payment.p_state = 'paid'
3978        payment.ac = u'666'
3979        payment.p_item = u'p-item'
3980        payment.p_level = 100
3981        payment.p_session = curr_year - 6
3982        payment.payment_date = datetime(2012, 12, 13)
3983        payment.amount_auth = 12.12
3984        payment.r_amount_approved = 12.12
3985        payment.r_code = u'r-code'
3986        # XXX: there is no addPayment method to give predictable names
3987        self.payment = student['payments']['my-payment'] = payment
3988        return payment
3989
3990    def test_datacenter_export(self):
3991        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3992        self.browser.open('http://localhost/app/datacenter/@@exportconfig')
3993        self.browser.getControl(name="exporter").value = ['bursary']
3994        self.browser.getControl(name="session").value = ['2004']
3995        self.browser.getControl(name="level").value = ['100']
3996        self.browser.getControl(name="mode").value = ['ug_ft']
3997        self.browser.getControl(name="payments_start").value = '13/12/2012'
3998        self.browser.getControl(name="payments_end").value = '14/12/2012'
3999        self.browser.getControl("Create CSV file").click()
4000
4001        # When the job is finished and we reload the page...
4002        job_id = self.wait_for_export_job_completed()
4003        # ... the csv file can be downloaded ...
4004        self.browser.open('http://localhost/app/datacenter/@@export')
4005        self.browser.getLink("Download").click()
4006        self.assertEqual(self.browser.headers['content-type'],
4007            'text/csv; charset=UTF-8')
4008        self.assertTrue(
4009            'filename="WAeUP.Kofa_bursary_%s.csv' % job_id in
4010            self.browser.headers['content-disposition'])
4011        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
4012        job_id = self.app['datacenter'].running_exports[0][0]
4013        # ... and discarded
4014        self.browser.open('http://localhost/app/datacenter/@@export')
4015        self.browser.getControl("Discard").click()
4016        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
4017        # Creation, downloading and discarding is logged
4018        logfile = os.path.join(
4019            self.app['datacenter'].storage, 'logs', 'datacenter.log')
4020        logcontent = open(logfile).read()
4021        self.assertTrue(
4022            'zope.mgr - students.browser.DatacenterExportJobContainerJobConfig '
4023            '- exported: bursary (2004, 100, ug_ft, None, None, None, '
4024            '13/12/2012, 14/12/2012, all, all, all, all, all), job_id=%s'
4025            % job_id in logcontent
4026            )
4027        self.assertTrue(
4028            'zope.mgr - browser.pages.ExportCSVView '
4029            '- downloaded: WAeUP.Kofa_bursary_%s.csv, job_id=%s'
4030            % (job_id, job_id) in logcontent
4031            )
4032        self.assertTrue(
4033            'zope.mgr - browser.pages.ExportCSVPage '
4034            '- discarded: job_id=%s' % job_id in logcontent
4035            )
4036
4037    def test_datacenter_export_selected(self):
4038        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
4039        self.browser.open('http://localhost/app/datacenter/@@exportselected')
4040        self.browser.getControl(name="exporter").value = ['students']
4041        self.browser.getControl(name="students").value = 'K1000000'
4042        self.browser.getControl("Create CSV file").click()
4043        # When the job is finished and we reload the page...
4044        job_id = self.wait_for_export_job_completed()
4045        # ... the csv file can be downloaded ...
4046        self.browser.open('http://localhost/app/datacenter/@@export')
4047        self.browser.getLink("Download").click()
4048        self.assertEqual(self.browser.headers['content-type'],
4049            'text/csv; charset=UTF-8')
4050        self.assertTrue(
4051            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
4052            self.browser.headers['content-disposition'])
4053        self.assertTrue(
4054            'adm_code,clr_code,date_of_birth,email,employer,'
4055            'firstname,flash_notice,lastname,matric_number,middlename,nationality,'
4056            'officer_comment,parents_email,perm_address,'
4057            'personal_updated,phone,reg_number,'
4058            'sex,student_id,suspended,suspended_comment,'
4059            'password,state,history,certcode,is_postgrad,'
4060            'current_level,current_session,entry_session\r\n'
4061            ',,1981-02-04#,aa@aa.ng,,Anna,,Tester,234,,,,,,,'
4062            '1234#,123,m,K1000000,0,,{SSHA}' in self.browser.contents)
4063        self.browser.open('http://localhost/app/datacenter/@@export')
4064        self.browser.getControl("Discard").click()
4065
4066    def test_payment_dates(self):
4067        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
4068        self.browser.open('http://localhost/app/datacenter/@@exportconfig')
4069        self.browser.getControl(name="exporter").value = ['bursary']
4070        self.browser.getControl(name="session").value = ['2004']
4071        self.browser.getControl(name="level").value = ['100']
4072        self.browser.getControl(name="mode").value = ['ug_ft']
4073        self.browser.getControl(name="payments_start").value = '13/12/2012'
4074        # If one payment date is missing, an error message appears
4075        self.browser.getControl(name="payments_end").value = ''
4076        self.browser.getControl("Create CSV file").click()
4077        self.assertTrue('Payment dates do not match format d/m/Y'
4078            in self.browser.contents)
4079
4080    def test_faculties_export(self):
4081        self.add_payment(self.student)
4082        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
4083        facs_path = 'http://localhost/app/faculties'
4084        self.browser.open(facs_path)
4085        self.browser.getLink("Export student data").click()
4086        self.browser.getControl("Set export parameters").click()
4087        self.browser.getControl(name="exporter").value = ['bursary']
4088        self.browser.getControl(name="session").value = ['2004']
4089        self.browser.getControl(name="level").value = ['100']
4090        self.browser.getControl(name="mode").value = ['ug_ft']
4091        self.browser.getControl(name="payments_start").value = '13/12/2012'
4092        self.browser.getControl(name="payments_end").value = '14/12/2012'
4093        self.browser.getControl(name="paycat").value = ['schoolfee']
4094        self.browser.getControl("Create CSV file").click()
4095
4096        # When the job is finished and we reload the page...
4097        job_id = self.wait_for_export_job_completed()
4098        self.browser.open(facs_path + '/exports')
4099        # ... the csv file can be downloaded ...
4100        self.browser.getLink("Download").click()
4101        self.assertEqual(self.browser.headers['content-type'],
4102            'text/csv; charset=UTF-8')
4103        self.assertTrue(
4104            'filename="WAeUP.Kofa_bursary_%s.csv' % job_id in
4105            self.browser.headers['content-disposition'])
4106        self.assertTrue(
4107            '666,12.12,2012-12-13 00:00:00#,schoolfee,[],1,my-id,p-item,100,%s,'
4108            'paid,2012-12-13 00:00:00#,12.12,r-code,,K1000000,234,123,Anna,,'
4109            'Tester,created,2004,2004,,fac1,dep1,CERT1'
4110            %(curr_year-6) in self.browser.contents)
4111        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
4112        job_id = self.app['datacenter'].running_exports[0][0]
4113        # ... and discarded
4114        self.browser.open(facs_path + '/exports')
4115        self.browser.getControl("Discard").click()
4116        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
4117        # Creation, downloading and discarding is logged
4118        logfile = os.path.join(
4119            self.app['datacenter'].storage, 'logs', 'datacenter.log')
4120        logcontent = open(logfile).read()
4121        self.assertTrue(
4122            'zope.mgr - students.browser.FacultiesExportJobContainerJobConfig '
4123            '- exported: bursary (2004, 100, ug_ft, None, None, None, '
4124            '13/12/2012, 14/12/2012, all, all, schoolfee, all, all), job_id=%s'
4125            % job_id in logcontent
4126            )
4127        self.assertTrue(
4128            'zope.mgr - students.browser.ExportJobContainerDownload '
4129            '- downloaded: WAeUP.Kofa_bursary_%s.csv, job_id=%s'
4130            % (job_id, job_id) in logcontent
4131            )
4132        self.assertTrue(
4133            'zope.mgr - students.browser.ExportJobContainerOverview '
4134            '- discarded: job_id=%s' % job_id in logcontent
4135            )
4136        # Officer can also enter student id and gets the same export file
4137        self.browser.open(facs_path)
4138        self.browser.getLink("Export student data").click()
4139        self.browser.getControl("Enter student ids or matric numbers").click()
4140        self.browser.getControl(name="exporter").value = ['bursary']
4141        self.browser.getControl(name="students").value = 'K1000000'
4142        self.browser.getControl("Create CSV file").click()
4143        # When the job is finished and we reload the page...
4144        job_id = self.wait_for_export_job_completed()
4145        # ... the csv file can be downloaded ...
4146        self.browser.open('http://localhost/app/faculties/exports')
4147        self.browser.getLink("Download").click()
4148        self.assertEqual(self.browser.headers['content-type'],
4149            'text/csv; charset=UTF-8')
4150        self.assertTrue(
4151            'filename="WAeUP.Kofa_bursary_%s.csv' % job_id in
4152            self.browser.headers['content-disposition'])
4153        self.assertTrue(
4154            '666,12.12,2012-12-13 00:00:00#,schoolfee,[],1,my-id,p-item,100,%s,'
4155            'paid,2012-12-13 00:00:00#,12.12,r-code,,K1000000,234,123,Anna,,'
4156            'Tester,created,2004,2004,,fac1,dep1,CERT1'
4157            %(curr_year-6) in self.browser.contents)
4158
4159    def test_faculty_export(self):
4160        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
4161        fac1_path = 'http://localhost/app/faculties/fac1'
4162        self.browser.open(fac1_path)
4163        self.browser.getLink("Export student data").click()
4164        self.browser.getControl("Set export parameters").click()
4165        self.browser.getControl(name="exporter").value = ['students']
4166        self.browser.getControl(name="session").value = ['2004']
4167        self.browser.getControl(name="level").value = ['100']
4168        self.browser.getControl(name="mode").value = ['ug_ft']
4169        # The testbrowser does not hide the payment period fields, but
4170        # values are ignored when using the students exporter.
4171        self.browser.getControl(name="payments_start").value = '13/12/2012'
4172        self.browser.getControl(name="payments_end").value = '14/12/2012'
4173        self.browser.getControl("Create CSV file").click()
4174        # When the job is finished and we reload the page...
4175        job_id = self.wait_for_export_job_completed()
4176        self.browser.open(fac1_path + '/exports')
4177        # ... the csv file can be downloaded ...
4178        self.browser.getLink("Download").click()
4179        self.assertEqual(self.browser.headers['content-type'],
4180            'text/csv; charset=UTF-8')
4181        self.assertTrue(
4182            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
4183            self.browser.headers['content-disposition'])
4184        self.assertTrue(
4185            'adm_code,clr_code,date_of_birth,email,employer,'
4186            'firstname,flash_notice,lastname,matric_number,middlename,nationality,'
4187            'officer_comment,parents_email,perm_address,'
4188            'personal_updated,phone,reg_number,'
4189            'sex,student_id,suspended,suspended_comment,'
4190            'password,state,history,certcode,is_postgrad,'
4191            'current_level,current_session,entry_session\r\n'
4192            ',,1981-02-04#,aa@aa.ng,,Anna,,Tester,234,,,,,,,'
4193            '1234#,123,m,K1000000,0,,{SSHA}' in self.browser.contents)
4194        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
4195        job_id = self.app['datacenter'].running_exports[0][0]
4196        # ... and discarded
4197        self.browser.open(fac1_path + '/exports')
4198        self.browser.getControl("Discard").click()
4199        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
4200        # Creation, downloading and discarding is logged
4201        logfile = os.path.join(
4202            self.app['datacenter'].storage, 'logs', 'datacenter.log')
4203        logcontent = open(logfile).read()
4204        self.assertTrue(
4205            'zope.mgr - students.browser.FacultyExportJobContainerJobConfig '
4206            '- exported: students (2004, 100, ug_ft, fac1, None, None, '
4207            '13/12/2012, 14/12/2012, all, all, all, all, all), job_id=%s'
4208            % job_id in logcontent
4209            )
4210        self.assertTrue(
4211            'zope.mgr - students.browser.ExportJobContainerDownload '
4212            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
4213            % (job_id, job_id) in logcontent
4214            )
4215        self.assertTrue(
4216            'zope.mgr - students.browser.ExportJobContainerOverview '
4217            '- discarded: job_id=%s' % job_id in logcontent
4218            )
4219        # Officer can set export parameters but cannot enter student id
4220        # at faculty level
4221        self.browser.open(fac1_path + '/exports')
4222        self.assertTrue("Set export parameters"
4223                         in self.browser.contents)
4224        self.assertFalse("Enter student ids or matric numbers"
4225                         in self.browser.contents)
4226
4227    def test_department_export(self):
4228        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
4229        dep1_path = 'http://localhost/app/faculties/fac1/dep1'
4230        self.browser.open(dep1_path)
4231        self.browser.getLink("Export student data").click()
4232        self.browser.getControl("Set export parameters").click()
4233        self.browser.getControl(name="exporter").value = ['students']
4234        self.browser.getControl(name="session").value = ['2004']
4235        self.browser.getControl(name="level").value = ['100']
4236        self.browser.getControl(name="mode").value = ['ug_ft']
4237        # The testbrowser does not hide the payment period fields, but
4238        # values are ignored when using the students exporter.
4239        self.browser.getControl(name="payments_start").value = '13/12/2012'
4240        self.browser.getControl(name="payments_end").value = '14/12/2012'
4241        self.browser.getControl("Create CSV file").click()
4242
4243        # When the job is finished and we reload the page...
4244        job_id = self.wait_for_export_job_completed()
4245        self.browser.open(dep1_path + '/exports')
4246        # ... the csv file can be downloaded ...
4247        self.browser.getLink("Download").click()
4248        self.assertEqual(self.browser.headers['content-type'],
4249            'text/csv; charset=UTF-8')
4250        self.assertTrue(
4251            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
4252            self.browser.headers['content-disposition'])
4253        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
4254        job_id = self.app['datacenter'].running_exports[0][0]
4255        # ... and discarded
4256        self.browser.open(dep1_path + '/exports')
4257        self.browser.getControl("Discard").click()
4258        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
4259        # Creation, downloading and discarding is logged
4260        logfile = os.path.join(
4261            self.app['datacenter'].storage, 'logs', 'datacenter.log')
4262        logcontent = open(logfile).read()
4263        self.assertTrue(
4264            'zope.mgr - students.browser.DepartmentExportJobContainerJobConfig '
4265            '- exported: students (2004, 100, ug_ft, None, dep1, None, '
4266            '13/12/2012, 14/12/2012, all, all, all, all, all), job_id=%s'
4267            % job_id in logcontent
4268            )
4269        self.assertTrue(
4270            'zope.mgr - students.browser.ExportJobContainerDownload '
4271            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
4272            % (job_id, job_id) in logcontent
4273            )
4274        self.assertTrue(
4275            'zope.mgr - students.browser.ExportJobContainerOverview '
4276            '- discarded: job_id=%s' % job_id in logcontent
4277            )
4278
4279    def test_certificate_export(self):
4280        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
4281        cert1_path = 'http://localhost/app/faculties/fac1/dep1/certificates/CERT1'
4282        self.browser.open(cert1_path)
4283        self.browser.getLink("Export student data").click()
4284        self.browser.getControl("Set export parameters").click()
4285        self.browser.getControl(name="exporter").value = ['students']
4286        self.browser.getControl(name="session").value = ['2004']
4287        self.browser.getControl(name="level").value = ['100']
4288        self.browser.getControl("Create CSV file").click()
4289
4290        # When the job is finished and we reload the page...
4291        job_id = self.wait_for_export_job_completed()
4292        self.browser.open(cert1_path + '/exports')
4293        # ... the csv file can be downloaded ...
4294        self.browser.getLink("Download").click()
4295        self.assertEqual(self.browser.headers['content-type'],
4296            'text/csv; charset=UTF-8')
4297        self.assertTrue(
4298            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
4299            self.browser.headers['content-disposition'])
4300        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
4301        job_id = self.app['datacenter'].running_exports[0][0]
4302        # ... and discarded
4303        self.browser.open(cert1_path + '/exports')
4304        self.browser.getControl("Discard").click()
4305        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
4306        # Creation, downloading and discarding is logged
4307        logfile = os.path.join(
4308            self.app['datacenter'].storage, 'logs', 'datacenter.log')
4309        logcontent = open(logfile).read()
4310        self.assertTrue(
4311            'zope.mgr - students.browser.CertificateExportJobContainerJobConfig '
4312            '- exported: students '
4313            '(2004, 100, None, None, None, CERT1, , , None, None, '
4314            'None, None, None), '
4315            'job_id=%s'
4316            % job_id in logcontent
4317            )
4318        self.assertTrue(
4319            'zope.mgr - students.browser.ExportJobContainerDownload '
4320            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
4321            % (job_id, job_id) in logcontent
4322            )
4323        self.assertTrue(
4324            'zope.mgr - students.browser.ExportJobContainerOverview '
4325            '- discarded: job_id=%s' % job_id in logcontent
4326            )
4327
4328    def deprecated_test_course_export_students(self):
4329        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
4330        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
4331        self.browser.open(course1_path)
4332        self.browser.getLink("Export student data").click()
4333        self.browser.getControl("Set export parameters").click()
4334        self.browser.getControl(name="exporter").value = ['students']
4335        self.browser.getControl(name="session").value = ['2004']
4336        self.browser.getControl(name="level").value = ['100']
4337        self.browser.getControl("Create CSV file").click()
4338
4339        # When the job is finished and we reload the page...
4340        job_id = self.wait_for_export_job_completed()
4341        self.browser.open(course1_path + '/exports')
4342        # ... the csv file can be downloaded ...
4343        self.browser.getLink("Download").click()
4344        self.assertEqual(self.browser.headers['content-type'],
4345            'text/csv; charset=UTF-8')
4346        self.assertTrue(
4347            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
4348            self.browser.headers['content-disposition'])
4349        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
4350        job_id = self.app['datacenter'].running_exports[0][0]
4351        # ... and discarded
4352        self.browser.open(course1_path + '/exports')
4353        self.browser.getControl("Discard").click()
4354        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
4355        # Creation, downloading and discarding is logged
4356        logfile = os.path.join(
4357            self.app['datacenter'].storage, 'logs', 'datacenter.log')
4358        logcontent = open(logfile).read()
4359        self.assertTrue(
4360            'zope.mgr - students.browser.CourseExportJobContainerJobConfig '
4361            '- exported: students (2004, 100, COURSE1), job_id=%s'
4362            % job_id in logcontent
4363            )
4364        self.assertTrue(
4365            'zope.mgr - students.browser.ExportJobContainerDownload '
4366            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
4367            % (job_id, job_id) in logcontent
4368            )
4369        self.assertTrue(
4370            'zope.mgr - students.browser.ExportJobContainerOverview '
4371            '- discarded: job_id=%s' % job_id in logcontent
4372            )
4373
4374    def test_course_export_lecturer(self):
4375        # We add study level 100 to the student's studycourse
4376        studylevel = StudentStudyLevel()
4377        studylevel.level = 100
4378        studylevel.level_session = 2004
4379        IWorkflowState(self.student).setState('courses validated')
4380        self.student['studycourse'].addStudentStudyLevel(
4381            self.certificate,studylevel)
4382        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
4383        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
4384        self.browser.open(course1_path)
4385        self.browser.getLink("Export student data").click()
4386        self.browser.getControl("Set export parameters").click()
4387        self.assertTrue(
4388            'Academic session not set. Please contact the administrator.'
4389            in self.browser.contents)
4390        self.app['configuration'].current_academic_session = 2004
4391        self.browser.getControl("Set export parameters").click()
4392        self.browser.getControl(name="exporter").value = ['lecturer']
4393        self.browser.getControl(name="session").value = ['2004']
4394        self.browser.getControl(name="level").value = ['100']
4395        self.browser.getControl("Create CSV file").click()
4396        # When the job is finished and we reload the page...
4397        job_id = self.wait_for_export_job_completed()
4398        self.browser.open(course1_path + '/exports')
4399        # ... the csv file can be downloaded ...
4400        self.browser.getLink("Download").click()
4401        self.assertEqual(self.browser.headers['content-type'],
4402            'text/csv; charset=UTF-8')
4403        self.assertTrue(
4404            'filename="WAeUP.Kofa_lecturer_%s.csv' % job_id in
4405            self.browser.headers['content-disposition'])
4406        # ... and contains the course ticket COURSE1
4407        self.assertEqual(self.browser.contents,
4408            'matric_number,student_id,display_fullname,level,code,'
4409            'level_session,score\r\n234,K1000000,Anna Tester,'
4410            '100,COURSE1,2004,\r\n')
4411        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
4412        job_id = self.app['datacenter'].running_exports[0][0]
4413        # Thew job can be discarded
4414        self.browser.open(course1_path + '/exports')
4415        self.browser.getControl("Discard").click()
4416        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
4417        # Creation, downloading and discarding is logged
4418        logfile = os.path.join(
4419            self.app['datacenter'].storage, 'logs', 'datacenter.log')
4420        logcontent = open(logfile).read()
4421        self.assertTrue(
4422            'zope.mgr - students.browser.CourseExportJobContainerJobConfig '
4423            '- exported: lecturer (2004, 100, COURSE1), job_id=%s'
4424            % job_id in logcontent
4425            )
4426        self.assertTrue(
4427            'zope.mgr - students.browser.ExportJobContainerDownload '
4428            '- downloaded: WAeUP.Kofa_lecturer_%s.csv, job_id=%s'
4429            % (job_id, job_id) in logcontent
4430            )
4431        self.assertTrue(
4432            'zope.mgr - students.browser.ExportJobContainerOverview '
4433            '- discarded: job_id=%s' % job_id in logcontent
4434            )
4435
4436    def test_export_departmet_officers(self):
4437        # Create department officer
4438        self.app['users'].addUser('mrdepartment', SECRET)
4439        self.app['users']['mrdepartment'].email = 'mrdepartment@foo.ng'
4440        self.app['users']['mrdepartment'].title = 'Carlo Pitter'
4441        # Assign local role
4442        department = self.app['faculties']['fac1']['dep1']
4443        prmlocal = IPrincipalRoleManager(department)
4444        prmlocal.assignRoleToPrincipal('waeup.local.DepartmentOfficer', 'mrdepartment')
4445        # Login as department officer
4446        self.browser.open(self.login_path)
4447        self.browser.getControl(name="form.login").value = 'mrdepartment'
4448        self.browser.getControl(name="form.password").value = SECRET
4449        self.browser.getControl("Login").click()
4450        self.assertMatches('...You logged in...', self.browser.contents)
4451        self.browser.open("http://localhost/app/faculties/fac1/dep1")
4452        self.browser.getLink("Export student data").click()
4453        self.browser.getControl("Set export parameters").click()
4454        # Only the sfpaymentsoverview exporter is available for department officers
4455        self.assertFalse('<option value="students">' in self.browser.contents)
4456        self.assertTrue(
4457            '<option value="sfpaymentsoverview">' in self.browser.contents)
4458        self.browser.getControl(name="exporter").value = ['sfpaymentsoverview']
4459        self.browser.getControl(name="session").value = ['2004']
4460        self.browser.getControl(name="level").value = ['100']
4461        self.browser.getControl("Create CSV file").click()
4462        self.assertTrue('Export started' in self.browser.contents)
4463        # Thew job can be discarded
4464        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
4465        self.wait_for_export_job_completed()
4466        self.browser.open("http://localhost/app/faculties/fac1/dep1/exports")
4467        self.browser.getControl("Discard").click()
4468        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
4469
4470    def test_export_bursary_officers(self):
4471        self.add_payment(self.student)
4472        # Create bursary officer
4473        self.app['users'].addUser('mrbursary', SECRET)
4474        self.app['users']['mrbursary'].email = 'mrbursary@foo.ng'
4475        self.app['users']['mrbursary'].title = 'Carlo Pitter'
4476        prmglobal = IPrincipalRoleManager(self.app)
4477        prmglobal.assignRoleToPrincipal('waeup.BursaryOfficer', 'mrbursary')
4478        # Login as bursary officer
4479        self.browser.open(self.login_path)
4480        self.browser.getControl(name="form.login").value = 'mrbursary'
4481        self.browser.getControl(name="form.password").value = SECRET
4482        self.browser.getControl("Login").click()
4483        self.assertMatches('...You logged in...', self.browser.contents)
4484        self.browser.getLink("Academics").click()
4485        self.browser.getLink("Export student data").click()
4486        self.browser.getControl("Set export parameters").click()
4487        # Only the bursary exporter is available for bursary officers
4488        # not only at facultiescontainer level ...
4489        self.assertFalse('<option value="students">' in self.browser.contents)
4490        self.assertTrue('<option value="bursary">' in self.browser.contents)
4491        self.browser.getControl(name="exporter").value = ['bursary']
4492        self.browser.getControl(name="session").value = ['2004']
4493        self.browser.getControl(name="level").value = ['100']
4494        self.browser.getControl("Create CSV file").click()
4495        self.assertTrue('Export started' in self.browser.contents)
4496        # ... but also at other levels
4497        self.browser.open('http://localhost/app/faculties/fac1/dep1')
4498        self.browser.getLink("Export student data").click()
4499        self.browser.getControl("Set export parameters").click()
4500        self.assertFalse('<option value="students">' in self.browser.contents)
4501        self.assertTrue('<option value="bursary">' in self.browser.contents)
4502        # Thew job can be downloaded
4503        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
4504        job_id = self.wait_for_export_job_completed()
4505        self.browser.open('http://localhost/app/faculties/exports')
4506        self.browser.getLink("Download").click()
4507        self.assertEqual(self.browser.headers['content-type'],
4508            'text/csv; charset=UTF-8')
4509        self.assertTrue(
4510            'filename="WAeUP.Kofa_bursary_%s.csv' % job_id in
4511            self.browser.headers['content-disposition'])
4512        self.assertTrue(
4513            '666,12.12,2012-12-13 00:00:00#,schoolfee,[],1,my-id,p-item,100,%s,'
4514            'paid,2012-12-13 00:00:00#,12.12,r-code,,K1000000,234,123,Anna,,'
4515            'Tester,created,2004,2004,,fac1,dep1,CERT1'
4516            %(curr_year-6) in self.browser.contents)
4517        # ... and discarded
4518        self.browser.open('http://localhost/app/faculties/exports')
4519        self.browser.getControl("Discard").click()
4520        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
4521        # At Academics level bursary officers can also enter student ids
4522        self.browser.getLink("Academics").click()
4523        self.browser.getLink("Export student data").click()
4524        self.browser.getControl("Enter student ids or matric numbers").click()
4525        self.assertFalse('<option value="students">' in self.browser.contents)
4526        self.browser.getControl(name="exporter").value = ['bursary']
4527        self.browser.getControl(name="students").value = 'K1000000'
4528        self.browser.getControl("Create CSV file").click()
4529        # When the job is finished and we reload the page...
4530        job_id = self.wait_for_export_job_completed()
4531        # ... the csv file can be downloaded ...
4532        self.browser.open('http://localhost/app/faculties/exports')
4533        self.browser.getLink("Download").click()
4534        self.assertEqual(self.browser.headers['content-type'],
4535            'text/csv; charset=UTF-8')
4536        self.assertTrue(
4537            'filename="WAeUP.Kofa_bursary_%s.csv' % job_id in
4538            self.browser.headers['content-disposition'])
4539        self.assertTrue(
4540            '666,12.12,2012-12-13 00:00:00#,schoolfee,[],1,my-id,p-item,100,%s,'
4541            'paid,2012-12-13 00:00:00#,12.12,r-code,,K1000000,234,123,Anna,,'
4542            'Tester,created,2004,2004,,fac1,dep1,CERT1'
4543            %(curr_year-6) in self.browser.contents)
4544
4545    def test_export_accommodation_officers(self):
4546        # Create bursary officer
4547        self.app['users'].addUser('mracco', SECRET)
4548        self.app['users']['mracco'].email = 'mracco@foo.ng'
4549        self.app['users']['mracco'].title = 'Carlo Pitter'
4550        prmglobal = IPrincipalRoleManager(self.app)
4551        prmglobal.assignRoleToPrincipal('waeup.AccommodationOfficer', 'mracco')
4552        # Login as bursary officer
4553        self.browser.open(self.login_path)
4554        self.browser.getControl(name="form.login").value = 'mracco'
4555        self.browser.getControl(name="form.password").value = SECRET
4556        self.browser.getControl("Login").click()
4557        self.assertMatches('...You logged in...', self.browser.contents)
4558        self.browser.getLink("Academics").click()
4559        self.browser.getLink("Export student data").click()
4560        self.browser.getControl("Set export parameters").click()
4561        # accommodationpayments and beds exporters are available
4562        # not only at facultiescontainer level ...
4563        self.assertFalse('<option value="students">' in self.browser.contents)
4564        self.assertTrue('<option value="accommodationpayments">'
4565            in self.browser.contents)
4566        self.assertTrue('<option value="bedtickets">' in self.browser.contents)
4567        self.browser.getControl(
4568            name="exporter").value = ['accommodationpayments']
4569        self.browser.getControl(name="session").value = ['2004']
4570        self.browser.getControl(name="level").value = ['100']
4571        self.browser.getControl("Create CSV file").click()
4572        self.assertTrue('Export started' in self.browser.contents)
4573        # ... but also at other levels
4574        self.browser.open('http://localhost/app/faculties/fac1/dep1')
4575        self.browser.getLink("Export student data").click()
4576        self.browser.getControl("Set export parameters").click()
4577        self.assertFalse('<option value="students">' in self.browser.contents)
4578        self.assertTrue('<option value="accommodationpayments">'
4579            in self.browser.contents)
4580        self.assertTrue('<option value="bedtickets">' in self.browser.contents)
4581        # Thew job can be discarded
4582        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
4583        self.wait_for_export_job_completed()
4584        self.browser.open('http://localhost/app/faculties/exports')
4585        self.browser.getControl("Discard").click()
4586        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
4587
4588
4589UPLOAD_CSV_TEMPLATE = (
4590    'matric_number,student_id,display_fullname,level,code,level_session,'
4591    'score\r\n'
4592    '234,K1000000,Anna Tester,100,COURSE1,2004,%s\r\n')
4593
4594class LecturerUITests(StudentsFullSetup):
4595    # Tests for UI actions when acting as lecturer.
4596
4597    def login_as_lecturer(self):
4598        self.app['users'].addUser('mrslecturer', SECRET)
4599        self.app['users']['mrslecturer'].email = 'mrslecturer@foo.ng'
4600        self.app['users']['mrslecturer'].title = u'Mercedes Benz'
4601        # Add course ticket
4602        self.studylevel = createObject(u'waeup.StudentStudyLevel')
4603        self.studylevel.level = 100
4604        self.studylevel.level_session = 2004
4605        self.student['studycourse'].addStudentStudyLevel(
4606            self.certificate, self.studylevel)
4607        # Assign local Lecturer role for a course.
4608        course = self.app['faculties']['fac1']['dep1'].courses['COURSE1']
4609        prmlocal = IPrincipalRoleManager(course)
4610        prmlocal.assignRoleToPrincipal('waeup.local.Lecturer', 'mrslecturer')
4611        notify(LocalRoleSetEvent(
4612            course, 'waeup.local.Lecturer', 'mrslecturer', granted=True))
4613        # Login as lecturer.
4614        self.browser.open(self.login_path)
4615        self.browser.getControl(name="form.login").value = 'mrslecturer'
4616        self.browser.getControl(
4617            name="form.password").value = SECRET
4618        self.browser.getControl("Login").click()
4619        # Store reused urls/paths
4620        self.course_url = (
4621            'http://localhost/app/faculties/fac1/dep1/courses/COURSE1')
4622        self.edit_scores_url = '%s/edit_scores' % self.course_url
4623        # Set standard parameters
4624        self.app['configuration'].current_academic_session = 2004
4625        self.app['faculties']['fac1']['dep1'].score_editing_disabled = False
4626        IWorkflowState(self.student).setState(VALIDATED)
4627
4628    @property
4629    def stud_log_path(self):
4630        return os.path.join(
4631            self.app['datacenter'].storage, 'logs', 'students.log')
4632
4633    def test_lecturer_lands_on_landing_page(self):
4634        # lecturers can login and will be led to landing page.
4635        self.login_as_lecturer()
4636        self.assertMatches('...You logged in...', self.browser.contents)
4637        self.assertEqual(self.browser.url, URL_LECTURER_LANDING)
4638        self.assertTrue(
4639            "<span>Unnamed Course (COURSE1)</span>"
4640            in self.browser.contents)
4641
4642    def test_lecturer_department_role(self):
4643        # lecturers can login and will be led to landing page also if
4644        # role is assigned at department level.
4645        self.login_as_lecturer()
4646        # we remove the role granted above
4647        course = self.app['faculties']['fac1']['dep1'].courses['COURSE1']
4648        prmlocal = IPrincipalRoleManager(course)
4649        prmlocal.removeRoleFromPrincipal('waeup.local.Lecturer', 'mrslecturer')
4650        notify(LocalRoleSetEvent(
4651            course, 'waeup.local.Lecturer', 'mrslecturer', granted=False))
4652        self.browser.open(URL_LECTURER_LANDING)
4653        # no course appears
4654        self.assertFalse(
4655            "<span>Unnamed Course (COURSE1)</span>"
4656            in self.browser.contents)
4657        # we assign lecturer at department level
4658        dep = self.app['faculties']['fac1']['dep1']
4659        prmlocal = IPrincipalRoleManager(dep)
4660        prmlocal.assignRoleToPrincipal('waeup.local.Lecturer', 'mrslecturer')
4661        notify(LocalRoleSetEvent(
4662            dep, 'waeup.local.Lecturer', 'mrslecturer', granted=True))
4663        self.browser.open(URL_LECTURER_LANDING)
4664        # course appears again
4665        self.assertTrue(
4666            "<span>Unnamed Course (COURSE1)</span>"
4667            in self.browser.contents)
4668
4669    def test_my_roles_link_works(self):
4670        # lecturers can see their roles
4671        self.login_as_lecturer()
4672        self.browser.getLink("My Roles").click()
4673        self.assertTrue(
4674            "<div>Academics Officer (view only)</div>"
4675            in self.browser.contents)
4676        self.assertTrue(
4677            '<a href="%s">' % self.course_url in self.browser.contents)
4678
4679    def test_my_roles_page_contains_backlink(self):
4680        # we can get back from 'My Roles' view to landing page
4681        self.login_as_lecturer()
4682        self.browser.getLink("My Roles").click()
4683        self.browser.getLink("My Courses").click()
4684        self.assertEqual(self.browser.url, URL_LECTURER_LANDING)
4685
4686    def test_lecturers_can_reach_their_courses(self):
4687        # lecturers get links to their courses on the landing page
4688        self.login_as_lecturer()
4689        self.browser.getLink("COURSE1").click()
4690        self.assertEqual(self.browser.url, self.course_url)
4691
4692    def test_lecturers_student_access_is_restricted(self):
4693        # lecturers are not able to change other student data
4694        self.login_as_lecturer()
4695        # Lecturers can neither filter students ...
4696        self.assertRaises(
4697            Unauthorized, self.browser.open, '%s/students' % self.course_url)
4698        # ... nor access the student ...
4699        self.assertRaises(
4700            Unauthorized, self.browser.open, self.student_path)
4701        # ... nor the respective course ticket since editing course
4702        # tickets by lecturers is not feasible.
4703        self.assertTrue('COURSE1' in self.student['studycourse']['100'].keys())
4704        course_ticket_path = self.student_path + '/studycourse/100/COURSE1'
4705        self.assertRaises(
4706            Unauthorized, self.browser.open, course_ticket_path)
4707
4708    def test_score_editing_requires_department_permit(self):
4709        # we get a warning if we try to update score while we are not allowed
4710        self.login_as_lecturer()
4711        self.app['faculties']['fac1']['dep1'].score_editing_disabled = True
4712        self.browser.open(self.course_url)
4713        self.browser.getLink("Update session 2004/2005 scores").click()
4714        self.assertTrue('Score editing disabled' in self.browser.contents)
4715        self.app['faculties']['fac1']['dep1'].score_editing_disabled = False
4716        self.browser.open(self.course_url)
4717        self.browser.getLink("Update session 2004/2005 scores").click()
4718        self.assertFalse('Score editing disabled' in self.browser.contents)
4719
4720    def test_score_editing_requires_validated_students(self):
4721        # we can edit only scores of students whose courses have been
4722        # validated.
4723        self.login_as_lecturer()
4724        # set invalid student state
4725        IWorkflowState(self.student).setState(CREATED)
4726        self.browser.open(self.edit_scores_url)
4727        self.assertRaises(
4728            LookupError, self.browser.getControl, name="scores")
4729        # set valid student state
4730        IWorkflowState(self.student).setState(VALIDATED)
4731        self.browser.open(self.edit_scores_url)
4732        self.assertTrue(
4733            self.browser.getControl(name="scores:list") is not None)
4734
4735    def test_score_editing_offers_only_current_scores(self):
4736        # only scores from current academic session can be edited
4737        self.login_as_lecturer()
4738        IWorkflowState(self.student).setState('courses validated')
4739        # with no academic session set
4740        self.app['configuration'].current_academic_session = None
4741        self.browser.open(self.edit_scores_url)
4742        self.assertRaises(
4743            LookupError, self.browser.getControl, name="scores")
4744        # with wrong academic session set
4745        self.app['configuration'].current_academic_session = 1999
4746        self.browser.open(self.edit_scores_url)
4747        self.assertRaises(
4748            LookupError, self.browser.getControl, name="scores")
4749        # with right academic session set
4750        self.app['configuration'].current_academic_session = 2004
4751        self.browser.reload()
4752        self.assertTrue(
4753            self.browser.getControl(name="scores:list") is not None)
4754        # if level_session of studycourse changes, catalog has been updated
4755        # and student disappears
4756        self.studylevel.level_session = 2005
4757        self.browser.reload()
4758        self.assertRaises(
4759            LookupError, self.browser.getControl, name="scores")
4760
4761    def test_score_editing_can_change_scores(self):
4762        # we can really change scores via edit_scores view
4763        self.login_as_lecturer()
4764        self.assertEqual(
4765            self.student['studycourse']['100']['COURSE1'].score, None)
4766        self.browser.open(self.edit_scores_url)
4767        self.browser.getControl(name="scores:list", index=0).value = '55'
4768        self.browser.getControl("Update scores").click()
4769        # the new value is stored in data
4770        self.assertEqual(
4771            self.student['studycourse']['100']['COURSE1'].score, 55)
4772        # the new value is displayed on page/prefilled in form
4773        self.assertEqual(
4774            self.browser.getControl(name="scores:list", index=0).value, '55')
4775        # The change has been logged
4776        with open(self.stud_log_path, 'r') as fd:
4777            self.assertTrue(
4778                'mrslecturer - students.browser.EditScoresPage - '
4779                'K1000000 100/COURSE1 score updated (55)' in fd.read())
4780
4781    def test_scores_editing_scores_must_be_integers(self):
4782        # Non-integer scores won't be accepted.
4783        self.login_as_lecturer()
4784        self.browser.open(self.edit_scores_url)
4785        self.browser.getControl(name="scores:list", index=0).value = 'abc'
4786        self.browser.getControl("Update scores").click()
4787        self.assertTrue(
4788            'Error: Score(s) of following students have not been updated '
4789            '(only integers are allowed): Anna Tester.'
4790            in self.browser.contents)
4791
4792    def test_scores_editing_allows_score_removal(self):
4793        # we can remove scores, once they were set
4794        self.login_as_lecturer()
4795        # without a prior value, we cannot remove
4796        self.student['studycourse']['100']['COURSE1'].score = None
4797        self.browser.open(self.edit_scores_url)
4798        self.browser.getControl(name="scores:list", index=0).value = ''
4799        self.browser.getControl("Update scores").click()
4800        logcontent = open(self.stud_log_path, 'r').read()
4801        self.assertFalse('COURSE1 score updated (None)' in logcontent)
4802        # now retry with some value set
4803        self.student['studycourse']['100']['COURSE1'].score = 55
4804        self.browser.getControl(name="scores:list", index=0).value = ''
4805        self.browser.getControl("Update scores").click()
4806        logcontent = open(self.stud_log_path, 'r').read()
4807        self.assertTrue('COURSE1 score updated (None)' in logcontent)
4808
4809    def test_lecturer_can_validate_courses(self):
4810        # the form is locked after validation
4811        self.login_as_lecturer()
4812        self.student['studycourse']['100']['COURSE1'].score = None
4813        self.browser.open(self.edit_scores_url)
4814        self.browser.getControl(name="scores:list", index=0).value = ''
4815        self.browser.getControl("Update scores").click()
4816        self.browser.getControl("Validate").click()
4817        self.assertTrue(
4818            'No score has been entered.' in self.browser.contents)
4819        self.browser.open(self.edit_scores_url)
4820        self.browser.getControl(name="scores:list", index=0).value = '66'
4821        self.browser.getControl("Update scores").click()
4822        self.browser.getControl("Validate").click()
4823        self.assertTrue(
4824            'You successfully validated the course results'
4825            in self.browser.contents)
4826        self.assertEqual(self.course.results_validation_session, 2004)
4827        self.assertEqual(self.course.results_validated_by, 'Mercedes Benz')
4828        self.assertEqual(self.browser.url, self.course_url)
4829        # Lecturer can't open edit_scores again
4830        self.browser.getLink("Update session 2004/2005 scores").click()
4831        self.assertEqual(self.browser.url, self.course_url)
4832        self.assertTrue(
4833            'Course results have already been validated'
4834            ' and can no longer be changed.'
4835            in self.browser.contents)
4836        # Also DownloadScoresView is blocked
4837        self.browser.open(self.browser.url + '/download_scores')
4838        self.assertEqual(self.browser.url, self.course_url)
4839        self.assertTrue(
4840            'Course results have already been validated'
4841            ' and can no longer be changed.'
4842            in self.browser.contents)
4843        # Students Manager can open page ...
4844        prmlocal = IPrincipalRoleManager(self.course)
4845        prmlocal.assignRoleToPrincipal(
4846            'waeup.local.LocalStudentsManager', 'mrslecturer')
4847        self.browser.getLink("Update session 2004/2005 scores").click()
4848        self.assertEqual(self.browser.url, self.edit_scores_url)
4849        self.browser.getLink("Download csv file").click()
4850        self.assertEqual(self.browser.headers['Status'], '200 Ok')
4851        self.assertEqual(self.browser.headers['Content-Type'],
4852                         'text/csv; charset=UTF-8')
4853        # ... but can't validate courses a second time
4854        self.browser.open(self.edit_scores_url)
4855        self.browser.getControl("Validate").click()
4856        self.assertTrue(
4857            'Course results have already been validated.'
4858            in self.browser.contents)
4859
4860    def test_lecturers_can_download_course_tickets(self):
4861        # A course ticket slip can be downloaded
4862        self.course.title = (u'Lorem ipsum     dolor sit amet, consectetur     adipisici, '
4863                             u'sed         eiusmod tempor    incidunt ut  labore et dolore')
4864        self.login_as_lecturer()
4865        pdf_url = '%s/coursetickets.pdf' % self.course_url
4866        self.browser.open(pdf_url)
4867        self.assertEqual(self.browser.headers['Status'], '200 Ok')
4868        self.assertEqual(
4869            self.browser.headers['Content-Type'], 'application/pdf')
4870        path = os.path.join(samples_dir(), 'coursetickets.pdf')
4871        open(path, 'wb').write(self.browser.contents)
4872        print "Sample PDF coursetickets.pdf written to %s" % path
4873
4874    def test_lecturers_can_download_attendance_sheet(self):
4875        # A course ticket slip can be downloaded
4876        self.course.title = (u'Lorem ipsum     dolor sit amet, consectetur     adipisici, '
4877                             u'sed         eiusmod tempor    incidunt ut  labore et dolore')
4878        self.student.firstname = u'Emmanuella Woyengifigha Mercy Onosemudiana'
4879        self.student.lastname = u'OYAKEMIEGBEGHA'
4880        self.student.matric_number = u'hdk7gd62i872z27zt27ge'
4881        self.login_as_lecturer()
4882        pdf_url = '%s/attendance.pdf' % self.course_url
4883        self.browser.open(pdf_url)
4884        self.assertEqual(self.browser.headers['Status'], '200 Ok')
4885        self.assertEqual(
4886            self.browser.headers['Content-Type'], 'application/pdf')
4887        path = os.path.join(samples_dir(), 'attendance.pdf')
4888        open(path, 'wb').write(self.browser.contents)
4889        print "Sample PDF attendance.pdf written to %s" % path
4890
4891
4892    def test_lecturers_can_download_scores_as_csv(self):
4893        # Lecturers can download course scores as CSV.
4894        self.login_as_lecturer()
4895        self.browser.open(self.edit_scores_url)
4896        self.browser.getLink("Download csv file").click()
4897        self.assertEqual(self.browser.headers['Status'], '200 Ok')
4898        self.assertEqual(self.browser.headers['Content-Type'],
4899                         'text/csv; charset=UTF-8')
4900        self.assertEqual(self.browser.contents, 'matric_number,student_id,'
4901            'display_fullname,level,code,level_session,score\r\n234,'
4902            'K1000000,Anna Tester,100,COURSE1,2004,\r\n')
4903
4904    def test_scores_csv_upload_available(self):
4905        # lecturers can upload a CSV file to set values.
4906        self.login_as_lecturer()
4907        # set value to change from
4908        self.student['studycourse']['100']['COURSE1'].score = 55
4909        self.browser.open(self.edit_scores_url)
4910        upload_ctrl = self.browser.getControl(name='uploadfile:file')
4911        upload_file = StringIO(UPLOAD_CSV_TEMPLATE % '65')
4912        upload_ctrl.add_file(upload_file, 'text/csv', 'myscores.csv')
4913        self.browser.getControl("Update editable scores from").click()
4914        # value changed
4915        self.assertEqual(
4916            self.student['studycourse']['100']['COURSE1'].score, 65)
4917
4918    def test_scores_csv_upload_ignored(self):
4919        # for many type of file contents we simply ignore uploaded data
4920        self.login_as_lecturer()
4921        self.student['studycourse']['100']['COURSE1'].score = 55
4922        self.browser.open(self.edit_scores_url)
4923        for content, mimetype, name in (
4924                # empty file
4925                ('', 'text/foo', 'my.foo'),
4926                # plain ASCII text, w/o comma
4927                ('abcdef' * 200, 'text/plain', 'my.txt'),
4928                # plain UTF-8 text, with umlauts
4929                ('umlauts: äöü', 'text/plain', 'my.txt'),
4930                # csv file with only a header row
4931                ('student_id,score', 'text/csv', 'my.csv'),
4932                # csv with student_id column missing
4933                ('foo,score\r\nbar,66\r\n', 'text/csv', 'my.csv'),
4934                # csv with score column missing
4935                ('student_id,foo\r\nK1000000,bar\r\n', 'text/csv', 'my.csv'),
4936                # csv with non number as score value
4937                (UPLOAD_CSV_TEMPLATE % 'not-a-number', 'text/csv', 'my.csv'),
4938                ):
4939            upload_ctrl = self.browser.getControl(name='uploadfile:file')
4940            upload_ctrl.add_file(StringIO(content), mimetype, name)
4941            self.browser.getControl("Update scores").click()
4942            self.assertEqual(
4943                self.student['studycourse']['100']['COURSE1'].score, 55)
4944            self.assertFalse(
4945                'Uploaded file contains illegal data' in self.browser.contents)
4946
4947    def test_scores_csv_upload_warn_illegal_chars(self):
4948        # for some types of files we issue a warning if upload data
4949        # contains illegal chars (and ignore the data)
4950        self.login_as_lecturer()
4951        self.student['studycourse']['100']['COURSE1'].score = 55
4952        self.browser.open(self.edit_scores_url)
4953        for content, mimetype, name in (
4954                # plain ASCII text, commas, control chars
4955                ('abv,qwe\n\r\r\t\b\n' * 20, 'text/plain', 'my.txt'),
4956                # image data (like a JPEG image)
4957                (open(SAMPLE_IMAGE, 'rb').read(), 'image/jpg', 'my.jpg'),
4958                ):
4959            upload_ctrl = self.browser.getControl(name='uploadfile:file')
4960            upload_ctrl.add_file(StringIO(content), mimetype, name)
4961            self.browser.getControl("Update editable scores").click()
4962            self.assertEqual(
4963                self.student['studycourse']['100']['COURSE1'].score, 55)
4964            self.assertTrue(
4965                'Uploaded file contains illegal data' in self.browser.contents)
4966
4967class ParentsUITests(StudentsFullSetup):
4968    # Tests for UI actions when acting as parents.
4969
4970    def test_request_ppw(self):
4971        self.app['students'][self.student_id].parents_email = 'par@yy.zz'
4972        self.browser.open('http://localhost/app/requestppw')
4973        self.browser.getControl(name="form.lastname").value = 'tESTer'
4974        self.browser.getControl(name="form.number").value = '123'
4975        self.browser.getControl(name="form.email").value = 'par@yy.zz'
4976        self.browser.getControl("Send temporary login credentials").click()
4977        self.assertTrue('Your password request was successful.'
4978            in self.browser.contents)
4979        logfile = os.path.join(
4980            self.app['datacenter'].storage, 'logs', 'main.log')
4981        logcontent = open(logfile).read()
4982        self.assertTrue('zope.anybody - students.browser.RequestParentsPasswordPage - '
4983                        '123 (K1000000) - par@yy.zz' in logcontent)
4984        return
4985
4986    def test_login_as_parents(self):
4987        # Student login still works after all the changes made
4988        self.browser.open(self.login_path)
4989        self.browser.getControl(name="form.login").value = self.student_id
4990        self.browser.getControl(name="form.password").value = 'spwd'
4991        self.browser.getControl("Login").click()
4992        self.assertTrue('You logged in' in self.browser.contents)
4993        self.browser.open(self.edit_personal_path)
4994        self.browser.getLink("Logout").click()
4995        self.assertTrue('You have been logged out' in self.browser.contents)
4996        # We set parents password
4997        self.app['students'][self.student_id].setParentsPassword('ppwd')
4998        self.browser.open(self.login_path)
4999        # Student can't login with original password
5000        self.browser.getControl(name="form.login").value = self.student_id
5001        self.browser.getControl(name="form.password").value = 'spwd'
5002        self.browser.getControl("Login").click()
5003        self.assertEqual(self.browser.url, self.login_path)
5004        self.assertTrue('Your account has been temporarily deactivated '
5005            'because your parents have logged in.' in self.browser.contents)
5006        # Parents can login with their password
5007        self.browser.open(self.login_path)
5008        self.browser.getControl(name="form.login").value = self.student_id
5009        self.browser.getControl(name="form.password").value = 'ppwd'
5010        self.browser.getControl("Login").click()
5011        self.assertTrue(
5012            'You logged in.' in self.browser.contents)
5013        self.assertTrue(
5014            '<a href="http://localhost/app/students/K1000000">Base Data</a>'
5015            in self.browser.contents)
5016        # They do not see all links ...
5017        self.assertFalse(
5018            '<a href="http://localhost/app/students/K1000000/history">History</a>'
5019            in self.browser.contents)
5020        # ... and can't change anything
5021        self.assertRaises(
5022            Unauthorized, self.browser.open, self.edit_personal_path)
5023        # If the password has expired, parents are logged out and the
5024        # student can login again with the original password
5025        delta = timedelta(minutes=11)
5026        self.app['students'][self.student_id].parents_password[
5027            'timestamp'] = datetime.utcnow() - delta
5028        self.app['students'][self.student_id]._p_changed = True
5029        self.assertRaises(
5030            Unauthorized, self.browser.open, self.student_path)
5031        # Parents login is written to log file
5032        logfile = os.path.join(
5033            self.app['datacenter'].storage, 'logs', 'students.log')
5034        logcontent = open(logfile).read()
5035        self.assertTrue(
5036            'K1000000 - browser.pages.LoginPage - K1000000 - Parents logged in'
5037            in logcontent)
Note: See TracBrowser for help on using the repository browser.