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

Last change on this file since 9713 was 9705, checked in by Henrik Bettermann, 13 years ago

If suspended_comment is set this message will be flashed instead.

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