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

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

Reorganize ICourseTicket. Add ICourseTicketImport which validates a new field called level_session.

Customize checkConversion of CourseTicketProcessor?:
If level_session is provided in row the importer checks if
the parent studylevel exists and if its level_session
attribute corresponds with the expected value in row. The error message
then tells us why course result import fails.

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