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

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

Different error strings should lead to a redirection to the PreviousPaymentAddFormPage?.

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