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

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

Split student access test.

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