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

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

We do not need to redirect to context view.

  • Property svn:keywords set to Id
File size: 136.2 KB
Line 
1## $Id: test_browser.py 9716 2012-11-23 11:24:35Z 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.getControl(name="form.p_category").value = ['bed_allocation']
929        self.browser.getControl("Create ticket").click()
930        ctrl = self.browser.getControl(name='val_id')
931        value = ctrl.options[0]
932        self.browser.getLink(value).click()
933        self.browser.open(self.browser.url + '/approve')
934        # The new HOS-0 pin has been created
935        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
936        pin = self.app['accesscodes']['HOS-0'].keys()[0]
937        ac = self.app['accesscodes']['HOS-0'][pin]
938        self.assertEqual(ac.owner, self.student_id)
939        parts = pin.split('-')[1:]
940        sfeseries, sfenumber = parts
941        # Managers can use HOS code and book a bed space with it
942        self.browser.open(self.acco_path)
943        self.browser.getLink("Book accommodation").click()
944        self.assertMatches('...You are in the wrong...',
945                           self.browser.contents)
946        IWorkflowInfo(self.student).fireTransition('admit')
947        # An existing HOS code can only be used if students
948        # are in accommodation session
949        self.student['studycourse'].current_session = 2003
950        self.browser.getLink("Book accommodation").click()
951        self.assertMatches('...Your current session does not match...',
952                           self.browser.contents)
953        self.student['studycourse'].current_session = 2004
954        # All requirements are met and ticket can be created
955        self.browser.getLink("Book accommodation").click()
956        self.assertMatches('...Activation Code:...',
957                           self.browser.contents)
958        self.browser.getControl(name="ac_series").value = sfeseries
959        self.browser.getControl(name="ac_number").value = sfenumber
960        self.browser.getControl("Create bed ticket").click()
961        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
962                           self.browser.contents)
963        # Bed has been allocated
964        bed1 = self.app['hostels']['hall-1']['hall-1_A_101_A']
965        self.assertTrue(bed1.owner == self.student_id)
966        # BedTicketAddPage is now blocked
967        self.browser.getLink("Book accommodation").click()
968        self.assertMatches('...You already booked a bed space...',
969            self.browser.contents)
970        # The bed ticket displays the data correctly
971        self.browser.open(self.acco_path + '/2004')
972        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
973                           self.browser.contents)
974        self.assertMatches('...2004/2005...', self.browser.contents)
975        self.assertMatches('...regular_male_fr...', self.browser.contents)
976        self.assertMatches('...%s...' % pin, self.browser.contents)
977        # Booking is properly logged
978        logcontent = open(logfile).read()
979        self.assertTrue('zope.mgr - students.browser.BedTicketAddPage '
980            '- K1000000 - booked: hall-1_A_101_A' in logcontent)
981        # Managers can relocate students if the student's bed_type has changed
982        self.browser.getLink("Relocate student").click()
983        self.assertMatches(
984            "...Student can't be relocated...", self.browser.contents)
985        self.student.sex = u'f'
986        self.browser.getLink("Relocate student").click()
987        self.assertMatches(
988            "...Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
989        self.assertTrue(bed1.owner == NOT_OCCUPIED)
990        bed2 = self.app['hostels']['hall-1']['hall-1_A_101_B']
991        self.assertTrue(bed2.owner == self.student_id)
992        self.assertTrue(self.student['accommodation'][
993            '2004'].bed_type == u'regular_female_fr')
994        # Relocation is properly logged
995        logcontent = open(logfile).read()
996        self.assertTrue('zope.mgr - students.browser.BedTicketRelocationPage '
997            '- K1000000 - relocated: hall-1_A_101_B' in logcontent)
998        # The payment object still shows the original payment item
999        payment_id = self.student['payments'].keys()[0]
1000        payment = self.student['payments'][payment_id]
1001        self.assertTrue(payment.p_item == u'regular_male_fr')
1002        # Managers can relocate students if the bed's bed_type has changed
1003        bed1.bed_type = u'regular_female_fr'
1004        bed2.bed_type = u'regular_male_fr'
1005        notify(grok.ObjectModifiedEvent(bed1))
1006        notify(grok.ObjectModifiedEvent(bed2))
1007        self.browser.getLink("Relocate student").click()
1008        self.assertMatches(
1009            "...Student relocated...", self.browser.contents)
1010        self.assertMatches(
1011            "... Hall 1, Block A, Room 101, Bed A...", self.browser.contents)
1012        self.assertMatches(bed1.owner, self.student_id)
1013        self.assertMatches(bed2.owner, NOT_OCCUPIED)
1014        # Managers can't relocate students if bed is reserved
1015        self.student.sex = u'm'
1016        bed1.bed_type = u'regular_female_reserved'
1017        notify(grok.ObjectModifiedEvent(bed1))
1018        self.browser.getLink("Relocate student").click()
1019        self.assertMatches(
1020            "...Students in reserved beds can't be relocated...",
1021            self.browser.contents)
1022        # Managers can relocate students if booking has been cancelled but
1023        # other bed space has been manually allocated after cancellation
1024        old_owner = bed1.releaseBed()
1025        self.assertMatches(old_owner, self.student_id)
1026        bed2.owner = self.student_id
1027        self.browser.open(self.acco_path + '/2004')
1028        self.assertMatches(
1029            "...booking cancelled...", self.browser.contents)
1030        self.browser.getLink("Relocate student").click()
1031        # We didn't informed the catalog therefore the new owner is not found
1032        self.assertMatches(
1033            "...There is no free bed in your category regular_male_fr...",
1034            self.browser.contents)
1035        # Now we fire the event properly
1036        notify(grok.ObjectModifiedEvent(bed2))
1037        self.browser.getLink("Relocate student").click()
1038        self.assertMatches(
1039            "...Student relocated...", self.browser.contents)
1040        self.assertMatches(
1041            "... Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
1042          # Managers can delete bed tickets
1043        self.browser.open(self.acco_path)
1044        ctrl = self.browser.getControl(name='val_id')
1045        value = ctrl.options[0]
1046        ctrl.getControl(value=value).selected = True
1047        self.browser.getControl("Remove selected", index=0).click()
1048        self.assertMatches('...Successfully removed...', self.browser.contents)
1049        # The bed has been properly released by the event handler
1050        self.assertMatches(bed1.owner, NOT_OCCUPIED)
1051        self.assertMatches(bed2.owner, NOT_OCCUPIED)
1052        return
1053
1054    def test_manage_workflow(self):
1055        # Managers can pass through the whole workflow
1056        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1057        student = self.app['students'][self.student_id]
1058        self.browser.open(self.trigtrans_path)
1059        self.assertTrue(student.clearance_locked)
1060        self.browser.getControl(name="transition").value = ['admit']
1061        self.browser.getControl("Save").click()
1062        self.assertTrue(student.clearance_locked)
1063        self.browser.getControl(name="transition").value = ['start_clearance']
1064        self.browser.getControl("Save").click()
1065        self.assertFalse(student.clearance_locked)
1066        self.browser.getControl(name="transition").value = ['request_clearance']
1067        self.browser.getControl("Save").click()
1068        self.assertTrue(student.clearance_locked)
1069        self.browser.getControl(name="transition").value = ['clear']
1070        self.browser.getControl("Save").click()
1071        # Managers approve payment, they do not pay
1072        self.assertFalse('pay_first_school_fee' in self.browser.contents)
1073        self.browser.getControl(
1074            name="transition").value = ['approve_first_school_fee']
1075        self.browser.getControl("Save").click()
1076        self.browser.getControl(name="transition").value = ['reset6']
1077        self.browser.getControl("Save").click()
1078        # In state returning the pay_school_fee transition triggers some
1079        # changes of attributes
1080        self.browser.getControl(name="transition").value = ['approve_school_fee']
1081        self.browser.getControl("Save").click()
1082        self.assertEqual(student['studycourse'].current_session, 2005) # +1
1083        self.assertEqual(student['studycourse'].current_level, 200) # +100
1084        self.assertEqual(student['studycourse'].current_verdict, '0') # 0 = Zero = not set
1085        self.assertEqual(student['studycourse'].previous_verdict, 'A')
1086        self.browser.getControl(name="transition").value = ['register_courses']
1087        self.browser.getControl("Save").click()
1088        self.browser.getControl(name="transition").value = ['validate_courses']
1089        self.browser.getControl("Save").click()
1090        self.browser.getControl(name="transition").value = ['return']
1091        self.browser.getControl("Save").click()
1092        return
1093
1094    def test_manage_pg_workflow(self):
1095        # Managers can pass through the whole workflow
1096        IWorkflowState(self.student).setState('school fee paid')
1097        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1098        student = self.app['students'][self.student_id]
1099        self.browser.open(self.trigtrans_path)
1100        self.assertTrue('<option value="reset6">' in self.browser.contents)
1101        self.assertTrue('<option value="register_courses">' in self.browser.contents)
1102        self.assertTrue('<option value="reset5">' in self.browser.contents)
1103        self.certificate.study_mode = 'pg_ft'
1104        self.browser.open(self.trigtrans_path)
1105        self.assertFalse('<option value="reset6">' in self.browser.contents)
1106        self.assertFalse('<option value="register_courses">' in self.browser.contents)
1107        self.assertTrue('<option value="reset5">' in self.browser.contents)
1108        return
1109
1110    def test_manage_import(self):
1111        # Managers can import student data files
1112        datacenter_path = 'http://localhost/app/datacenter'
1113        # Prepare a csv file for students
1114        open('students.csv', 'wb').write(
1115"""firstname,lastname,reg_number,date_of_birth,matric_number,email,phone,sex,password
1116Aaren,Pieri,1,1990-01-02,100000,aa@aa.ng,1234,m,mypwd1
1117Claus,Finau,2,1990-01-03,100001,aa@aa.ng,1234,m,mypwd1
1118Brit,Berson,3,1990-01-04,100001,aa@aa.ng,1234,m,mypwd1
1119""")
1120        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1121        self.browser.open(datacenter_path)
1122        self.browser.getLink('Upload data').click()
1123        filecontents = StringIO(open('students.csv', 'rb').read())
1124        filewidget = self.browser.getControl(name='uploadfile:file')
1125        filewidget.add_file(filecontents, 'text/plain', 'students.csv')
1126        self.browser.getControl(name='SUBMIT').click()
1127        self.browser.getLink('Process data').click()
1128        button = lookup_submit_value(
1129            'select', 'students_zope.mgr.csv', self.browser)
1130        button.click()
1131        importerselect = self.browser.getControl(name='importer')
1132        modeselect = self.browser.getControl(name='mode')
1133        importerselect.getControl('Student Processor').selected = True
1134        modeselect.getControl(value='create').selected = True
1135        self.browser.getControl('Proceed to step 3').click()
1136        self.assertTrue('Header fields OK' in self.browser.contents)
1137        self.browser.getControl('Perform import').click()
1138        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
1139        self.assertTrue('Successfully processed 2 rows' in self.browser.contents)
1140        self.assertTrue('Batch processing finished' in self.browser.contents)
1141        open('studycourses.csv', 'wb').write(
1142"""reg_number,matric_number,certificate,current_session,current_level
11431,,CERT1,2008,100
1144,100001,CERT1,2008,100
1145,100002,CERT1,2008,100
1146""")
1147        self.browser.open(datacenter_path)
1148        self.browser.getLink('Upload data').click()
1149        filecontents = StringIO(open('studycourses.csv', 'rb').read())
1150        filewidget = self.browser.getControl(name='uploadfile:file')
1151        filewidget.add_file(filecontents, 'text/plain', 'studycourses.csv')
1152        self.browser.getControl(name='SUBMIT').click()
1153        self.browser.getLink('Process data').click()
1154        button = lookup_submit_value(
1155            'select', 'studycourses_zope.mgr.csv', self.browser)
1156        button.click()
1157        importerselect = self.browser.getControl(name='importer')
1158        modeselect = self.browser.getControl(name='mode')
1159        importerselect.getControl(
1160            'StudentStudyCourse Processor (update only)').selected = True
1161        modeselect.getControl(value='create').selected = True
1162        self.browser.getControl('Proceed to step 3').click()
1163        self.assertTrue('Update mode only' in self.browser.contents)
1164        self.browser.getControl('Proceed to step 3').click()
1165        self.assertTrue('Header fields OK' in self.browser.contents)
1166        self.browser.getControl('Perform import').click()
1167        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
1168        self.assertTrue('Successfully processed 2 rows'
1169                        in self.browser.contents)
1170        # The students are properly indexed and we can
1171        # thus find a student in  the department
1172        self.browser.open(self.manage_container_path)
1173        self.browser.getControl(name="searchtype").value = ['depcode']
1174        self.browser.getControl(name="searchterm").value = 'dep1'
1175        self.browser.getControl("Search").click()
1176        self.assertTrue('Aaren Pieri' in self.browser.contents)
1177        # We can search for a new student by name ...
1178        self.browser.getControl(name="searchtype").value = ['fullname']
1179        self.browser.getControl(name="searchterm").value = 'Claus'
1180        self.browser.getControl("Search").click()
1181        self.assertTrue('Claus Finau' in self.browser.contents)
1182        # ... and check if the imported password has been properly set
1183        ctrl = self.browser.getControl(name='entries')
1184        value = ctrl.options[0]
1185        claus = self.app['students'][value]
1186        self.assertTrue(IUserAccount(claus).checkPassword('mypwd1'))
1187        return
1188
1189    def test_handle_clearance_by_co(self):
1190        # Create clearance officer
1191        self.app['users'].addUser('mrclear', 'mrclearsecret')
1192        self.app['users']['mrclear'].email = 'mrclear@foo.ng'
1193        self.app['users']['mrclear'].title = 'Carlo Pitter'
1194        # Clearance officers need not necessarily to get
1195        # the StudentsOfficer site role
1196        #prmglobal = IPrincipalRoleManager(self.app)
1197        #prmglobal.assignRoleToPrincipal('waeup.StudentsOfficer', 'mrclear')
1198        # Assign local ClearanceOfficer role
1199        department = self.app['faculties']['fac1']['dep1']
1200        prmlocal = IPrincipalRoleManager(department)
1201        prmlocal.assignRoleToPrincipal('waeup.local.ClearanceOfficer', 'mrclear')
1202        IWorkflowState(self.student).setState('clearance started')
1203        # Login as clearance officer
1204        self.browser.open(self.login_path)
1205        self.browser.getControl(name="form.login").value = 'mrclear'
1206        self.browser.getControl(name="form.password").value = 'mrclearsecret'
1207        self.browser.getControl("Login").click()
1208        self.assertMatches('...You logged in...', self.browser.contents)
1209        # CO can see his roles
1210        self.browser.getLink("My Roles").click()
1211        self.assertMatches(
1212            '...<div>Academics Officer (view only)</div>...',
1213            self.browser.contents)
1214        #self.assertMatches(
1215        #    '...<div>Students Officer (view only)</div>...',
1216        #    self.browser.contents)
1217        # But not his local role ...
1218        self.assertFalse('Clearance Officer' in self.browser.contents)
1219        # ... because we forgot to notify the department that the local role
1220        # has changed
1221        notify(LocalRoleSetEvent(
1222            department, 'waeup.local.ClearanceOfficer', 'mrclear', granted=True))
1223        self.browser.open('http://localhost/app/users/mrclear/my_roles')
1224        self.assertTrue('Clearance Officer' in self.browser.contents)
1225        self.assertMatches(
1226            '...<a href="http://localhost/app/faculties/fac1/dep1">...',
1227            self.browser.contents)
1228        # CO can view the student ...
1229        self.browser.open(self.clearance_path)
1230        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1231        self.assertEqual(self.browser.url, self.clearance_path)
1232        # ... but not other students
1233        other_student = Student()
1234        other_student.firstname = u'Dep2'
1235        other_student.lastname = u'Student'
1236        self.app['students'].addStudent(other_student)
1237        other_student_path = (
1238            'http://localhost/app/students/%s' % other_student.student_id)
1239        self.assertRaises(
1240            Unauthorized, self.browser.open, other_student_path)
1241        # Only in state clearance requested the CO does see the 'Clear' button
1242        self.browser.open(self.clearance_path)
1243        self.assertFalse('Clear student' in self.browser.contents)
1244        IWorkflowInfo(self.student).fireTransition('request_clearance')
1245        self.browser.open(self.clearance_path)
1246        self.assertTrue('Clear student' in self.browser.contents)
1247        self.browser.getLink("Clear student").click()
1248        self.assertTrue('Student has been cleared' in self.browser.contents)
1249        self.assertTrue('cleared' in self.browser.contents)
1250        self.browser.open(self.history_path)
1251        self.assertTrue('Cleared by Carlo Pitter' in self.browser.contents)
1252        # Hide real name.
1253        self.app['users']['mrclear'].public_name = 'My Public Name'
1254        self.browser.open(self.clearance_path)
1255        self.browser.getLink("Reject clearance").click()
1256        self.assertEqual(
1257            self.browser.url, self.student_path + '/reject_clearance')
1258        # Type comment why
1259        self.browser.getControl(name="form.officer_comment").value = """Dear Student,
1260You did not fill properly.
1261"""
1262        self.browser.getControl("Save comment").click()
1263        self.assertTrue('Clearance has been annulled' in self.browser.contents)
1264        url = ('http://localhost/app/students/K1000000/'
1265              'contactstudent?body=Dear+Student%2C%0AYou+did+not+fill+properly.'
1266              '%0A&subject=Clearance+has+been+annulled.')
1267        # CO does now see the prefilled contact form and can send a message
1268        self.assertEqual(self.browser.url, url)
1269        self.assertTrue('clearance started' in self.browser.contents)
1270        self.assertTrue('name="form.subject" size="20" type="text" '
1271            'value="Clearance has been annulled."'
1272            in self.browser.contents)
1273        self.assertTrue('name="form.body" rows="10" >Dear Student,'
1274            in self.browser.contents)
1275        self.browser.getControl("Send message now").click()
1276        self.assertTrue('Your message has been sent' in self.browser.contents)
1277        # The comment has been stored ...
1278        self.assertEqual(self.student.officer_comment,
1279            u'Dear Student,\nYou did not fill properly.\n')
1280        # ... and logged
1281        logfile = os.path.join(
1282            self.app['datacenter'].storage, 'logs', 'students.log')
1283        logcontent = open(logfile).read()
1284        self.assertTrue(
1285            'INFO - mrclear - students.browser.StudentRejectClearancePage - '
1286            'K1000000 - comment: Dear Student,<br>You did not fill '
1287            'properly.<br>\n' in logcontent)
1288        self.browser.open(self.history_path)
1289        self.assertTrue("Reset to 'clearance started' by My Public Name" in
1290            self.browser.contents)
1291        IWorkflowInfo(self.student).fireTransition('request_clearance')
1292        self.browser.open(self.clearance_path)
1293        self.browser.getLink("Reject clearance").click()
1294        self.browser.getControl("Save comment").click()
1295        self.assertTrue('Clearance request has been rejected'
1296            in self.browser.contents)
1297        self.assertTrue('clearance started' in self.browser.contents)
1298        # The CO can't clear students if not in state
1299        # clearance requested
1300        self.browser.open(self.student_path + '/clear')
1301        self.assertTrue('Student is in wrong state'
1302            in self.browser.contents)
1303        # The CO can go to his department throug the my_roles page ...
1304        self.browser.open('http://localhost/app/users/mrclear/my_roles')
1305        self.browser.getLink("http://localhost/app/faculties/fac1/dep1").click()
1306        # ... and view the list of students
1307        self.browser.getLink("Show students").click()
1308        self.assertTrue(self.student_id in self.browser.contents)
1309        # The comment is indicated by 'yes'
1310        self.assertTrue('<td><span>yes</span></td>' in self.browser.contents)
1311        # When a student is cleared the comment is automatically deleted
1312        IWorkflowInfo(self.student).fireTransition('request_clearance')
1313        IWorkflowInfo(self.student).fireTransition('clear')
1314        self.assertEqual(self.student.officer_comment, None)
1315
1316    def test_handle_courses_by_ca(self):
1317        # Create course adviser
1318        self.app['users'].addUser('mrsadvise', 'mrsadvisesecret')
1319        self.app['users']['mrsadvise'].email = 'mradvise@foo.ng'
1320        self.app['users']['mrsadvise'].title = u'Helen Procter'
1321        # Assign local CourseAdviser100 role for a certificate
1322        cert = self.app['faculties']['fac1']['dep1'].certificates['CERT1']
1323        prmlocal = IPrincipalRoleManager(cert)
1324        prmlocal.assignRoleToPrincipal('waeup.local.CourseAdviser100', 'mrsadvise')
1325        IWorkflowState(self.student).setState('school fee paid')
1326        # Login as course adviser
1327        self.browser.open(self.login_path)
1328        self.browser.getControl(name="form.login").value = 'mrsadvise'
1329        self.browser.getControl(name="form.password").value = 'mrsadvisesecret'
1330        self.browser.getControl("Login").click()
1331        self.assertMatches('...You logged in...', self.browser.contents)
1332        # CO can see his roles
1333        self.browser.getLink("My Roles").click()
1334        self.assertMatches(
1335            '...<div>Academics Officer (view only)</div>...',
1336            self.browser.contents)
1337        # But not his local role ...
1338        self.assertFalse('Course Adviser' in self.browser.contents)
1339        # ... because we forgot to notify the certificate that the local role
1340        # has changed
1341        notify(LocalRoleSetEvent(
1342            cert, 'waeup.local.CourseAdviser100', 'mrsadvise', granted=True))
1343        self.browser.open('http://localhost/app/users/mrsadvise/my_roles')
1344        self.assertTrue('Course Adviser 100L' in self.browser.contents)
1345        self.assertMatches(
1346            '...<a href="http://localhost/app/faculties/fac1/dep1/certificates/CERT1">...',
1347            self.browser.contents)
1348        # CA can view the student ...
1349        self.browser.open(self.student_path)
1350        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1351        self.assertEqual(self.browser.url, self.student_path)
1352        # ... but not other students
1353        other_student = Student()
1354        other_student.firstname = u'Dep2'
1355        other_student.lastname = u'Student'
1356        self.app['students'].addStudent(other_student)
1357        other_student_path = (
1358            'http://localhost/app/students/%s' % other_student.student_id)
1359        self.assertRaises(
1360            Unauthorized, self.browser.open, other_student_path)
1361        # We add study level 110 to the student's studycourse
1362        studylevel = StudentStudyLevel()
1363        studylevel.level = 110
1364        self.student['studycourse'].addStudentStudyLevel(
1365            cert,studylevel)
1366        L110_student_path = self.studycourse_path + '/110'
1367        # Only in state courses registered and only if the current level
1368        # corresponds with the name of the study level object
1369        # the 100L CA does see the 'Validate' button
1370        self.browser.open(L110_student_path)
1371        self.assertFalse('Validate courses' in self.browser.contents)
1372        IWorkflowInfo(self.student).fireTransition('register_courses')
1373        self.browser.open(L110_student_path)
1374        self.assertFalse('Validate courses' in self.browser.contents)
1375        self.student['studycourse'].current_level = 110
1376        self.browser.open(L110_student_path)
1377        self.assertTrue('Validate courses' in self.browser.contents)
1378        # ... but a 100L CA does not see the button on other levels
1379        studylevel2 = StudentStudyLevel()
1380        studylevel2.level = 200
1381        self.student['studycourse'].addStudentStudyLevel(
1382            cert,studylevel2)
1383        L200_student_path = self.studycourse_path + '/200'
1384        self.browser.open(L200_student_path)
1385        self.assertFalse('Validate courses' in self.browser.contents)
1386        self.browser.open(L110_student_path)
1387        self.browser.getLink("Validate courses").click()
1388        self.assertTrue('Course list has been validated' in self.browser.contents)
1389        self.assertTrue('courses validated' in self.browser.contents)
1390        self.assertEqual(self.student['studycourse']['110'].validated_by,
1391            'Helen Procter')
1392        self.assertMatches(
1393            '<YYYY-MM-DD hh:mm:ss>',
1394            self.student['studycourse']['110'].validation_date.strftime(
1395                "%Y-%m-%d %H:%M:%S"))
1396        self.browser.getLink("Reject courses").click()
1397        self.assertTrue('Course list request has been annulled.'
1398            in self.browser.contents)
1399        urlmessage = 'Course+list+request+has+been+annulled.'
1400        self.assertEqual(self.browser.url, self.student_path +
1401            '/contactstudent?subject=%s' % urlmessage)
1402        self.assertTrue('school fee paid' in self.browser.contents)
1403        self.assertTrue(self.student['studycourse']['110'].validated_by is None)
1404        self.assertTrue(self.student['studycourse']['110'].validation_date is None)
1405        IWorkflowInfo(self.student).fireTransition('register_courses')
1406        self.browser.open(L110_student_path)
1407        self.browser.getLink("Reject courses").click()
1408        self.assertTrue('Course list request has been rejected'
1409            in self.browser.contents)
1410        self.assertTrue('school fee paid' in self.browser.contents)
1411        # CA does now see the contact form and can send a message
1412        self.browser.getControl(name="form.subject").value = 'Important subject'
1413        self.browser.getControl(name="form.body").value = 'Course list rejected'
1414        self.browser.getControl("Send message now").click()
1415        self.assertTrue('Your message has been sent' in self.browser.contents)
1416        # The CA can't validate courses if not in state
1417        # courses registered
1418        self.browser.open(L110_student_path + '/validate_courses')
1419        self.assertTrue('Student is in the wrong state'
1420            in self.browser.contents)
1421        # The CA can go to his certificate through the my_roles page
1422        self.browser.open('http://localhost/app/users/mrsadvise/my_roles')
1423        self.browser.getLink(
1424            "http://localhost/app/faculties/fac1/dep1/certificates/CERT1").click()
1425        # and view the list of students
1426        self.browser.getLink("Show students").click()
1427        self.assertTrue(self.student_id in self.browser.contents)
1428
1429    def test_handle_courses_by_lecturer(self):
1430        # Create course lecturer
1431        self.app['users'].addUser('mrslecturer', 'mrslecturersecret')
1432        self.app['users']['mrslecturer'].email = 'mrslecturer@foo.ng'
1433        self.app['users']['mrslecturer'].title = u'Mercedes Benz'
1434        # Assign local Courselecturer100 role for a certificate
1435        course = self.app['faculties']['fac1']['dep1'].courses['COURSE1']
1436        prmlocal = IPrincipalRoleManager(course)
1437        prmlocal.assignRoleToPrincipal('waeup.local.Lecturer', 'mrslecturer')
1438        # Login as lecturer
1439        self.browser.open(self.login_path)
1440        self.browser.getControl(name="form.login").value = 'mrslecturer'
1441        self.browser.getControl(name="form.password").value = 'mrslecturersecret'
1442        self.browser.getControl("Login").click()
1443        self.assertMatches('...You logged in...', self.browser.contents)
1444        # CO can see her roles
1445        self.browser.getLink("My Roles").click()
1446        self.assertMatches(
1447            '...<div>Academics Officer (view only)</div>...',
1448            self.browser.contents)
1449        # But not her local role ...
1450        self.assertFalse('Lecturer' in self.browser.contents)
1451        # ... because we forgot to notify the course that the local role
1452        # has changed
1453        notify(LocalRoleSetEvent(
1454            course, 'waeup.local.Lecturer', 'mrslecturer', granted=True))
1455        self.browser.open('http://localhost/app/users/mrslecturer/my_roles')
1456        self.assertTrue('Lecturer' in self.browser.contents)
1457        self.assertMatches(
1458            '...<a href="http://localhost/app/faculties/fac1/dep1/courses/COURSE1">...',
1459            self.browser.contents)
1460        # The lecturer can go to her course
1461        self.browser.getLink(
1462            "http://localhost/app/faculties/fac1/dep1/courses/COURSE1").click()
1463        # and view the list of students
1464        self.browser.getLink("Show students").click()
1465        self.assertTrue('<th>Fullname</th>' in self.browser.contents)
1466        # No student in course so far
1467        self.assertFalse(self.student_id in self.browser.contents)
1468        studylevel = createObject(u'waeup.StudentStudyLevel')
1469        studylevel.level = 100
1470        self.student['studycourse'].addStudentStudyLevel(
1471            self.certificate, studylevel)
1472        # Now the student has registered the course and can
1473        # be seen by the lecturer.
1474        self.browser.open("http://localhost/app/faculties/fac1/dep1/courses/COURSE1/showcoursestudents")
1475        self.assertTrue(self.student_id in self.browser.contents)
1476        # XXX: So far the lecturer can neither access ths student ...
1477        self.assertRaises(
1478            Unauthorized, self.browser.open, self.student_path)
1479        # ... nor the respective course ticket since a
1480        # CourseTicketPrincipalRoleManager does not yet exist.
1481        self.assertTrue('COURSE1' in self.student['studycourse']['100'].keys())
1482        course_ticket_path = self.student_path + '/studycourse/100/COURSE1'
1483        self.assertRaises(
1484            Unauthorized, self.browser.open, course_ticket_path)
1485
1486    def test_change_current_mode(self):
1487        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1488        self.browser.open(self.clearance_path)
1489        self.assertFalse('Employer' in self.browser.contents)
1490        self.browser.open(self.manage_clearance_path)
1491        self.assertFalse('Employer' in self.browser.contents)
1492        self.student.clearance_locked = False
1493        self.browser.open(self.edit_clearance_path)
1494        self.assertFalse('Employer' in self.browser.contents)
1495        # Now we change the study mode of the certificate and a different
1496        # interface is used by clearance views.
1497        self.certificate.study_mode = 'pg_ft'
1498        # Invariants are not being checked here?!
1499        self.certificate.end_level = 100
1500        self.browser.open(self.clearance_path)
1501        self.assertTrue('Employer' in self.browser.contents)
1502        self.browser.open(self.manage_clearance_path)
1503        self.assertTrue('Employer' in self.browser.contents)
1504        self.browser.open(self.edit_clearance_path)
1505        self.assertTrue('Employer' in self.browser.contents)
1506
1507    def test_activate_deactivate_buttons(self):
1508        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1509        self.browser.open(self.student_path)
1510        self.browser.getLink("Deactivate").click()
1511        self.assertTrue(
1512            'Student account has been deactivated.' in self.browser.contents)
1513        self.assertTrue(
1514            'Base Data (account deactivated)' in self.browser.contents)
1515        self.assertTrue(self.student.suspended)
1516        self.browser.getLink("Activate").click()
1517        self.assertTrue(
1518            'Student account has been activated.' in self.browser.contents)
1519        self.assertFalse(
1520            'Base Data (account deactivated)' in self.browser.contents)
1521        self.assertFalse(self.student.suspended)
1522        # History messages have been added ...
1523        self.browser.getLink("History").click()
1524        self.assertTrue(
1525            'Student account deactivated by Manager<br />' in self.browser.contents)
1526        self.assertTrue(
1527            'Student account activated by Manager<br />' in self.browser.contents)
1528        # ... and actions have been logged.
1529        logfile = os.path.join(
1530            self.app['datacenter'].storage, 'logs', 'students.log')
1531        logcontent = open(logfile).read()
1532        self.assertTrue('zope.mgr - students.browser.StudentDeactivatePage - '
1533                        'K1000000 - account deactivated' in logcontent)
1534        self.assertTrue('zope.mgr - students.browser.StudentActivatePage - '
1535                        'K1000000 - account activated' in logcontent)
1536
1537    def test_manage_student_transfer(self):
1538        # Add second certificate
1539        self.certificate2 = createObject('waeup.Certificate')
1540        self.certificate2.code = u'CERT2'
1541        self.certificate2.study_mode = 'ug_ft'
1542        self.certificate2.start_level = 999
1543        self.certificate2.end_level = 999
1544        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
1545            self.certificate2)
1546
1547        # Add study level to old study course
1548        studylevel = createObject(u'waeup.StudentStudyLevel')
1549        studylevel.level = 200
1550        self.student['studycourse'].addStudentStudyLevel(
1551            self.certificate, studylevel)
1552        studylevel = createObject(u'waeup.StudentStudyLevel')
1553        studylevel.level = 999
1554        self.student['studycourse'].addStudentStudyLevel(
1555            self.certificate, studylevel)
1556
1557        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1558        self.browser.open(self.student_path)
1559        self.browser.getLink("Transfer").click()
1560        self.browser.getControl(name="form.certificate").value = ['CERT2']
1561        self.browser.getControl(name="form.current_session").value = ['2011']
1562        self.browser.getControl(name="form.current_level").value = ['200']
1563        self.browser.getControl("Transfer").click()
1564        self.assertTrue(
1565            'Current level does not match certificate levels'
1566            in self.browser.contents)
1567        self.browser.getControl(name="form.current_level").value = ['999']
1568        self.browser.getControl("Transfer").click()
1569        self.assertTrue('Successfully transferred' in self.browser.contents)
1570        # The catalog has been updated
1571        cat = queryUtility(ICatalog, name='students_catalog')
1572        results = list(
1573            cat.searchResults(
1574            certcode=('CERT2', 'CERT2')))
1575        self.assertTrue(results[0] is self.student)
1576        results = list(
1577            cat.searchResults(
1578            current_session=(2011, 2011)))
1579        self.assertTrue(results[0] is self.student)
1580        # Add study level to new study course
1581        studylevel = createObject(u'waeup.StudentStudyLevel')
1582        studylevel.level = 999
1583        self.student['studycourse'].addStudentStudyLevel(
1584            self.certificate, studylevel)
1585
1586        # Edit and add pages are locked for old study courses
1587        self.browser.open(self.student_path + '/studycourse/manage')
1588        self.assertFalse('The requested form is locked' in self.browser.contents)
1589        self.browser.open(self.student_path + '/studycourse_1/manage')
1590        self.assertTrue('The requested form is locked' in self.browser.contents)
1591
1592        self.browser.open(self.student_path + '/studycourse/start_session')
1593        self.assertFalse('The requested form is locked' in self.browser.contents)
1594        self.browser.open(self.student_path + '/studycourse_1/start_session')
1595        self.assertTrue('The requested form is locked' in self.browser.contents)
1596
1597        IWorkflowState(self.student).setState('school fee paid')
1598        self.browser.open(self.student_path + '/studycourse/add')
1599        self.assertFalse('The requested form is locked' in self.browser.contents)
1600        self.browser.open(self.student_path + '/studycourse_1/add')
1601        self.assertTrue('The requested form is locked' in self.browser.contents)
1602
1603        self.browser.open(self.student_path + '/studycourse/999/manage')
1604        self.assertFalse('The requested form is locked' in self.browser.contents)
1605        self.browser.open(self.student_path + '/studycourse_1/999/manage')
1606        self.assertTrue('The requested form is locked' in self.browser.contents)
1607
1608        self.browser.open(self.student_path + '/studycourse/999/validate_courses')
1609        self.assertFalse('The requested form is locked' in self.browser.contents)
1610        self.browser.open(self.student_path + '/studycourse_1/999/validate_courses')
1611        self.assertTrue('The requested form is locked' in self.browser.contents)
1612
1613        self.browser.open(self.student_path + '/studycourse/999/reject_courses')
1614        self.assertFalse('The requested form is locked' in self.browser.contents)
1615        self.browser.open(self.student_path + '/studycourse_1/999/reject_courses')
1616        self.assertTrue('The requested form is locked' in self.browser.contents)
1617
1618        self.browser.open(self.student_path + '/studycourse/999/add')
1619        self.assertFalse('The requested form is locked' in self.browser.contents)
1620        self.browser.open(self.student_path + '/studycourse_1/999/add')
1621        self.assertTrue('The requested form is locked' in self.browser.contents)
1622
1623        self.browser.open(self.student_path + '/studycourse/999/edit')
1624        self.assertFalse('The requested form is locked' in self.browser.contents)
1625        self.browser.open(self.student_path + '/studycourse_1/999/edit')
1626        self.assertTrue('The requested form is locked' in self.browser.contents)
1627
1628    def test_login_as_student(self):
1629        # StudentImpersonators can login as student
1630        # Create clearance officer
1631        self.app['users'].addUser('mrofficer', 'mrofficersecret')
1632        self.app['users']['mrofficer'].email = 'mrofficer@foo.ng'
1633        self.app['users']['mrofficer'].title = 'Harry Actor'
1634        prmglobal = IPrincipalRoleManager(self.app)
1635        prmglobal.assignRoleToPrincipal('waeup.StudentImpersonator', 'mrofficer')
1636        prmglobal.assignRoleToPrincipal('waeup.StudentsManager', 'mrofficer')
1637        # Login as student impersonator
1638        self.browser.open(self.login_path)
1639        self.browser.getControl(name="form.login").value = 'mrofficer'
1640        self.browser.getControl(name="form.password").value = 'mrofficersecret'
1641        self.browser.getControl("Login").click()
1642        self.assertMatches('...You logged in...', self.browser.contents)
1643        self.browser.open(self.student_path)
1644        self.browser.getLink("Login as").click()
1645        self.browser.getControl("Set password now").click()
1646        temp_password = self.browser.getControl(name='form.password').value
1647        self.browser.getControl("Login now").click()
1648        self.assertMatches(
1649            '...You successfully logged in as...', self.browser.contents)
1650        # We are logged in as student and can see the 'My Data' tab
1651        self.assertMatches(
1652            '...<a href="#" class="dropdown-toggle">My Data</a>...',
1653            self.browser.contents)
1654        self.browser.getLink("Logout").click()
1655        # The student can't login with the original password ...
1656        self.browser.open(self.login_path)
1657        self.browser.getControl(name="form.login").value = self.student_id
1658        self.browser.getControl(name="form.password").value = 'spwd'
1659        self.browser.getControl("Login").click()
1660        self.assertMatches(
1661            '...Your account has been temporarily deactivated...',
1662            self.browser.contents)
1663        # ... but with the temporary password
1664        self.browser.open(self.login_path)
1665        self.browser.getControl(name="form.login").value = self.student_id
1666        self.browser.getControl(name="form.password").value = temp_password
1667        self.browser.getControl("Login").click()
1668        self.assertMatches('...You logged in...', self.browser.contents)
1669        # Creation of temp_password is properly logged
1670        logfile = os.path.join(
1671            self.app['datacenter'].storage, 'logs', 'students.log')
1672        logcontent = open(logfile).read()
1673        self.assertTrue(
1674            'mrofficer - students.browser.LoginAsStudentStep1 - K1000000 - '
1675            'temp_password generated: %s' % temp_password in logcontent)
1676
1677class StudentUITests(StudentsFullSetup):
1678    # Tests for Student class views and pages
1679
1680    def test_student_change_password(self):
1681        # Students can change the password
1682        self.student.personal_updated = datetime.utcnow()
1683        self.browser.open(self.login_path)
1684        self.browser.getControl(name="form.login").value = self.student_id
1685        self.browser.getControl(name="form.password").value = 'spwd'
1686        self.browser.getControl("Login").click()
1687        self.assertEqual(self.browser.url, self.student_path)
1688        self.assertTrue('You logged in' in self.browser.contents)
1689        # Change password
1690        self.browser.getLink("Change password").click()
1691        self.browser.getControl(name="change_password").value = 'pw'
1692        self.browser.getControl(
1693            name="change_password_repeat").value = 'pw'
1694        self.browser.getControl("Save").click()
1695        self.assertTrue('Password must have at least' in self.browser.contents)
1696        self.browser.getControl(name="change_password").value = 'new_password'
1697        self.browser.getControl(
1698            name="change_password_repeat").value = 'new_passssword'
1699        self.browser.getControl("Save").click()
1700        self.assertTrue('Passwords do not match' in self.browser.contents)
1701        self.browser.getControl(name="change_password").value = 'new_password'
1702        self.browser.getControl(
1703            name="change_password_repeat").value = 'new_password'
1704        self.browser.getControl("Save").click()
1705        self.assertTrue('Password changed' in self.browser.contents)
1706        # We are still logged in. Changing the password hasn't thrown us out.
1707        self.browser.getLink("Base Data").click()
1708        self.assertEqual(self.browser.url, self.student_path)
1709        # We can logout
1710        self.browser.getLink("Logout").click()
1711        self.assertTrue('You have been logged out' in self.browser.contents)
1712        self.assertEqual(self.browser.url, 'http://localhost/app')
1713        # We can login again with the new password
1714        self.browser.getLink("Login").click()
1715        self.browser.open(self.login_path)
1716        self.browser.getControl(name="form.login").value = self.student_id
1717        self.browser.getControl(name="form.password").value = 'new_password'
1718        self.browser.getControl("Login").click()
1719        self.assertEqual(self.browser.url, self.student_path)
1720        self.assertTrue('You logged in' in self.browser.contents)
1721        return
1722
1723    def test_setpassword(self):
1724        # Set password for first-time access
1725        student = Student()
1726        student.reg_number = u'123456'
1727        student.firstname = u'Klaus'
1728        student.lastname = u'Tester'
1729        self.app['students'].addStudent(student)
1730        setpassword_path = 'http://localhost/app/setpassword'
1731        student_path = 'http://localhost/app/students/%s' % student.student_id
1732        self.browser.open(setpassword_path)
1733        self.browser.getControl(name="ac_series").value = self.existing_pwdseries
1734        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
1735        self.browser.getControl(name="reg_number").value = '223456'
1736        self.browser.getControl("Set").click()
1737        self.assertMatches('...No student found...',
1738                           self.browser.contents)
1739        self.browser.getControl(name="reg_number").value = '123456'
1740        self.browser.getControl(name="ac_number").value = '999999'
1741        self.browser.getControl("Set").click()
1742        self.assertMatches('...Access code is invalid...',
1743                           self.browser.contents)
1744        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
1745        self.browser.getControl("Set").click()
1746        self.assertMatches('...Password has been set. Your Student Id is...',
1747                           self.browser.contents)
1748        self.browser.getControl("Set").click()
1749        self.assertMatches(
1750            '...Password has already been set. Your Student Id is...',
1751            self.browser.contents)
1752        existing_pwdpin = self.pwdpins[1]
1753        parts = existing_pwdpin.split('-')[1:]
1754        existing_pwdseries, existing_pwdnumber = parts
1755        self.browser.getControl(name="ac_series").value = existing_pwdseries
1756        self.browser.getControl(name="ac_number").value = existing_pwdnumber
1757        self.browser.getControl(name="reg_number").value = '123456'
1758        self.browser.getControl("Set").click()
1759        self.assertMatches(
1760            '...You are using the wrong Access Code...',
1761            self.browser.contents)
1762        # The student can login with the new credentials
1763        self.browser.open(self.login_path)
1764        self.browser.getControl(name="form.login").value = student.student_id
1765        self.browser.getControl(
1766            name="form.password").value = self.existing_pwdnumber
1767        self.browser.getControl("Login").click()
1768        self.assertEqual(self.browser.url, student_path)
1769        self.assertTrue('You logged in' in self.browser.contents)
1770        return
1771
1772    def test_student_login(self):
1773        # Student cant login if their password is not set
1774        self.student.password = None
1775        self.browser.open(self.login_path)
1776        self.browser.getControl(name="form.login").value = self.student_id
1777        self.browser.getControl(name="form.password").value = 'spwd'
1778        self.browser.getControl("Login").click()
1779        self.assertTrue(
1780            'You entered invalid credentials.' in self.browser.contents)
1781        # We set the password again
1782        IUserAccount(
1783            self.app['students'][self.student_id]).setPassword('spwd')
1784        # Students can't login if their account is suspended/deactivated
1785        self.student.suspended = True
1786        self.browser.open(self.login_path)
1787        self.browser.getControl(name="form.login").value = self.student_id
1788        self.browser.getControl(name="form.password").value = 'spwd'
1789        self.browser.getControl("Login").click()
1790        self.assertMatches(
1791            '...<div class="alert-message warning">'
1792            'Your account has been deactivated.</div>...', self.browser.contents)
1793        # If suspended_comment is set this message will be flashed instead
1794        self.student.suspended_comment = u'Aetsch baetsch!'
1795        self.browser.getControl(name="form.login").value = self.student_id
1796        self.browser.getControl(name="form.password").value = 'spwd'
1797        self.browser.getControl("Login").click()
1798        self.assertMatches(
1799            '...<div class="alert-message warning">Aetsch baetsch!</div>...',
1800            self.browser.contents)
1801        self.student.suspended = False
1802        # Students can't login if a temporary password has been set and
1803        # is not expired
1804        self.app['students'][self.student_id].setTempPassword(
1805            'anybody', 'temp_spwd')
1806        self.browser.open(self.login_path)
1807        self.browser.getControl(name="form.login").value = self.student_id
1808        self.browser.getControl(name="form.password").value = 'spwd'
1809        self.browser.getControl("Login").click()
1810        self.assertMatches(
1811            '...Your account has been temporarily deactivated...',
1812            self.browser.contents)
1813        # The student can login with the temporary password
1814        self.browser.open(self.login_path)
1815        self.browser.getControl(name="form.login").value = self.student_id
1816        self.browser.getControl(name="form.password").value = 'temp_spwd'
1817        self.browser.getControl("Login").click()
1818        self.assertMatches(
1819            '...You logged in...', self.browser.contents)
1820        # Student can view the base data
1821        self.browser.open(self.student_path)
1822        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1823        self.assertEqual(self.browser.url, self.student_path)
1824        # When the password expires ...
1825        delta = timedelta(minutes=11)
1826        self.app['students'][self.student_id].temp_password[
1827            'timestamp'] = datetime.utcnow() - delta
1828        self.app['students'][self.student_id]._p_changed = True
1829        # ... the student will be automatically logged out
1830        self.assertRaises(
1831            Unauthorized, self.browser.open, self.student_path)
1832        # Then the student can login with the original password
1833        self.browser.open(self.login_path)
1834        self.browser.getControl(name="form.login").value = self.student_id
1835        self.browser.getControl(name="form.password").value = 'spwd'
1836        self.browser.getControl("Login").click()
1837        self.assertMatches(
1838            '...You logged in...', self.browser.contents)
1839
1840    def test_student_clearance(self):
1841        # Student cant login if their password is not set
1842        IWorkflowInfo(self.student).fireTransition('admit')
1843        self.browser.open(self.login_path)
1844        self.browser.getControl(name="form.login").value = self.student_id
1845        self.browser.getControl(name="form.password").value = 'spwd'
1846        self.browser.getControl("Login").click()
1847        self.assertMatches(
1848            '...You logged in...', self.browser.contents)
1849        # Admitted student can upload a passport picture
1850        self.browser.open(self.student_path + '/change_portrait')
1851        ctrl = self.browser.getControl(name='passportuploadedit')
1852        file_obj = open(SAMPLE_IMAGE, 'rb')
1853        file_ctrl = ctrl.mech_control
1854        file_ctrl.add_file(file_obj, filename='my_photo.jpg')
1855        self.browser.getControl(
1856            name='upload_passportuploadedit').click()
1857        self.assertTrue(
1858            '<img align="middle" height="125px" src="passport.jpg" />'
1859            in self.browser.contents)
1860        # Students can open admission letter
1861        self.browser.getLink("Base Data").click()
1862        self.browser.getLink("Download admission letter").click()
1863        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1864        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1865        # Student can view the clearance data
1866        self.browser.open(self.student_path)
1867        self.browser.getLink("Clearance Data").click()
1868        # Student can't open clearance edit form before starting clearance
1869        self.browser.open(self.student_path + '/cedit')
1870        self.assertMatches('...The requested form is locked...',
1871                           self.browser.contents)
1872        self.browser.getLink("Clearance Data").click()
1873        self.browser.getLink("Start clearance").click()
1874        self.student.email = None
1875        # Uups, we forgot to fill the email fields
1876        self.browser.getControl("Start clearance").click()
1877        self.assertMatches('...Not all required fields filled...',
1878                           self.browser.contents)
1879        self.browser.open(self.student_path + '/edit_base')
1880        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
1881        self.browser.getControl("Save").click()
1882        self.browser.open(self.student_path + '/start_clearance')
1883        self.browser.getControl(name="ac_series").value = '3'
1884        self.browser.getControl(name="ac_number").value = '4444444'
1885        self.browser.getControl("Start clearance now").click()
1886        self.assertMatches('...Activation code is invalid...',
1887                           self.browser.contents)
1888        self.browser.getControl(name="ac_series").value = self.existing_clrseries
1889        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
1890        # Owner is Hans Wurst, AC can't be invalidated
1891        self.browser.getControl("Start clearance now").click()
1892        self.assertMatches('...You are not the owner of this access code...',
1893                           self.browser.contents)
1894        # Set the correct owner
1895        self.existing_clrac.owner = self.student_id
1896        # clr_code might be set (and thus returns None) due importing
1897        # an empty clr_code column.
1898        self.student.clr_code = None
1899        self.browser.getControl("Start clearance now").click()
1900        self.assertMatches('...Clearance process has been started...',
1901                           self.browser.contents)
1902        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
1903        self.browser.getControl("Save", index=0).click()
1904        # Student can view the clearance data
1905        self.browser.getLink("Clearance Data").click()
1906        # and go back to the edit form
1907        self.browser.getLink("Edit").click()
1908        # Students can upload documents
1909        ctrl = self.browser.getControl(name='birthcertificateupload')
1910        file_obj = open(SAMPLE_IMAGE, 'rb')
1911        file_ctrl = ctrl.mech_control
1912        file_ctrl.add_file(file_obj, filename='my_birth_certificate.jpg')
1913        self.browser.getControl(
1914            name='upload_birthcertificateupload').click()
1915        self.assertTrue(
1916            '<a target="image" href="birth_certificate">Birth Certificate Scan</a>'
1917            in self.browser.contents)
1918        # Students can open clearance slip
1919        self.browser.getLink("View").click()
1920        self.browser.getLink("Download clearance slip").click()
1921        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1922        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1923        # Students can request clearance
1924        self.browser.open(self.edit_clearance_path)
1925        self.browser.getControl("Save and request clearance").click()
1926        self.browser.getControl(name="ac_series").value = self.existing_clrseries
1927        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
1928        self.browser.getControl("Request clearance now").click()
1929        self.assertMatches('...Clearance has been requested...',
1930                           self.browser.contents)
1931        # Student can't reopen clearance form after requesting clearance
1932        self.browser.open(self.student_path + '/cedit')
1933        self.assertMatches('...The requested form is locked...',
1934                           self.browser.contents)
1935
1936    def test_student_course_registration(self):
1937        # Student cant login if their password is not set
1938        IWorkflowInfo(self.student).fireTransition('admit')
1939        self.browser.open(self.login_path)
1940        self.browser.getControl(name="form.login").value = self.student_id
1941        self.browser.getControl(name="form.password").value = 'spwd'
1942        self.browser.getControl("Login").click()
1943        # Student can't add study level if not in state 'school fee paid'
1944        self.browser.open(self.student_path + '/studycourse/add')
1945        self.assertMatches('...The requested form is locked...',
1946                           self.browser.contents)
1947        # ... and must be transferred first
1948        IWorkflowState(self.student).setState('school fee paid')
1949        # Now students can add the current study level
1950        self.browser.getLink("Study Course").click()
1951        self.browser.getLink("Add course list").click()
1952        self.assertMatches('...Add current level 100 (Year 1)...',
1953                           self.browser.contents)
1954        self.browser.getControl("Create course list now").click()
1955        # A level with one course ticket was created
1956        self.assertEqual(self.student['studycourse']['100'].number_of_tickets, 1)
1957        self.browser.getLink("100").click()
1958        self.browser.getLink("Edit course list").click()
1959        self.browser.getControl("Add course ticket").click()
1960        self.browser.getControl(name="form.course").value = ['COURSE1']
1961        self.browser.getControl("Add course ticket").click()
1962        self.assertMatches('...The ticket exists...',
1963                           self.browser.contents)
1964        self.student['studycourse'].current_level = 200
1965        self.browser.getLink("Study Course").click()
1966        self.browser.getLink("Add course list").click()
1967        self.assertMatches('...Add current level 200 (Year 2)...',
1968                           self.browser.contents)
1969        self.browser.getControl("Create course list now").click()
1970        self.browser.getLink("200").click()
1971        self.browser.getLink("Edit course list").click()
1972        self.browser.getControl("Add course ticket").click()
1973        self.browser.getControl(name="form.course").value = ['COURSE1']
1974        self.course.credits = 100
1975        self.browser.getControl("Add course ticket").click()
1976        self.assertMatches(
1977            '...Your total credits exceed 58...', self.browser.contents)
1978        self.course.credits = 10
1979        self.browser.getControl("Add course ticket").click()
1980        self.assertMatches('...The ticket exists...',
1981                           self.browser.contents)
1982        # Indeed the ticket exists as carry-over course from level 100
1983        # since its score was 0
1984        self.assertTrue(
1985            self.student['studycourse']['200']['COURSE1'].carry_over is True)
1986        # Students can open the pdf course registration slip
1987        self.browser.open(self.student_path + '/studycourse/200')
1988        self.browser.getLink("Download course registration slip").click()
1989        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1990        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1991        # Students can remove course tickets
1992        self.browser.open(self.student_path + '/studycourse/200/edit')
1993        self.browser.getControl("Remove selected", index=0).click()
1994        self.assertTrue('No ticket selected' in self.browser.contents)
1995        # No ticket can be selected since the carry-over course is a core course
1996        self.assertRaises(
1997            LookupError, self.browser.getControl, name='val_id')
1998        self.student['studycourse']['200']['COURSE1'].mandatory = False
1999        self.browser.open(self.student_path + '/studycourse/200/edit')
2000        # Course list can't be registered if total_credits exceeds max_credits
2001        self.student['studycourse']['200']['COURSE1'].credits = 60
2002        self.browser.getControl("Register course list").click()
2003        self.assertTrue('Maximum credits of 50 exceeded' in self.browser.contents)
2004        # Student can now remove the ticket
2005        ctrl = self.browser.getControl(name='val_id')
2006        ctrl.getControl(value='COURSE1').selected = True
2007        self.browser.getControl("Remove selected", index=0).click()
2008        self.assertTrue('Successfully removed' in self.browser.contents)
2009        # Course list can be registered, even if it's empty
2010        self.browser.getControl("Register course list").click()
2011        self.assertTrue('Course list has been registered' in self.browser.contents)
2012        self.assertEqual(self.student.state, 'courses registered')
2013        return
2014
2015    def test_postgraduate_student_access(self):
2016        self.certificate.study_mode = 'pg_ft'
2017        self.certificate.start_level = 999
2018        self.certificate.end_level = 999
2019        self.student['studycourse'].current_level = 999
2020        IWorkflowState(self.student).setState('school fee paid')
2021        self.browser.open(self.login_path)
2022        self.browser.getControl(name="form.login").value = self.student_id
2023        self.browser.getControl(name="form.password").value = 'spwd'
2024        self.browser.getControl("Login").click()
2025        self.assertTrue(
2026            'You logged in.' in self.browser.contents)
2027        # Now students can add the current study level
2028        self.browser.getLink("Study Course").click()
2029        self.browser.getLink("Add course list").click()
2030        self.assertMatches('...Add current level Postgraduate Level...',
2031                           self.browser.contents)
2032        self.browser.getControl("Create course list now").click()
2033        # A level with one course ticket was created
2034        self.assertEqual(self.student['studycourse']['999'].number_of_tickets, 0)
2035        self.browser.getLink("999").click()
2036        self.browser.getLink("Edit course list").click()
2037        self.browser.getControl("Add course ticket").click()
2038        self.browser.getControl(name="form.course").value = ['COURSE1']
2039        self.browser.getControl("Add course ticket").click()
2040        self.assertMatches('...Successfully added COURSE1...',
2041                           self.browser.contents)
2042        # Postgraduate students can't register course lists
2043        self.browser.getControl("Register course list").click()
2044        self.assertTrue("your course list can't bee registered"
2045            in self.browser.contents)
2046        self.assertEqual(self.student.state, 'school fee paid')
2047        return
2048
2049    def test_student_clearance_wo_clrcode(self):
2050        IWorkflowState(self.student).setState('clearance started')
2051        self.browser.open(self.login_path)
2052        self.browser.getControl(name="form.login").value = self.student_id
2053        self.browser.getControl(name="form.password").value = 'spwd'
2054        self.browser.getControl("Login").click()
2055        self.student.clearance_locked = False
2056        self.browser.open(self.edit_clearance_path)
2057        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
2058        self.browser.getControl("Save and request clearance").click()
2059        self.assertMatches('...Clearance has been requested...',
2060                           self.browser.contents)
2061
2062    def test_student_clearance_payment(self):
2063        # Login
2064        self.browser.open(self.login_path)
2065        self.browser.getControl(name="form.login").value = self.student_id
2066        self.browser.getControl(name="form.password").value = 'spwd'
2067        self.browser.getControl("Login").click()
2068
2069        # Students can add online clearance payment tickets
2070        self.browser.open(self.payments_path + '/addop')
2071        self.browser.getControl(name="form.p_category").value = ['clearance']
2072        self.browser.getControl("Create ticket").click()
2073        self.assertMatches('...ticket created...',
2074                           self.browser.contents)
2075
2076        # Students can't approve the payment
2077        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
2078        ctrl = self.browser.getControl(name='val_id')
2079        value = ctrl.options[0]
2080        self.browser.getLink(value).click()
2081        payment_url = self.browser.url
2082        self.assertRaises(
2083            Unauthorized, self.browser.open, payment_url + '/approve')
2084        # In the base package they can 'use' a fake approval view
2085        self.browser.open(payment_url + '/fake_approve')
2086        self.assertMatches('...Payment approved...',
2087                          self.browser.contents)
2088        expected = '''...
2089        <td>
2090          <span>Paid</span>
2091        </td>...'''
2092        expected = '''...
2093        <td>
2094          <span>Paid</span>
2095        </td>...'''
2096        self.assertMatches(expected,self.browser.contents)
2097        payment_id = self.student['payments'].keys()[0]
2098        payment = self.student['payments'][payment_id]
2099        self.assertEqual(payment.p_state, 'paid')
2100        self.assertEqual(payment.r_amount_approved, 3456.0)
2101        self.assertEqual(payment.r_code, 'AP')
2102        self.assertEqual(payment.r_desc, u'Payment approved by Anna Tester')
2103        # The new CLR-0 pin has been created
2104        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
2105        pin = self.app['accesscodes']['CLR-0'].keys()[0]
2106        ac = self.app['accesscodes']['CLR-0'][pin]
2107        self.assertEqual(ac.owner, self.student_id)
2108        self.assertEqual(ac.cost, 3456.0)
2109
2110        # Students can open the pdf payment slip
2111        self.browser.open(payment_url + '/payment_slip.pdf')
2112        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2113        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2114
2115        # The new CLR-0 pin can be used for starting clearance
2116        # but they have to upload a passport picture first
2117        # which is only possible in state admitted
2118        self.browser.open(self.student_path + '/change_portrait')
2119        self.assertMatches('...form is locked...',
2120                          self.browser.contents)
2121        IWorkflowInfo(self.student).fireTransition('admit')
2122        self.browser.open(self.student_path + '/change_portrait')
2123        image = open(SAMPLE_IMAGE, 'rb')
2124        ctrl = self.browser.getControl(name='passportuploadedit')
2125        file_ctrl = ctrl.mech_control
2126        file_ctrl.add_file(image, filename='my_photo.jpg')
2127        self.browser.getControl(
2128            name='upload_passportuploadedit').click()
2129        self.browser.open(self.student_path + '/start_clearance')
2130        parts = pin.split('-')[1:]
2131        clrseries, clrnumber = parts
2132        self.browser.getControl(name="ac_series").value = clrseries
2133        self.browser.getControl(name="ac_number").value = clrnumber
2134        self.browser.getControl("Start clearance now").click()
2135        self.assertMatches('...Clearance process has been started...',
2136                           self.browser.contents)
2137
2138    def test_student_schoolfee_payment(self):
2139        configuration = createObject('waeup.SessionConfiguration')
2140        configuration.academic_session = 2005
2141        self.app['configuration'].addSessionConfiguration(configuration)
2142        # Login
2143        self.browser.open(self.login_path)
2144        self.browser.getControl(name="form.login").value = self.student_id
2145        self.browser.getControl(name="form.password").value = 'spwd'
2146        self.browser.getControl("Login").click()
2147
2148        # Students can add online school fee payment tickets.
2149        IWorkflowState(self.student).setState('returning')
2150        self.browser.open(self.payments_path)
2151        self.assertRaises(
2152            LookupError, self.browser.getControl, name='val_id')
2153        self.browser.getLink("Add current session payment ticket").click()
2154        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2155        self.browser.getControl("Create ticket").click()
2156        self.assertMatches('...ticket created...',
2157                           self.browser.contents)
2158        ctrl = self.browser.getControl(name='val_id')
2159        value = ctrl.options[0]
2160        self.browser.getLink(value).click()
2161        self.assertMatches('...Amount Authorized...',
2162                           self.browser.contents)
2163        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2164        # Payment session and will be calculated as defined
2165        # in w.k.students.utils because we set changed the state
2166        # to returning
2167        self.assertEqual(self.student['payments'][value].p_session, 2005)
2168        self.assertEqual(self.student['payments'][value].p_level, 200)
2169
2170        # Student is the payee of the payment ticket.
2171        webservice = IPaymentWebservice(self.student['payments'][value])
2172        self.assertEqual(webservice.display_fullname, 'Anna Tester')
2173        self.assertEqual(webservice.id, self.student_id)
2174        self.assertEqual(webservice.faculty, 'fac1')
2175        self.assertEqual(webservice.department, 'dep1')
2176
2177        # We simulate the approval
2178        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
2179        self.browser.open(self.browser.url + '/fake_approve')
2180        self.assertMatches('...Payment approved...',
2181                          self.browser.contents)
2182
2183        # The new SFE-0 pin can be used for starting new session
2184        self.browser.open(self.studycourse_path)
2185        self.browser.getLink('Start new session').click()
2186        pin = self.app['accesscodes']['SFE-0'].keys()[0]
2187        parts = pin.split('-')[1:]
2188        sfeseries, sfenumber = parts
2189        self.browser.getControl(name="ac_series").value = sfeseries
2190        self.browser.getControl(name="ac_number").value = sfenumber
2191        self.browser.getControl("Start now").click()
2192        self.assertMatches('...Session started...',
2193                           self.browser.contents)
2194        self.assertTrue(self.student.state == 'school fee paid')
2195        return
2196
2197    def test_student_bedallocation_payment(self):
2198        # Login
2199        self.browser.open(self.login_path)
2200        self.browser.getControl(name="form.login").value = self.student_id
2201        self.browser.getControl(name="form.password").value = 'spwd'
2202        self.browser.getControl("Login").click()
2203        self.browser.open(self.payments_path)
2204        self.browser.open(self.payments_path + '/addop')
2205        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
2206        self.browser.getControl("Create ticket").click()
2207        self.assertMatches('...ticket created...',
2208                           self.browser.contents)
2209        # Students can remove only online payment tickets which have
2210        # not received a valid callback
2211        self.browser.open(self.payments_path)
2212        ctrl = self.browser.getControl(name='val_id')
2213        value = ctrl.options[0]
2214        ctrl.getControl(value=value).selected = True
2215        self.browser.getControl("Remove selected", index=0).click()
2216        self.assertTrue('Successfully removed' in self.browser.contents)
2217
2218    def test_student_maintenance_payment(self):
2219        # Login
2220        self.browser.open(self.login_path)
2221        self.browser.getControl(name="form.login").value = self.student_id
2222        self.browser.getControl(name="form.password").value = 'spwd'
2223        self.browser.getControl("Login").click()
2224        self.browser.open(self.payments_path)
2225        self.browser.open(self.payments_path + '/addop')
2226        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
2227        self.browser.getControl("Create ticket").click()
2228        self.assertMatches('...You have not yet booked accommodation...',
2229                           self.browser.contents)
2230        # We continue this test in test_student_accommodation
2231
2232    def test_student_previous_payments(self):
2233        configuration = createObject('waeup.SessionConfiguration')
2234        configuration.academic_session = 2000
2235        configuration.clearance_fee = 3456.0
2236        configuration.booking_fee = 123.4
2237        self.app['configuration'].addSessionConfiguration(configuration)
2238        configuration2 = createObject('waeup.SessionConfiguration')
2239        configuration2.academic_session = 2003
2240        configuration2.clearance_fee = 3456.0
2241        configuration2.booking_fee = 123.4
2242        self.app['configuration'].addSessionConfiguration(configuration2)
2243        configuration3 = createObject('waeup.SessionConfiguration')
2244        configuration3.academic_session = 2005
2245        configuration3.clearance_fee = 3456.0
2246        configuration3.booking_fee = 123.4
2247        self.app['configuration'].addSessionConfiguration(configuration3)
2248        self.student['studycourse'].entry_session = 2002
2249
2250        # Login
2251        self.browser.open(self.login_path)
2252        self.browser.getControl(name="form.login").value = self.student_id
2253        self.browser.getControl(name="form.password").value = 'spwd'
2254        self.browser.getControl("Login").click()
2255
2256        # Students can add previous school fee payment tickets in any state.
2257        IWorkflowState(self.student).setState('courses registered')
2258        self.browser.open(self.payments_path)
2259        self.browser.getLink("Add previous session payment ticket").click()
2260
2261        # Previous session payment form is provided
2262        self.assertEqual(self.student.current_session, 2004)
2263        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2264        self.browser.getControl(name="form.p_session").value = ['2000']
2265        self.browser.getControl(name="form.p_level").value = ['300']
2266        self.browser.getControl("Create ticket").click()
2267        self.assertMatches('...The previous session must not fall below...',
2268                           self.browser.contents)
2269        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2270        self.browser.getControl(name="form.p_session").value = ['2005']
2271        self.browser.getControl(name="form.p_level").value = ['300']
2272        self.browser.getControl("Create ticket").click()
2273        self.assertMatches('...This is not a previous session...',
2274                           self.browser.contents)
2275        # Students can pay current session school fee.
2276        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2277        self.browser.getControl(name="form.p_session").value = ['2004']
2278        self.browser.getControl(name="form.p_level").value = ['300']
2279        self.browser.getControl("Create ticket").click()
2280        self.assertMatches('...ticket created...',
2281                           self.browser.contents)
2282        ctrl = self.browser.getControl(name='val_id')
2283        value = ctrl.options[0]
2284        self.browser.getLink(value).click()
2285        self.assertMatches('...Amount Authorized...',
2286                           self.browser.contents)
2287        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2288
2289        # Payment session is properly set
2290        self.assertEqual(self.student['payments'][value].p_session, 2004)
2291        self.assertEqual(self.student['payments'][value].p_level, 300)
2292
2293        # We simulate the approval
2294        self.browser.open(self.browser.url + '/fake_approve')
2295        self.assertMatches('...Payment approved...',
2296                          self.browser.contents)
2297
2298        # No AC has been created
2299        self.assertEqual(len(self.app['accesscodes']['SFE-0'].keys()), 0)
2300        self.assertTrue(self.student['payments'][value].ac is None)
2301
2302        # Current payment flag is set False
2303        self.assertFalse(self.student['payments'][value].p_current)
2304
2305        # Button and form are not available for students who are in
2306        # states up to cleared
2307        self.student['studycourse'].entry_session = 2004
2308        IWorkflowState(self.student).setState('cleared')
2309        self.browser.open(self.payments_path)
2310        self.assertFalse(
2311            "Add previous session payment ticket" in self.browser.contents)
2312        self.browser.open(self.payments_path + '/addpp')
2313        self.assertTrue(
2314            "No previous payment to be made" in self.browser.contents)
2315        return
2316
2317    def test_postgraduate_student_payments(self):
2318        configuration = createObject('waeup.SessionConfiguration')
2319        configuration.academic_session = 2005
2320        self.app['configuration'].addSessionConfiguration(configuration)
2321        self.certificate.study_mode = 'pg_ft'
2322        self.certificate.start_level = 999
2323        self.certificate.end_level = 999
2324        self.student['studycourse'].current_level = 999
2325        # Login
2326        self.browser.open(self.login_path)
2327        self.browser.getControl(name="form.login").value = self.student_id
2328        self.browser.getControl(name="form.password").value = 'spwd'
2329        self.browser.getControl("Login").click()
2330        # Students can add online school fee payment tickets.
2331        IWorkflowState(self.student).setState('cleared')
2332        self.browser.open(self.payments_path)
2333        self.browser.getLink("Add current session payment ticket").click()
2334        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2335        self.browser.getControl("Create ticket").click()
2336        self.assertMatches('...ticket created...',
2337                           self.browser.contents)
2338        ctrl = self.browser.getControl(name='val_id')
2339        value = ctrl.options[0]
2340        self.browser.getLink(value).click()
2341        self.assertMatches('...Amount Authorized...',
2342                           self.browser.contents)
2343        # Payment session and level are current ones.
2344        # Postgrads have to pay school_fee_1.
2345        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
2346        self.assertEqual(self.student['payments'][value].p_session, 2004)
2347        self.assertEqual(self.student['payments'][value].p_level, 999)
2348
2349        # We simulate the approval
2350        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
2351        self.browser.open(self.browser.url + '/fake_approve')
2352        self.assertMatches('...Payment approved...',
2353                          self.browser.contents)
2354
2355        # The new SFE-0 pin can be used for starting session
2356        self.browser.open(self.studycourse_path)
2357        self.browser.getLink('Start new session').click()
2358        pin = self.app['accesscodes']['SFE-0'].keys()[0]
2359        parts = pin.split('-')[1:]
2360        sfeseries, sfenumber = parts
2361        self.browser.getControl(name="ac_series").value = sfeseries
2362        self.browser.getControl(name="ac_number").value = sfenumber
2363        self.browser.getControl("Start now").click()
2364        self.assertMatches('...Session started...',
2365                           self.browser.contents)
2366        self.assertTrue(self.student.state == 'school fee paid')
2367
2368        # Postgrad students do not need to register courses the
2369        # can just pay for the next session.
2370        self.browser.open(self.payments_path)
2371        # Remove first payment to be sure that we access the right ticket
2372        del self.student['payments'][value]
2373        self.browser.getLink("Add current session payment ticket").click()
2374        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2375        self.browser.getControl("Create ticket").click()
2376        ctrl = self.browser.getControl(name='val_id')
2377        value = ctrl.options[0]
2378        self.browser.getLink(value).click()
2379        # Payment session has increased by one, payment level remains the same.
2380        # Returning Postgraduates have to pay school_fee_2.
2381        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2382        self.assertEqual(self.student['payments'][value].p_session, 2005)
2383        self.assertEqual(self.student['payments'][value].p_level, 999)
2384
2385        # Student is still in old session
2386        self.assertEqual(self.student.current_session, 2004)
2387
2388        # We do not need to pay the ticket if any other
2389        # SFE pin is provided
2390        pin_container = self.app['accesscodes']
2391        pin_container.createBatch(
2392            datetime.utcnow(), 'some_userid', 'SFE', 9.99, 1)
2393        pin = pin_container['SFE-1'].values()[0].representation
2394        sfeseries, sfenumber = pin.split('-')[1:]
2395        # The new SFE-1 pin can be used for starting new session
2396        self.browser.open(self.studycourse_path)
2397        self.browser.getLink('Start new session').click()
2398        self.browser.getControl(name="ac_series").value = sfeseries
2399        self.browser.getControl(name="ac_number").value = sfenumber
2400        self.browser.getControl("Start now").click()
2401        self.assertMatches('...Session started...',
2402                           self.browser.contents)
2403        self.assertTrue(self.student.state == 'school fee paid')
2404        # Student is in new session
2405        self.assertEqual(self.student.current_session, 2005)
2406        self.assertEqual(self.student['studycourse'].current_level, 999)
2407        return
2408
2409    def test_student_accommodation(self):
2410        # Login
2411        self.browser.open(self.login_path)
2412        self.browser.getControl(name="form.login").value = self.student_id
2413        self.browser.getControl(name="form.password").value = 'spwd'
2414        self.browser.getControl("Login").click()
2415
2416        # Students can add online booking fee payment tickets and open the
2417        # callback view (see test_manage_payments)
2418        self.browser.getLink("Payments").click()
2419        self.browser.getLink("Add current session payment ticket").click()
2420        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
2421        self.browser.getControl("Create ticket").click()
2422        ctrl = self.browser.getControl(name='val_id')
2423        value = ctrl.options[0]
2424        self.browser.getLink(value).click()
2425        self.browser.open(self.browser.url + '/fake_approve')
2426        # The new HOS-0 pin has been created
2427        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
2428        pin = self.app['accesscodes']['HOS-0'].keys()[0]
2429        ac = self.app['accesscodes']['HOS-0'][pin]
2430        parts = pin.split('-')[1:]
2431        sfeseries, sfenumber = parts
2432
2433        # Students can use HOS code and book a bed space with it ...
2434        self.browser.open(self.acco_path)
2435        # ... but not if booking period has expired ...
2436        self.app['hostels'].enddate = datetime.now(pytz.utc)
2437        self.browser.getLink("Book accommodation").click()
2438        self.assertMatches('...Outside booking period: ...',
2439                           self.browser.contents)
2440        self.app['hostels'].enddate = datetime.now(pytz.utc) + timedelta(days=10)
2441        # ... or student is not the an allowed state ...
2442        self.browser.getLink("Book accommodation").click()
2443        self.assertMatches('...You are in the wrong...',
2444                           self.browser.contents)
2445        IWorkflowInfo(self.student).fireTransition('admit')
2446        self.browser.getLink("Book accommodation").click()
2447        self.assertMatches('...Activation Code:...',
2448                           self.browser.contents)
2449        # Student can't used faked ACs ...
2450        self.browser.getControl(name="ac_series").value = u'nonsense'
2451        self.browser.getControl(name="ac_number").value = sfenumber
2452        self.browser.getControl("Create bed ticket").click()
2453        self.assertMatches('...Activation code is invalid...',
2454                           self.browser.contents)
2455        # ... or ACs owned by somebody else.
2456        ac.owner = u'Anybody'
2457        self.browser.getControl(name="ac_series").value = sfeseries
2458        self.browser.getControl(name="ac_number").value = sfenumber
2459        self.browser.getControl("Create bed ticket").click()
2460        self.assertMatches('...You are not the owner of this access code...',
2461                           self.browser.contents)
2462        ac.owner = self.student_id
2463        self.browser.getControl(name="ac_series").value = sfeseries
2464        self.browser.getControl(name="ac_number").value = sfenumber
2465        self.browser.getControl("Create bed ticket").click()
2466        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
2467                           self.browser.contents)
2468
2469        # Bed has been allocated
2470        bed = self.app['hostels']['hall-1']['hall-1_A_101_A']
2471        self.assertTrue(bed.owner == self.student_id)
2472
2473        # BedTicketAddPage is now blocked
2474        self.browser.getLink("Book accommodation").click()
2475        self.assertMatches('...You already booked a bed space...',
2476            self.browser.contents)
2477
2478        # The bed ticket displays the data correctly
2479        self.browser.open(self.acco_path + '/2004')
2480        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
2481                           self.browser.contents)
2482        self.assertMatches('...2004/2005...', self.browser.contents)
2483        self.assertMatches('...regular_male_fr...', self.browser.contents)
2484        self.assertMatches('...%s...' % pin, self.browser.contents)
2485
2486        # Students can open the pdf slip
2487        self.browser.open(self.browser.url + '/bed_allocation_slip.pdf')
2488        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2489        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2490
2491        # Students can't relocate themselves
2492        self.assertFalse('Relocate' in self.browser.contents)
2493        relocate_path = self.acco_path + '/2004/relocate'
2494        self.assertRaises(
2495            Unauthorized, self.browser.open, relocate_path)
2496
2497        # Students can't the Remove button and check boxes
2498        self.browser.open(self.acco_path)
2499        self.assertFalse('Remove' in self.browser.contents)
2500        self.assertFalse('val_id' in self.browser.contents)
2501
2502        # Students can pay maintenance fee now
2503        self.browser.open(self.payments_path)
2504        self.browser.open(self.payments_path + '/addop')
2505        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
2506        self.browser.getControl("Create ticket").click()
2507        self.assertMatches('...Payment ticket created...',
2508                           self.browser.contents)
2509        return
2510
2511    def test_change_password_request(self):
2512        self.browser.open('http://localhost/app/changepw')
2513        self.browser.getControl(name="form.identifier").value = '123'
2514        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
2515        self.browser.getControl("Send login credentials").click()
2516        self.assertTrue('An email with' in self.browser.contents)
2517
2518    def test_student_expired_personal_data(self):
2519        # Login
2520        IWorkflowState(self.student).setState('school fee paid')
2521        delta = timedelta(days=180)
2522        self.student.personal_updated = datetime.utcnow() - delta
2523        self.browser.open(self.login_path)
2524        self.browser.getControl(name="form.login").value = self.student_id
2525        self.browser.getControl(name="form.password").value = 'spwd'
2526        self.browser.getControl("Login").click()
2527        self.assertEqual(self.browser.url, self.student_path)
2528        self.assertTrue(
2529            'You logged in' in self.browser.contents)
2530        # Students don't see personal_updated field in edit form
2531        self.browser.open(self.edit_personal_path)
2532        self.assertFalse('Updated' in self.browser.contents)
2533        self.browser.open(self.personal_path)
2534        self.assertTrue('Updated' in self.browser.contents)
2535        self.browser.getLink("Logout").click()
2536        delta = timedelta(days=181)
2537        self.student.personal_updated = datetime.utcnow() - delta
2538        self.browser.open(self.login_path)
2539        self.browser.getControl(name="form.login").value = self.student_id
2540        self.browser.getControl(name="form.password").value = 'spwd'
2541        self.browser.getControl("Login").click()
2542        self.assertEqual(self.browser.url, self.edit_personal_path)
2543        self.assertTrue(
2544            'Your personal data record is outdated.' in self.browser.contents)
2545
2546class StudentRequestPWTests(StudentsFullSetup):
2547    # Tests for student registration
2548
2549    layer = FunctionalLayer
2550
2551    def test_request_pw(self):
2552        # Student with wrong number can't be found.
2553        self.browser.open('http://localhost/app/requestpw')
2554        self.browser.getControl(name="form.firstname").value = 'Anna'
2555        self.browser.getControl(name="form.number").value = 'anynumber'
2556        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
2557        self.browser.getControl("Send login credentials").click()
2558        self.assertTrue('No student record found.'
2559            in self.browser.contents)
2560        # Anonymous is not informed that firstname verification failed.
2561        # It seems that the record doesn't exist.
2562        self.browser.open('http://localhost/app/requestpw')
2563        self.browser.getControl(name="form.firstname").value = 'Johnny'
2564        self.browser.getControl(name="form.number").value = '123'
2565        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
2566        self.browser.getControl("Send login credentials").click()
2567        self.assertTrue('No student record found.'
2568            in self.browser.contents)
2569        # Even with the correct firstname we can't register if a
2570        # password has been set and used.
2571        self.browser.getControl(name="form.firstname").value = 'Anna'
2572        self.browser.getControl(name="form.number").value = '123'
2573        self.browser.getControl("Send login credentials").click()
2574        self.assertTrue('Your password has already been set and used.'
2575            in self.browser.contents)
2576        self.browser.open('http://localhost/app/requestpw')
2577        self.app['students'][self.student_id].password = None
2578        # The firstname field, used for verification, is not case-sensitive.
2579        self.browser.getControl(name="form.firstname").value = 'aNNa'
2580        self.browser.getControl(name="form.number").value = '123'
2581        self.browser.getControl(name="form.email").value = 'new@yy.zz'
2582        self.browser.getControl("Send login credentials").click()
2583        # Yeah, we succeded ...
2584        self.assertTrue('Your password request was successful.'
2585            in self.browser.contents)
2586        # We can also use the matric_number instead.
2587        self.browser.open('http://localhost/app/requestpw')
2588        self.browser.getControl(name="form.firstname").value = 'aNNa'
2589        self.browser.getControl(name="form.number").value = '234'
2590        self.browser.getControl(name="form.email").value = 'new@yy.zz'
2591        self.browser.getControl("Send login credentials").click()
2592        self.assertTrue('Your password request was successful.'
2593            in self.browser.contents)
2594        # ... and  student can be found in the catalog via the email address
2595        cat = queryUtility(ICatalog, name='students_catalog')
2596        results = list(
2597            cat.searchResults(
2598            email=('new@yy.zz', 'new@yy.zz')))
2599        self.assertEqual(self.student,results[0])
2600        logfile = os.path.join(
2601            self.app['datacenter'].storage, 'logs', 'main.log')
2602        logcontent = open(logfile).read()
2603        self.assertTrue('zope.anybody - students.browser.StudentRequestPasswordPage - '
2604                        '234 (K1000000) - new@yy.zz' in logcontent)
2605        return
2606
2607    def test_student_locked_level_forms(self):
2608
2609        # Add two study levels, one current and one previous
2610        studylevel = createObject(u'waeup.StudentStudyLevel')
2611        studylevel.level = 100
2612        self.student['studycourse'].addStudentStudyLevel(
2613            self.certificate, studylevel)
2614        studylevel = createObject(u'waeup.StudentStudyLevel')
2615        studylevel.level = 200
2616        self.student['studycourse'].addStudentStudyLevel(
2617            self.certificate, studylevel)
2618        IWorkflowState(self.student).setState('school fee paid')
2619        self.student['studycourse'].current_level = 200
2620
2621        self.browser.open(self.login_path)
2622        self.browser.getControl(name="form.login").value = self.student_id
2623        self.browser.getControl(name="form.password").value = 'spwd'
2624        self.browser.getControl("Login").click()
2625
2626        self.browser.open(self.student_path + '/studycourse/200/edit')
2627        self.assertFalse('The requested form is locked' in self.browser.contents)
2628        self.browser.open(self.student_path + '/studycourse/100/edit')
2629        self.assertTrue('The requested form is locked' in self.browser.contents)
2630
2631        self.browser.open(self.student_path + '/studycourse/200/ctadd')
2632        self.assertFalse('The requested form is locked' in self.browser.contents)
2633        self.browser.open(self.student_path + '/studycourse/100/ctadd')
2634        self.assertTrue('The requested form is locked' in self.browser.contents)
2635
2636        IWorkflowState(self.student).setState('courses registered')
2637        self.browser.open(self.student_path + '/studycourse/200/edit')
2638        self.assertTrue('The requested form is locked' in self.browser.contents)
2639        self.browser.open(self.student_path + '/studycourse/200/ctadd')
2640        self.assertTrue('The requested form is locked' in self.browser.contents)
2641
2642
2643class PublicPagesTests(StudentsFullSetup):
2644    # Tests for swebservices
2645
2646    layer = FunctionalLayer
2647
2648    def test_paymentrequest(self):
2649        payment = createObject('waeup.StudentOnlinePayment')
2650        payment.p_category = u'schoolfee'
2651        payment.p_session = self.student.current_session
2652        payment.p_item = u'My Certificate'
2653        payment.p_id = u'anyid'
2654        self.student['payments']['anykey'] = payment
2655        # Request information about unpaid payment ticket
2656        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
2657        self.assertEqual(self.browser.contents, '-1')
2658        # Request information about paid payment ticket
2659        payment.p_state = u'paid'
2660        notify(grok.ObjectModifiedEvent(payment))
2661        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
2662        self.assertEqual(self.browser.contents,
2663            'FULL_NAME=Anna Tester&FACULTY=fac1&DEPARTMENT=dep1'
2664            '&PAYMENT_ITEM=My Certificate&PAYMENT_CATEGORY=School Fee'
2665            '&ACADEMIC_SESSION=2004/2005&MATRIC_NUMBER=234&FEE_AMOUNT=0.0')
2666        self.browser.open('http://localhost/app/paymentrequest?NONSENSE=nonsense')
2667        self.assertEqual(self.browser.contents, '-1')
2668        self.browser.open('http://localhost/app/paymentrequest?P_ID=nonsense')
2669        self.assertEqual(self.browser.contents, '-1')
Note: See TracBrowser for help on using the repository browser.