source: main/waeup.sirp/trunk/src/waeup/sirp/students/tests/test_browser.py @ 7057

Last change on this file since 7057 was 7057, checked in by Henrik Bettermann, 13 years ago

Managers can relocate students if the bed's bed_type has changed.

Also fix assertion. Then we see that handle_bedticket_removed event handler does not work properly.

  • Property svn:keywords set to Id
File size: 52.6 KB
Line 
1##
2## test_browser.py
3## Login : <uli@pu.smp.net>
4## Started on  Tue Mar 29 11:31:11 2011 Uli Fouquet
5## $Id: test_browser.py 7057 2011-11-09 12:34:48Z henrik $
6##
7## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
8## This program is free software; you can redistribute it and/or modify
9## it under the terms of the GNU General Public License as published by
10## the Free Software Foundation; either version 2 of the License, or
11## (at your option) any later version.
12##
13## This program is distributed in the hope that it will be useful,
14## but WITHOUT ANY WARRANTY; without even the implied warranty of
15## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16## GNU General Public License for more details.
17##
18## You should have received a copy of the GNU General Public License
19## along with this program; if not, write to the Free Software
20## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21##
22"""
23Test the student-related UI components.
24"""
25import shutil
26import tempfile
27import cStringIO
28from datetime import datetime
29import grok
30from zope.event import notify
31from zope.component import createObject
32from zope.component.hooks import setSite, clearSite
33from zope.security.interfaces import Unauthorized
34from zope.testbrowser.testing import Browser
35from hurry.workflow.interfaces import IWorkflowInfo
36from waeup.sirp.testing import FunctionalLayer, FunctionalTestCase
37from waeup.sirp.app import University
38from waeup.sirp.configuration import SessionConfiguration
39from waeup.sirp.students.student import Student
40from waeup.sirp.university.faculty import Faculty
41from waeup.sirp.university.department import Department
42from waeup.sirp.interfaces import IUserAccount
43from waeup.sirp.hostels.hostel import Hostel, Bed, NOT_OCCUPIED
44
45PH_LEN = 2059  # Length of placeholder file
46
47def lookup_submit_value(name, value, browser):
48    """Find a button with a certain value."""
49    for num in range(0, 100):
50        try:
51            button = browser.getControl(name=name, index=num)
52            if button.value.endswith(value):
53                return button
54        except IndexError:
55            break
56    return None
57
58class StudentsFullSetup(FunctionalTestCase):
59    # A test case that only contains a setup and teardown
60    #
61    # Complete setup for students handlings is rather complex and
62    # requires lots of things created before we can start. This is a
63    # setup that does all this, creates a university, creates PINs,
64    # etc.  so that we do not have to bother with that in different
65    # test cases.
66
67    layer = FunctionalLayer
68
69    def setUp(self):
70        super(StudentsFullSetup, self).setUp()
71
72        # Setup a sample site for each test
73        app = University()
74        self.dc_root = tempfile.mkdtemp()
75        app['datacenter'].setStoragePath(self.dc_root)
76
77        # Prepopulate the ZODB...
78        self.getRootFolder()['app'] = app
79        # we add the site immediately after creation to the
80        # ZODB. Catalogs and other local utilities are not setup
81        # before that step.
82        self.app = self.getRootFolder()['app']
83        # Set site here. Some of the following setup code might need
84        # to access grok.getSite() and should get our new app then
85        setSite(app)
86
87        # Add student with subobjects
88        student = Student()
89        student.fullname = u'Anna Tester'
90        student.reg_number = u'123'
91        student.matric_number = u'234'
92        student.sex = u'm'
93        self.app['students'].addStudent(student)
94        self.student_id = student.student_id
95        self.student = self.app['students'][self.student_id]
96
97        # Set password
98        IUserAccount(
99            self.app['students'][self.student_id]).setPassword('spwd')
100
101        self.login_path = 'http://localhost/app/login'
102        self.container_path = 'http://localhost/app/students'
103        self.manage_container_path = self.container_path + '/@@manage'
104        self.add_student_path = self.container_path + '/addstudent'
105        self.student_path = self.container_path + '/' + self.student_id
106        self.manage_student_path = self.student_path + '/edit_base'
107        self.clearance_student_path = self.student_path + '/view_clearance'
108        self.personal_student_path = self.student_path + '/view_personal'
109        self.edit_clearance_student_path = self.student_path + '/edit_clearance'
110        self.edit_personal_student_path = self.student_path + '/edit_personal'
111        self.studycourse_student_path = self.student_path + '/studycourse'
112        self.payments_student_path = self.student_path + '/payments'
113        self.acco_student_path = self.student_path + '/accommodation'
114        self.history_student_path = self.student_path + '/history'
115
116        # Create 5 access codes with prefix'PWD'
117        pin_container = self.app['accesscodes']
118        pin_container.createBatch(
119            datetime.now(), 'some_userid', 'PWD', 9.99, 5)
120        pins = pin_container['PWD-1'].values()
121        self.pwdpins = [x.representation for x in pins]
122        self.existing_pwdpin = self.pwdpins[0]
123        parts = self.existing_pwdpin.split('-')[1:]
124        self.existing_pwdseries, self.existing_pwdnumber = parts
125        # Create 5 access codes with prefix 'CLR'
126        pin_container.createBatch(
127            datetime.now(), 'some_userid', 'CLR', 9.99, 5)
128        pins = pin_container['CLR-1'].values()
129        pins[0].owner = u'Hans Wurst'
130        self.existing_clrac = pins[0]
131        self.existing_clrpin = pins[0].representation
132        parts = self.existing_clrpin.split('-')[1:]
133        self.existing_clrseries, self.existing_clrnumber = parts
134
135        # Populate university
136        self.certificate = createObject('waeup.Certificate')
137        self.certificate.code = u'CERT1'
138        self.certificate.application_category = 'basic'
139        self.certificate.study_mode = 'ug_ft'
140        self.certificate.start_level = 100
141        self.certificate.end_level = 500
142        self.app['faculties']['fac1'] = Faculty()
143        self.app['faculties']['fac1']['dep1'] = Department()
144        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
145            self.certificate)
146        self.course = createObject('waeup.Course')
147        self.course.code = 'COURSE1'
148        self.course.semester = 1
149        self.course.credits = 10
150        self.course.passmark = 40
151        self.app['faculties']['fac1']['dep1'].courses.addCourse(
152            self.course)
153        self.app['faculties']['fac1']['dep1'].certificates['CERT1'].addCourseRef(
154            self.course, level=100)
155
156        # Configure university
157        self.app['configuration'].accommodation_states = ['admitted']
158        self.app['configuration'].accommodation_session = 2004
159        configuration = SessionConfiguration()
160        # These attributes must also exist in the customization packages.
161        configuration.academic_session = 2004
162        configuration.fee_1 = 20000
163        configuration.boocking_fee = 500
164        self.app['configuration'].addSessionConfiguration(configuration)
165
166        # Create a hostel with two beds
167        hostel = Hostel()
168        hostel.hostel_id = u'hall-1'
169        hostel.hostel_name = u'Hall 1'
170        self.app['hostels'].addHostel(hostel)
171        bed = Bed()
172        bed.bed_id = u'hall-1_A_101_A'
173        bed.bed_number = 1
174        bed.owner = NOT_OCCUPIED
175        bed.bed_type = u'regular_male_fr'
176        self.app['hostels'][hostel.hostel_id].addBed(bed)
177        bed = Bed()
178        bed.bed_id = u'hall-1_A_101_B'
179        bed.bed_number = 2
180        bed.owner = NOT_OCCUPIED
181        bed.bed_type = u'regular_female_fr'
182        self.app['hostels'][hostel.hostel_id].addBed(bed)
183
184        # Set study course attributes of test student
185        self.student['studycourse'].certificate = self.certificate
186        self.student['studycourse'].current_session = 2004
187        self.student['studycourse'].entry_session = 2004
188        self.student['studycourse'].current_verdict = 'A'
189        self.student['studycourse'].current_level = 100
190
191        # Put the prepopulated site into test ZODB and prepare test
192        # browser
193        self.browser = Browser()
194        self.browser.handleErrors = False
195
196    def tearDown(self):
197        super(StudentsFullSetup, self).tearDown()
198        clearSite()
199        shutil.rmtree(self.dc_root)
200
201
202
203class StudentsContainerUITests(StudentsFullSetup):
204    # Tests for StudentsContainer class views and pages
205
206    layer = FunctionalLayer
207
208    def test_anonymous_access(self):
209        # Anonymous users can't access students containers
210        self.assertRaises(
211            Unauthorized, self.browser.open, self.container_path)
212        self.assertRaises(
213            Unauthorized, self.browser.open, self.manage_container_path)
214        return
215
216    def test_manage_access(self):
217        # Managers can access the view page of students
218        # containers and can perform actions
219        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
220        self.browser.open(self.container_path)
221        self.assertEqual(self.browser.headers['Status'], '200 Ok')
222        self.assertEqual(self.browser.url, self.container_path)
223        self.browser.getLink("Manage student section").click()
224        self.assertEqual(self.browser.headers['Status'], '200 Ok')
225        self.assertEqual(self.browser.url, self.manage_container_path)
226        return
227
228    def test_add_search_delete_students(self):
229        # Managers can add search and remove students
230        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
231        self.browser.open(self.manage_container_path)
232        self.browser.getLink("Add student").click()
233        self.assertEqual(self.browser.headers['Status'], '200 Ok')
234        self.assertEqual(self.browser.url, self.add_student_path)
235        self.browser.getControl(name="form.fullname").value = 'Bob Tester'
236        self.browser.getControl("Create student record").click()
237        self.assertTrue('Student record created' in self.browser.contents)
238
239        # Registration and matric numbers must be unique
240        self.browser.getLink("Manage").click()
241        self.browser.getControl(name="form.reg_number").value = '123'
242        self.browser.getControl("Save").click()
243        self.assertMatches('...Registration number exists...',
244                           self.browser.contents)
245        self.browser.getControl(name="form.reg_number").value = '789'
246        self.browser.getControl(name="form.matric_number").value = '234'
247        self.browser.getControl("Save").click()
248        self.assertMatches('...Matriculation number exists...',
249                           self.browser.contents)
250
251        self.browser.open(self.container_path)
252        self.browser.getControl("Search").click()
253        self.assertTrue('Empty search string' in self.browser.contents)
254        self.browser.getControl(name="searchtype").value = ['student_id']
255        self.browser.getControl(name="searchterm").value = self.student_id
256        self.browser.getControl("Search").click()
257        self.assertTrue('Anna Tester' in self.browser.contents)
258
259        self.browser.open(self.manage_container_path)
260        self.browser.getControl("Search").click()
261        self.assertTrue('Empty search string' in self.browser.contents)
262        self.browser.getControl(name="searchtype").value = ['fullname']
263        self.browser.getControl(name="searchterm").value = 'Anna Tester'
264        self.browser.getControl("Search").click()
265        self.assertTrue('Anna Tester' in self.browser.contents)
266        # The old searchterm will be used again
267        self.browser.getControl("Search").click()
268        self.assertTrue('Anna Tester' in self.browser.contents)
269
270        ctrl = self.browser.getControl(name='entries')
271        ctrl.getControl(value=self.student_id).selected = True
272        self.browser.getControl("Remove selected", index=0).click()
273        self.assertTrue('Successfully removed' in self.browser.contents)
274        self.browser.getControl(name="searchtype").value = ['student_id']
275        self.browser.getControl(name="searchterm").value = self.student_id
276        self.browser.getControl("Search").click()
277        self.assertTrue('No student found' in self.browser.contents)
278
279        self.browser.open(self.container_path)
280        self.browser.getControl(name="searchtype").value = ['student_id']
281        self.browser.getControl(name="searchterm").value = self.student_id
282        self.browser.getControl("Search").click()
283        self.assertTrue('No student found' in self.browser.contents)
284        return
285
286class StudentUITests(StudentsFullSetup):
287    # Tests for Student class views and pages
288
289    layer = FunctionalLayer
290
291    def test_manage_access(self):
292        # Managers can access the pages of students
293        # and can perform actions
294        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
295
296        self.browser.open(self.student_path)
297        self.assertEqual(self.browser.headers['Status'], '200 Ok')
298        self.assertEqual(self.browser.url, self.student_path)
299        self.browser.getLink("Manage").click()
300        self.assertEqual(self.browser.headers['Status'], '200 Ok')
301        self.assertEqual(self.browser.url, self.manage_student_path)
302        # Managers can edit base data and fire transitions
303        self.browser.getControl(name="transition").value = ['admit']
304        self.browser.getControl(name="form.fullname").value = 'John Tester'
305        self.browser.getControl(name="form.reg_number").value = '345'
306        self.browser.getControl(name="password").value = 'secret'
307        self.browser.getControl(name="control_password").value = 'secret'
308        self.browser.getControl("Save").click()
309        self.assertMatches('...Form has been saved...',
310                           self.browser.contents)
311        self.browser.open(self.student_path)
312        self.browser.getLink("Clearance Data").click()
313        self.assertEqual(self.browser.headers['Status'], '200 Ok')
314        self.assertEqual(self.browser.url, self.clearance_student_path)
315        self.browser.getLink("Manage").click()
316        self.assertEqual(self.browser.headers['Status'], '200 Ok')
317        self.assertEqual(self.browser.url, self.edit_clearance_student_path)
318        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
319        self.browser.getControl("Save").click()
320        self.assertMatches('...Form has been saved...',
321                           self.browser.contents)
322
323        self.browser.open(self.student_path)
324        self.browser.getLink("Personal Data").click()
325        self.assertEqual(self.browser.headers['Status'], '200 Ok')
326        self.assertEqual(self.browser.url, self.personal_student_path)
327        self.browser.getLink("Manage").click()
328        self.assertEqual(self.browser.headers['Status'], '200 Ok')
329        self.assertEqual(self.browser.url, self.edit_personal_student_path)
330        self.browser.getControl("Save").click()
331        self.assertMatches('...Form has been saved...',
332                           self.browser.contents)
333
334        # Managers can browse all subobjects
335        self.browser.open(self.student_path)
336        self.browser.getLink("Payments").click()
337        self.assertEqual(self.browser.headers['Status'], '200 Ok')
338        self.assertEqual(self.browser.url, self.payments_student_path)
339        self.browser.open(self.student_path)
340        self.browser.getLink("Accommodation").click()
341        self.assertEqual(self.browser.headers['Status'], '200 Ok')
342        self.assertEqual(self.browser.url, self.acco_student_path)
343        self.browser.open(self.student_path)
344        self.browser.getLink("History").click()
345        self.assertEqual(self.browser.headers['Status'], '200 Ok')
346        self.assertEqual(self.browser.url, self.history_student_path)
347        self.assertMatches('...Student admitted by zope.mgr...',
348                           self.browser.contents)
349        return
350
351    def test_manage_course_lists(self):
352        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
353        self.browser.open(self.student_path)
354        self.browser.getLink("Study Course").click()
355        self.assertEqual(self.browser.headers['Status'], '200 Ok')
356        self.assertEqual(self.browser.url, self.studycourse_student_path)
357        self.browser.getLink("Manage").click()
358        self.assertTrue('Manage study course' in self.browser.contents)
359        # Before we can select a level, the certificate must be selected and saved
360        self.browser.getControl(name="form.certificate").value = ['CERT1']
361        self.browser.getControl(name="form.current_session").value = ['2004']
362        self.browser.getControl(name="form.current_verdict").value = ['A']
363        self.browser.getControl("Save").click()
364        # Now we can save also the current level which depends on start and end
365        # level of the certificate
366        self.browser.getControl(name="form.current_level").value = ['100']
367        self.browser.getControl("Save").click()
368        # Managers can add and remove any study level (course list)
369        self.browser.getControl(name="addlevel").value = ['100']
370        self.browser.getControl("Add study level").click()
371        self.assertMatches('...<span>100</span>...', self.browser.contents)
372        self.browser.getControl("Add study level").click()
373        self.assertMatches('...This level exists...', self.browser.contents)
374        self.browser.getControl("Remove selected").click()
375        self.assertMatches('...No study level selected...', self.browser.contents)
376        self.browser.getControl(name="val_id").value = ['100']
377        self.browser.getControl("Remove selected").click()
378        self.assertMatches('...Successfully removed...', self.browser.contents)
379        # Add level again
380        self.browser.getControl(name="addlevel").value = ['100']
381        self.browser.getControl("Add study level").click()
382        self.browser.getControl(name="addlevel").value = ['100']
383
384        # Managers can view and manage course lists
385        self.browser.getLink("100").click()
386        self.assertMatches('...: Study Level 100 (Year 1)...', self.browser.contents)
387        self.browser.getLink("Manage").click()
388        self.browser.getControl(name="form.level_session").value = ['2002']
389        self.browser.getControl("Save").click()
390        self.browser.getControl("Remove selected").click()
391        self.assertMatches('...No ticket selected...', self.browser.contents)
392        ctrl = self.browser.getControl(name='val_id')
393        ctrl.getControl(value='COURSE1').selected = True
394        self.browser.getControl("Remove selected", index=0).click()
395        self.assertTrue('Successfully removed' in self.browser.contents)
396        self.browser.getControl("Add course ticket").click()
397        self.browser.getControl(name="form.course").value = ['COURSE1']
398        self.browser.getControl("Add course ticket").click()
399        self.assertTrue('Successfully added' in self.browser.contents)
400        self.browser.getControl("Add course ticket").click()
401        self.browser.getControl(name="form.course").value = ['COURSE1']
402        self.browser.getControl("Add course ticket").click()
403        self.assertTrue('The ticket exists' in self.browser.contents)
404        self.browser.getControl("Cancel").click()
405        self.browser.getLink("COURSE1").click()
406        self.browser.getLink("Manage").click()
407        self.browser.getControl(name="form.score").value = '10'
408        self.browser.getControl("Save").click()
409        self.assertTrue('Form has been saved' in self.browser.contents)
410        return
411
412    def test_manage_workflow(self):
413        # Managers can pass through the whole workflow
414        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
415        student = self.app['students'][self.student_id]
416        self.browser.open(self.manage_student_path)
417        self.assertTrue(student.clearance_locked)
418        self.browser.getControl(name="transition").value = ['admit']
419        self.browser.getControl("Save").click()
420        self.assertTrue(student.clearance_locked)
421        self.browser.getControl(name="transition").value = ['start_clearance']
422        self.browser.getControl("Save").click()
423        self.assertFalse(student.clearance_locked)
424        self.browser.getControl(name="transition").value = ['request_clearance']
425        self.browser.getControl("Save").click()
426        self.assertTrue(student.clearance_locked)
427        self.browser.getControl(name="transition").value = ['clear']
428        self.browser.getControl("Save").click()
429        self.browser.getControl(name="transition").value = ['pay_first_school_fee']
430        self.browser.getControl("Save").click()
431        self.browser.getControl(name="transition").value = ['reset6']
432        self.browser.getControl("Save").click()
433        # In state returning the pay_school_fee transition triggers some
434        # changes of attributes
435        self.browser.getControl(name="transition").value = ['pay_school_fee']
436        self.browser.getControl("Save").click()
437        self.assertEqual(student['studycourse'].current_session, 2005) # +1
438        self.assertEqual(student['studycourse'].current_level, 200) # +100
439        self.assertEqual(student['studycourse'].current_verdict, '0') # 0 = not set
440        self.assertEqual(student['studycourse'].previous_verdict, 'A')
441        self.browser.getControl(name="transition").value = ['register_courses']
442        self.browser.getControl("Save").click()
443        self.browser.getControl(name="transition").value = ['validate_courses']
444        self.browser.getControl("Save").click()
445        self.browser.getControl(name="transition").value = ['return']
446        self.browser.getControl("Save").click()
447        return
448
449    def test_manage_import(self):
450        # Managers can import student data files
451        datacenter_path = 'http://localhost/app/datacenter'
452        # Prepare a csv file for students
453        open('students.csv', 'wb').write(
454"""firstname,lastname,fullname,reg_number,date_of_birth,matric_number
455Aaren,Pieri,Aaren Pieri,1,1990-01-02,100000
456Claus,Finau,Claus Finau,2,1990-01-03,100001
457Brit,Berson,Brit Berson,3,1990-01-04,100001
458""")
459        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
460        self.browser.open(datacenter_path)
461        self.browser.getLink('Upload CSV file').click()
462        filecontents = cStringIO.StringIO(open('students.csv', 'rb').read())
463        filewidget = self.browser.getControl(name='uploadfile:file')
464        filewidget.add_file(filecontents, 'text/plain', 'students.csv')
465        self.browser.getControl(name='SUBMIT').click()
466        self.browser.getLink('Batch processing').click()
467        button = lookup_submit_value(
468            'select', 'students_zope.mgr.csv', self.browser)
469        button.click()
470        importerselect = self.browser.getControl(name='importer')
471        modeselect = self.browser.getControl(name='mode')
472        importerselect.getControl('Student Importer').selected = True
473        modeselect.getControl(value='create').selected = True
474        self.browser.getControl('Proceed to step 3...').click()
475        self.assertTrue('Header fields OK' in self.browser.contents)
476        self.browser.getControl('Perform import...').click()
477        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
478        self.assertTrue('Successfully processed 2 rows' in self.browser.contents)
479        self.assertTrue('Batch processing finished' in self.browser.contents)
480        open('studycourses.csv', 'wb').write(
481"""reg_number,matric_number,certificate,current_session,current_level
4821,,CERT1,2008,100
483,100001,CERT1,2008,100
484,100002,CERT1,2008,100
485""")
486        self.browser.open(datacenter_path)
487        self.browser.getLink('Upload CSV file').click()
488        filecontents = cStringIO.StringIO(open('studycourses.csv', 'rb').read())
489        filewidget = self.browser.getControl(name='uploadfile:file')
490        filewidget.add_file(filecontents, 'text/plain', 'studycourses.csv')
491        self.browser.getControl(name='SUBMIT').click()
492        self.browser.getLink('Batch processing').click()
493        button = lookup_submit_value(
494            'select', 'studycourses_zope.mgr.csv', self.browser)
495        button.click()
496        importerselect = self.browser.getControl(name='importer')
497        modeselect = self.browser.getControl(name='mode')
498        importerselect.getControl(
499            'StudentStudyCourse Importer (update only)').selected = True
500        modeselect.getControl(value='create').selected = True
501        self.browser.getControl('Proceed to step 3...').click()
502        self.assertTrue('Update mode only' in self.browser.contents)
503        self.browser.getControl('Proceed to step 3...').click()
504        self.assertTrue('Header fields OK' in self.browser.contents)
505        self.browser.getControl('Perform import...').click()
506        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
507        self.assertTrue('Successfully processed 2 rows' in self.browser.contents)
508        return
509
510    def test_student_change_password(self):
511        # Students can change the password
512        self.browser.open(self.login_path)
513        self.browser.getControl(name="form.login").value = self.student_id
514        self.browser.getControl(name="form.password").value = 'spwd'
515        self.browser.getControl("Login").click()
516        self.assertEqual(self.browser.url, self.student_path)
517        self.assertTrue('You logged in' in self.browser.contents)
518        # Change password
519        self.browser.getLink("Change password").click()
520        self.browser.getControl(name="form.password").value = 'new_password'
521        self.browser.getControl(
522            name="form.password_repeat").value = 'new_passssword'
523        self.browser.getControl("Save").click()
524        self.assertTrue('passwords do not match' in self.browser.contents)
525        self.browser.getControl(name="form.password").value = 'new_password'
526        self.browser.getControl(
527            name="form.password_repeat").value = 'new_password'
528        self.browser.getControl("Save").click()
529        self.assertTrue('Form has been saved' in self.browser.contents)
530        # We are still logged in. Changing the password hasn't thrown us out.
531        self.browser.getLink("My Data").click()
532        self.assertEqual(self.browser.url, self.student_path)
533        # We can logout
534        self.browser.getLink("Logout").click()
535        self.assertTrue('You have been logged out' in self.browser.contents)
536        self.assertEqual(self.browser.url, 'http://localhost/app')
537        # We can login again with the new password
538        self.browser.getLink("Login").click()
539        self.browser.open(self.login_path)
540        self.browser.getControl(name="form.login").value = self.student_id
541        self.browser.getControl(name="form.password").value = 'new_password'
542        self.browser.getControl("Login").click()
543        self.assertEqual(self.browser.url, self.student_path)
544        self.assertTrue('You logged in' in self.browser.contents)
545        return
546
547    def test_setpassword(self):
548        # Set password for first-time access
549        student = Student()
550        student.reg_number = u'123456'
551        student.fullname = u'Klaus Tester'
552        self.app['students'].addStudent(student)
553        setpassword_path = 'http://localhost/app/setpassword'
554        student_path = 'http://localhost/app/students/%s' % student.student_id
555        self.browser.open(setpassword_path)
556        self.browser.getControl(name="ac_series").value = self.existing_pwdseries
557        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
558        self.browser.getControl(name="reg_number").value = '223456'
559        self.browser.getControl("Show").click()
560        self.assertMatches('...No student found...',
561                           self.browser.contents)
562        self.browser.getControl(name="reg_number").value = '123456'
563        self.browser.getControl(name="ac_number").value = '999999'
564        self.browser.getControl("Show").click()
565        self.assertMatches('...Access code is invalid...',
566                           self.browser.contents)
567        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
568        self.browser.getControl("Show").click()
569        self.assertMatches('...Password has been set. Your Student Id is...',
570                           self.browser.contents)
571        self.browser.getControl("Show").click()
572        self.assertMatches(
573            '...Password has already been set. Your Student Id is...',
574            self.browser.contents)
575        existing_pwdpin = self.pwdpins[1]
576        parts = existing_pwdpin.split('-')[1:]
577        existing_pwdseries, existing_pwdnumber = parts
578        self.browser.getControl(name="ac_series").value = existing_pwdseries
579        self.browser.getControl(name="ac_number").value = existing_pwdnumber
580        self.browser.getControl(name="reg_number").value = '123456'
581        self.browser.getControl("Show").click()
582        self.assertMatches(
583            '...You are using the wrong Access Code...',
584            self.browser.contents)
585        # The student can login with the new credentials
586        self.browser.open(self.login_path)
587        self.browser.getControl(name="form.login").value = student.student_id
588        self.browser.getControl(
589            name="form.password").value = self.existing_pwdnumber
590        self.browser.getControl("Login").click()
591        self.assertEqual(self.browser.url, student_path)
592        self.assertTrue('You logged in' in self.browser.contents)
593        return
594
595    def test_student_access(self):
596        # Students can access their own objects
597        # and can perform actions
598        IWorkflowInfo(self.student).fireTransition('admit')
599        self.browser.open(self.login_path)
600        self.browser.getControl(name="form.login").value = self.student_id
601        self.browser.getControl(name="form.password").value = 'spwd'
602        self.browser.getControl("Login").click()
603        # Student can view the clearance data
604        self.browser.getLink("Clearance Data").click()
605        # Student can't open clearance edit form before starting clearance
606        self.browser.open(self.student_path + '/cedit')
607        self.assertMatches('...The requested form is locked...',
608                           self.browser.contents)
609        self.browser.getLink("Clearance Data").click()
610        self.browser.getLink("Start clearance").click()
611        self.browser.getControl(name="ac_series").value = '3'
612        self.browser.getControl(name="ac_number").value = '4444444'
613        self.browser.getControl("Start clearance now").click()
614        self.assertMatches('...Activation code is invalid...',
615                           self.browser.contents)
616        self.browser.getControl(name="ac_series").value = self.existing_clrseries
617        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
618        # Owner is Hans Wurst, AC can't be invalidated
619        self.browser.getControl("Start clearance now").click()
620        self.assertMatches('...You are not the owner of this access code...',
621                           self.browser.contents)
622        # Set the correct owner
623        self.existing_clrac.owner = self.student_id
624        self.browser.getControl("Start clearance now").click()
625        self.assertMatches('...Clearance process has been started...',
626                           self.browser.contents)
627        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
628        self.browser.getControl("Save", index=0).click()
629        # Student can view the clearance data
630        self.browser.getLink("Clearance Data").click()
631        # and go back to the edit form
632        self.browser.getLink("Edit").click()
633        self.browser.getControl("Save and request clearance").click()
634        self.browser.getControl(name="ac_series").value = self.existing_clrseries
635        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
636        self.browser.getControl("Request clearance now").click()
637        self.assertMatches('...Clearance has been requested...',
638                           self.browser.contents)
639        # Student can't reopen clearance form after requesting clearance
640        self.browser.open(self.student_path + '/cedit')
641        self.assertMatches('...The requested form is locked...',
642                           self.browser.contents)
643        # Student can't add study level if not in state 'school fee paid'
644        self.browser.open(self.student_path + '/studycourse/add')
645        self.assertMatches('...The requested form is locked...',
646                           self.browser.contents)
647        # ... and must be transferred first
648        IWorkflowInfo(self.student).fireTransition('clear')
649        IWorkflowInfo(self.student).fireTransition('pay_first_school_fee')
650        # Now students can add the current study level
651        self.browser.getLink("Study Course").click()
652        self.browser.getLink("Add course list").click()
653        self.assertMatches('...Add current level 100 (Year 1)...',
654                           self.browser.contents)
655        self.browser.getControl("Create course list now").click()
656        self.browser.getLink("100").click()
657        self.browser.getLink("Add and remove courses").click()
658        self.browser.getControl("Add course ticket").click()
659        self.browser.getControl(name="form.course").value = ['COURSE1']
660        self.browser.getControl("Add course ticket").click()
661        self.assertMatches('...The ticket exists...',
662                           self.browser.contents)
663        self.student['studycourse'].current_level = 200
664        self.browser.getLink("Study Course").click()
665        self.browser.getLink("Add course list").click()
666        self.assertMatches('...Add current level 200 (Year 2)...',
667                           self.browser.contents)
668        self.browser.getControl("Create course list now").click()
669        self.browser.getLink("200").click()
670        self.browser.getLink("Add and remove courses").click()
671        self.browser.getControl("Add course ticket").click()
672        self.browser.getControl(name="form.course").value = ['COURSE1']
673        self.browser.getControl("Add course ticket").click()
674        self.assertMatches('...Successfully added COURSE1...',
675                           self.browser.contents)
676        # Students can open the pdf course registration slip
677        self.browser.open(self.student_path + '/studycourse/200')
678        self.browser.getLink("Download course registration slip").click()
679        self.assertEqual(self.browser.headers['Status'], '200 Ok')
680        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
681        # Students can remove course tickets
682        self.browser.open(self.student_path + '/studycourse/200/edit')
683        self.browser.getControl("Remove selected", index=0).click()
684        self.assertTrue('No ticket selected' in self.browser.contents)
685        ctrl = self.browser.getControl(name='val_id')
686        ctrl.getControl(value='COURSE1').selected = True
687        self.browser.getControl("Remove selected", index=0).click()
688        self.assertTrue('Successfully removed' in self.browser.contents)
689        self.browser.getControl("Register course list").click()
690        self.assertTrue('Course list has been registered' in self.browser.contents)
691        self.assertEqual(self.student.state, 'courses registered')
692        return
693
694    def test_manage_payments(self):
695        # Managers can add online school fee payment tickets
696        # if certain requirements are met
697        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
698        self.browser.open(self.payments_student_path)
699        self.browser.getControl("Add online payment ticket").click()
700        self.browser.getControl(name="form.p_category").value = ['schoolfee']
701        self.browser.getControl("Create ticket").click()
702        self.assertMatches('...ticket created...',
703                           self.browser.contents)
704        ctrl = self.browser.getControl(name='val_id')
705        value = ctrl.options[0]
706        self.browser.getLink(value).click()
707        self.assertMatches('...Amount Authorized...',
708                           self.browser.contents)
709        payment_url = self.browser.url
710
711        # The pdf payment receipt can't yet be opened
712        self.browser.open(payment_url + '/payment_receipt.pdf')
713        self.assertMatches('...Ticket not yet paid...',
714                           self.browser.contents)
715
716        # The same payment ticket (with same p_item, p_session and p_category)
717        # can't be added twice.
718        self.browser.open(self.payments_student_path)
719        self.browser.getControl("Add online payment ticket").click()
720        self.browser.getControl(name="form.p_category").value = ['schoolfee']
721        self.browser.getControl("Create ticket").click()
722        self.assertMatches('...This payment ticket already exists...',
723                           self.browser.contents)
724
725        # Managers can open the callback view which simulates a valid callback
726        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
727        self.browser.open(payment_url)
728        self.browser.getLink("Request callback").click()
729        self.assertMatches('...Valid callback received...',
730                          self.browser.contents)
731
732        # Callback can't be applied twice
733        self.browser.open(payment_url + '/callback')
734        self.assertMatches('...This ticket has already been paid...',
735                          self.browser.contents)
736
737        # Managers can open the pdf payment receipt
738        self.browser.getLink("Download payment receipt").click()
739        self.assertEqual(self.browser.headers['Status'], '200 Ok')
740        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
741
742        # Managers can remove online school fee payment tickets
743        self.browser.open(self.payments_student_path)
744        self.browser.getControl("Remove selected").click()
745        self.assertMatches('...No payment selected...', self.browser.contents)
746        ctrl = self.browser.getControl(name='val_id')
747        value = ctrl.options[0]
748        ctrl.getControl(value=value).selected = True
749        self.browser.getControl("Remove selected", index=0).click()
750        self.assertTrue('Successfully removed' in self.browser.contents)
751
752        # Managers can add online clearance payment tickets
753        self.browser.open(self.payments_student_path + '/addop')
754        self.browser.getControl(name="form.p_category").value = ['clearance']
755        self.browser.getControl("Create ticket").click()
756        self.assertMatches('...ticket created...',
757                           self.browser.contents)
758
759        # Managers can open the callback view which simulates a valid callback
760        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
761        ctrl = self.browser.getControl(name='val_id')
762        value = ctrl.options[0]
763        self.browser.getLink(value).click()
764        self.browser.open(self.browser.url + '/callback')
765        self.assertMatches('...Valid callback received...',
766                          self.browser.contents)
767        expected = '''...
768        <td>
769          Paid
770        </td>...'''
771        self.assertMatches(expected,self.browser.contents)
772        # The new CLR-0 pin has been created
773        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
774        pin = self.app['accesscodes']['CLR-0'].keys()[0]
775        ac = self.app['accesscodes']['CLR-0'][pin]
776        ac.owner = self.student_id
777        # The new CLR-0 pin can be used for starting clearance
778        IWorkflowInfo(self.student).fireTransition('admit')
779        self.browser.open(self.student_path + '/start_clearance')
780        parts = pin.split('-')[1:]
781        clrseries, clrnumber = parts
782        self.browser.getControl(name="ac_series").value = clrseries
783        self.browser.getControl(name="ac_number").value = clrnumber
784        self.browser.getControl("Start clearance now").click()
785        self.assertMatches('...Clearance process has been started...',
786                           self.browser.contents)
787        return
788
789    def test_student_payments(self):
790        # Login
791        self.browser.open(self.login_path)
792        self.browser.getControl(name="form.login").value = self.student_id
793        self.browser.getControl(name="form.password").value = 'spwd'
794        self.browser.getControl("Login").click()
795
796        # Students can add online clearance payment tickets
797        self.browser.open(self.payments_student_path + '/addop')
798        self.browser.getControl(name="form.p_category").value = ['clearance']
799        self.browser.getControl("Create ticket").click()
800        self.assertMatches('...ticket created...',
801                           self.browser.contents)
802
803        # Students can open the callback view which simulates a valid callback
804        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
805        ctrl = self.browser.getControl(name='val_id')
806        value = ctrl.options[0]
807        self.browser.getLink(value).click()
808        payment_url = self.browser.url
809        self.browser.open(payment_url + '/callback')
810        self.assertMatches('...Valid callback received...',
811                          self.browser.contents)
812        expected = '''...
813        <td>
814          Paid
815        </td>...'''
816        self.assertMatches(expected,self.browser.contents)
817        # The new CLR-0 pin has been created
818        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
819        pin = self.app['accesscodes']['CLR-0'].keys()[0]
820        ac = self.app['accesscodes']['CLR-0'][pin]
821        ac.owner = self.student_id
822
823        # Students can open the pdf payment receipt
824        self.browser.open(payment_url + '/payment_receipt.pdf')
825        self.assertEqual(self.browser.headers['Status'], '200 Ok')
826        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
827
828        # The new CLR-0 pin can be used for starting clearance
829        IWorkflowInfo(self.student).fireTransition('admit')
830        self.browser.open(self.student_path + '/start_clearance')
831        parts = pin.split('-')[1:]
832        clrseries, clrnumber = parts
833        self.browser.getControl(name="ac_series").value = clrseries
834        self.browser.getControl(name="ac_number").value = clrnumber
835        self.browser.getControl("Start clearance now").click()
836        self.assertMatches('...Clearance process has been started...',
837                           self.browser.contents)
838
839        # Students can add online school fee payment tickets
840        self.browser.open(self.payments_student_path)
841        self.browser.getControl("Add online payment ticket").click()
842        self.browser.getControl(name="form.p_category").value = ['schoolfee']
843        self.browser.getControl("Create ticket").click()
844        self.assertMatches('...ticket created...',
845                           self.browser.contents)
846        ctrl = self.browser.getControl(name='val_id')
847        value = ctrl.options[0]
848        self.browser.getLink(value).click()
849        self.assertMatches('...Amount Authorized...',
850                           self.browser.contents)
851
852        # Students can open the callback view which simulates a valid callback
853        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
854        self.browser.open(self.browser.url + '/callback')
855        self.assertMatches('...Valid callback received...',
856                          self.browser.contents)
857
858        # Students can remove only online payment tickets which have
859        # not received a valid callback
860        self.browser.open(self.payments_student_path)
861        self.assertRaises(
862            LookupError, self.browser.getControl, name='val_id')
863        self.browser.open(self.payments_student_path + '/addop')
864        self.browser.getControl(name="form.p_category").value = ['gown']
865        self.browser.getControl("Create ticket").click()
866        self.browser.open(self.payments_student_path)
867        ctrl = self.browser.getControl(name='val_id')
868        value = ctrl.options[0]
869        ctrl.getControl(value=value).selected = True
870        self.browser.getControl("Remove selected", index=0).click()
871        self.assertTrue('Successfully removed' in self.browser.contents)
872
873        # The new SFE-0 pin can be used for starting course registration
874        IWorkflowInfo(self.student).fireTransition('request_clearance')
875        IWorkflowInfo(self.student).fireTransition('clear')
876        self.browser.open(self.studycourse_student_path)
877        self.browser.getLink('Start course registration').click()
878        pin = self.app['accesscodes']['SFE-0'].keys()[0]
879        parts = pin.split('-')[1:]
880        sfeseries, sfenumber = parts
881        self.browser.getControl(name="ac_series").value = sfeseries
882        self.browser.getControl(name="ac_number").value = sfenumber
883        self.browser.getControl("Start course registration now").click()
884        self.assertMatches('...Course registration has been started...',
885                           self.browser.contents)
886        self.assertTrue(self.student.state == 'school fee paid')
887        return
888
889    def test_manage_accommodation(self):
890        # Managers can add online booking fee payment tickets and open the
891        # callback view (see test_manage_payments)
892        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
893        self.browser.open(self.payments_student_path)
894        self.browser.getControl("Add online payment ticket").click()
895        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
896        # If student is not in accommodation session, payment cannot be processed
897        self.app['configuration'].accommodation_session = 2011
898        self.browser.getControl("Create ticket").click()
899        self.assertMatches('...Your current session does not match...',
900                           self.browser.contents)
901        self.app['configuration'].accommodation_session = 2004
902        self.browser.getControl("Add online payment ticket").click()
903        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
904        self.browser.getControl("Create ticket").click()
905        ctrl = self.browser.getControl(name='val_id')
906        value = ctrl.options[0]
907        self.browser.getLink(value).click()
908        self.browser.open(self.browser.url + '/callback')
909        # The new HOS-0 pin has been created
910        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
911        pin = self.app['accesscodes']['HOS-0'].keys()[0]
912        ac = self.app['accesscodes']['HOS-0'][pin]
913        ac.owner = self.student_id
914        parts = pin.split('-')[1:]
915        sfeseries, sfenumber = parts
916        # Managers can use HOS code and book a bed space with it
917        self.browser.open(self.acco_student_path)
918        self.browser.getLink("Book accommodation").click()
919        self.assertMatches('...You are in the wrong...',
920                           self.browser.contents)
921        IWorkflowInfo(self.student).fireTransition('admit')
922        self.browser.getLink("Book accommodation").click()
923        self.assertMatches('...Activation Code:...',
924                           self.browser.contents)
925        self.browser.getControl(name="ac_series").value = sfeseries
926        self.browser.getControl(name="ac_number").value = sfenumber
927        self.browser.getControl("Create bed ticket").click()
928        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
929                           self.browser.contents)
930        # Bed has been allocated
931        bed1 = self.app['hostels']['hall-1']['hall-1_A_101_A']
932        self.assertTrue(bed1.owner == self.student_id)
933        # BedTicketAddPage is now blocked
934        self.browser.getLink("Book accommodation").click()
935        self.assertMatches('...You already booked a bed space...',
936            self.browser.contents)
937        # The bed ticket displays the data correctly
938        self.browser.open(self.acco_student_path + '/2004')
939        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
940                           self.browser.contents)
941        self.assertMatches('...2004/2005...', self.browser.contents)
942        self.assertMatches('...regular_male_fr...', self.browser.contents)
943        self.assertMatches('...%s...' % pin, self.browser.contents)
944        # Managers can relocate students if the student's bed_type has changed
945        self.browser.getLink("Relocate student").click()
946        self.assertMatches(
947            "...Bed category hasn't changed...", self.browser.contents)
948        self.student.sex = u'f'
949        self.browser.getLink("Relocate student").click()
950        self.assertMatches(
951            "...Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
952        self.assertTrue(bed1.owner == NOT_OCCUPIED)
953        bed2 = self.app['hostels']['hall-1']['hall-1_A_101_B']
954        self.assertTrue(bed2.owner == self.student_id)
955        self.assertTrue(self.student['accommodation'][
956            '2004'].bed_type == u'regular_female_fr')
957        # The payment object still shows the original payment item
958        payment_id = self.student['payments'].keys()[0]
959        payment = self.student['payments'][payment_id]
960        self.assertTrue(payment.p_item == u'regular_male_fr')
961        # Managers can relocate students if the bed's bed_type has changed
962        bed1.bed_type = u'regular_female_fr'
963        bed2.bed_type = u'regular_male_reserved'
964        notify(grok.ObjectModifiedEvent(bed1))
965        notify(grok.ObjectModifiedEvent(bed2))
966        self.browser.getLink("Relocate student").click()
967        self.assertMatches(
968            "...Student relocated and new bed booked...", self.browser.contents)
969        self.assertMatches(
970            "... Hall 1, Block A, Room 101, Bed A...", self.browser.contents)
971        self.assertMatches(bed1.owner, self.student_id)
972        self.assertMatches(bed2.owner, NOT_OCCUPIED)
973        # Managers can delete bed tickets
974        self.browser.open(self.acco_student_path)
975        ctrl = self.browser.getControl(name='val_id')
976        value = ctrl.options[0]
977        ctrl.getControl(value=value).selected = True
978        self.browser.getControl("Remove selected", index=0).click()
979        self.assertMatches('...Successfully removed...', self.browser.contents)
980        # The bed has been properly released by the event handler
981        self.assertMatches(bed1.owner, NOT_OCCUPIED)
982        self.assertMatches(bed2.owner, NOT_OCCUPIED)
983        return
984
985    def test_student_accommodation(self):
986        # Login
987        self.browser.open(self.login_path)
988        self.browser.getControl(name="form.login").value = self.student_id
989        self.browser.getControl(name="form.password").value = 'spwd'
990        self.browser.getControl("Login").click()
991
992        # Students can add online booking fee payment tickets and open the
993        # callback view (see test_manage_payments)
994        self.browser.getLink("Payments").click()
995        self.browser.getControl("Add online payment ticket").click()
996        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
997        self.browser.getControl("Create ticket").click()
998        ctrl = self.browser.getControl(name='val_id')
999        value = ctrl.options[0]
1000        self.browser.getLink(value).click()
1001        self.browser.open(self.browser.url + '/callback')
1002        # The new HOS-0 pin has been created
1003        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
1004        pin = self.app['accesscodes']['HOS-0'].keys()[0]
1005        ac = self.app['accesscodes']['HOS-0'][pin]
1006        ac.owner = self.student_id
1007        parts = pin.split('-')[1:]
1008        sfeseries, sfenumber = parts
1009
1010        # Students can use HOS code and book a bed space with it
1011        self.browser.open(self.acco_student_path)
1012        self.browser.getLink("Book accommodation").click()
1013        self.assertMatches('...You are in the wrong...',
1014                           self.browser.contents)
1015        IWorkflowInfo(self.student).fireTransition('admit')
1016        self.browser.getLink("Book accommodation").click()
1017        self.assertMatches('...Activation Code:...',
1018                           self.browser.contents)
1019        self.browser.getControl(name="ac_series").value = sfeseries
1020        self.browser.getControl(name="ac_number").value = sfenumber
1021        self.browser.getControl("Create bed ticket").click()
1022        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1023                           self.browser.contents)
1024
1025        # Bed has been allocated
1026        bed = self.app['hostels']['hall-1']['hall-1_A_101_A']
1027        self.assertTrue(bed.owner == self.student_id)
1028
1029        # BedTicketAddPage is now blocked
1030        self.browser.getLink("Book accommodation").click()
1031        self.assertMatches('...You already booked a bed space...',
1032            self.browser.contents)
1033
1034        # The bed ticket displays the data correctly
1035        self.browser.open(self.acco_student_path + '/2004')
1036        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1037                           self.browser.contents)
1038        self.assertMatches('...2004/2005...', self.browser.contents)
1039        self.assertMatches('...regular_male_fr...', self.browser.contents)
1040        self.assertMatches('...%s...' % pin, self.browser.contents)
1041
1042        # Students can open the pdf slip
1043        self.browser.open(self.browser.url + '/bed_allocation.pdf')
1044        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1045        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1046
1047        # Students can't relocate themselves
1048        self.assertFalse('Relocate' in self.browser.contents)
1049        relocate_path = self.acco_student_path + '/2004/relocate'
1050        self.assertRaises(
1051            Unauthorized, self.browser.open, relocate_path)
1052
1053        # Students can't the Remove button and check boxes
1054        self.browser.open(self.acco_student_path)
1055        self.assertFalse('Remove' in self.browser.contents)
1056        self.assertFalse('val_id' in self.browser.contents)
1057        return
Note: See TracBrowser for help on using the repository browser.