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

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

Resort tests.

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