source: main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/students/tests/test_browser.py @ 17611

Last change on this file since 17611 was 17611, checked in by Henrik Bettermann, 13 months ago

Customize LoginPage? to redirect to request_webservice instead of showing the login page.

  • Property svn:keywords set to Id
File size: 24.1 KB
Line 
1## $Id: test_browser.py 17611 2023-10-12 21:45:55Z 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##
18import os
19import shutil
20import tempfile
21from time import time
22from datetime import datetime, timedelta
23from StringIO import StringIO
24from hurry.workflow.interfaces import IWorkflowState, IWorkflowInfo
25from zope.component.hooks import setSite, clearSite
26from zope.component import getUtility, createObject
27from zope.interface import verify
28from zope.securitypolicy.interfaces import IPrincipalRoleManager
29from waeup.kofa.app import University
30from waeup.kofa.students.tests.test_browser import (
31    StudentsFullSetup, SAMPLE_IMAGE)
32from waeup.kofa.testing import FunctionalTestCase
33from waeup.kofa.interfaces import (
34    IExtFileStore, IFileStoreNameChooser)
35from waeup.kofa.schoolgrades import ResultEntry
36from waeup.kofa.students.batching import StudentProcessor
37from waeup.kofa.students.interfaces import IStudentsUtils
38from waeup.kofa.browser.tests.test_pdf import samples_dir
39from waeup.kofa.tests.test_authentication import SECRET
40from kofacustom.nigeria.students.batching import NigeriaStudentProcessor
41from kofacustom.nigeria.testing import FunctionalLayer
42from kofacustom.nigeria.utils.utils import NigeriaKofaUtils
43from kofacustom.nigeria.students.interfaces import (
44    INigeriaStudentStudyCourse, INigeriaStudent,
45    INigeriaStudentStudyLevel, INigeriaCourseTicket)
46
47
48STUDENT_SAMPLE_DATA = open(
49    os.path.join(os.path.dirname(__file__), 'sample_student_data.csv'),
50    'rb').read()
51
52STUDENT_HEADER_FIELDS = STUDENT_SAMPLE_DATA.split(
53    '\n')[0].split(',')
54
55class StudentProcessorTest(FunctionalTestCase):
56    """Perform some batching tests.
57    """
58
59    layer = FunctionalLayer
60
61    def setUp(self):
62        super(StudentProcessorTest, self).setUp()
63        # Setup a sample site for each test
64        app = University()
65        self.dc_root = tempfile.mkdtemp()
66        app['datacenter'].setStoragePath(self.dc_root)
67
68        # Prepopulate the ZODB...
69        self.getRootFolder()['app'] = app
70        # we add the site immediately after creation to the
71        # ZODB. Catalogs and other local utilities are not setup
72        # before that step.
73        self.app = self.getRootFolder()['app']
74        # Set site here. Some of the following setup code might need
75        # to access grok.getSite() and should get our new app then
76        setSite(app)
77
78        self.processor_base = StudentProcessor()
79        self.processor = NigeriaStudentProcessor()
80        self.workdir = tempfile.mkdtemp()
81        self.csv_file = os.path.join(self.workdir, 'sample_student_data.csv')
82        open(self.csv_file, 'wb').write(STUDENT_SAMPLE_DATA)
83
84    def tearDown(self):
85        super(StudentProcessorTest, self).tearDown()
86        shutil.rmtree(self.workdir)
87        shutil.rmtree(self.dc_root)
88        clearSite()
89        return
90
91    def test_import(self):
92        num, num_warns, fin_file, fail_file = self.processor.doImport(
93            self.csv_file, STUDENT_HEADER_FIELDS)
94        #print open(fail_file).read()
95        self.assertEqual(num_warns,0)
96        assert len(self.app['students'].keys()) == 3
97        # Also fst_sit_results have been properly imported (tested only here!)
98        self.assertEqual(
99            self.app['students']['K1000000'].fst_sit_results[0].__dict__,
100            {'grade': 'A1', 'subject': 'visual_art'})
101        self.assertEqual(
102            self.app['students']['K1000000'].fst_sit_results[1].__dict__,
103            {'grade': 'C6', 'subject': 'applied_electricity'})
104        shutil.rmtree(os.path.dirname(fin_file))
105
106
107class StudentUITests(StudentsFullSetup):
108    """Tests for customized student class views and pages
109    """
110
111    layer = FunctionalLayer
112
113    def setUp(self):
114        super(StudentUITests, self).setUp()
115
116    def test_classes(self):
117        # Let's see if objects created in the customized
118        # portal really implement the customized interfaces
119        verify.verifyObject(INigeriaStudent, self.student)
120        verify.verifyObject(
121            INigeriaStudentStudyCourse, self.student['studycourse'])
122        studylevel = createObject(u'waeup.StudentStudyLevel')
123        verify.verifyObject(INigeriaStudentStudyLevel, studylevel)
124        ticket = createObject(u'waeup.CourseTicket')
125        verify.verifyObject(INigeriaCourseTicket, ticket)
126        IWorkflowState(self.student).setState('returning')
127        # Let's see if next_session_allowed works as expected
128        # A, ug_ft, 100
129        self.assertTrue(self.student['studycourse'].next_session_allowed)
130        # Zero, ug_ft, 100
131        self.student['studycourse'].current_verdict = '0' # Zero!
132        self.assertTrue(self.student['studycourse'].next_session_allowed)
133        # Zero, ug_ft, 200
134        self.student['studycourse'].current_level = 200
135        self.assertFalse(self.student['studycourse'].next_session_allowed)
136        # Zero, de_ft, 200
137        self.student['studycourse'].certificate.study_mode = 'de_ft'
138        self.assertTrue(self.student['studycourse'].next_session_allowed)
139        # Zero, ph_ft, 300
140        self.student['studycourse'].certificate.study_mode = 'ph_ft'
141        self.student['studycourse'].current_level = 300
142        self.assertTrue(self.student['studycourse'].next_session_allowed)
143        # Zero, ph_ft, 400
144        self.student['studycourse'].current_level = 400
145        self.assertFalse(self.student['studycourse'].next_session_allowed)
146
147        # Now we convert the certificate into a postgraduate certificate
148        IWorkflowState(self.student).setState('school fee paid')
149        self.certificate.study_mode = 'pg_ft'
150        # ... and voila next session registration is allowed
151        self.assertTrue(self.student['studycourse'].next_session_allowed)
152
153    def test_manage_access(self):
154        self.student.nationality = u'DE'
155        # Managers can access the pages of students
156        # and can perform actions
157        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
158        # The student created in the base package is an ug student
159        self.browser.open(self.student_path)
160        self.browser.getLink("Clearance Data").click()
161        self.assertEqual(self.browser.headers['Status'], '200 Ok')
162        self.assertEqual(self.browser.url, self.clearance_path)
163        self.browser.getLink("Manage").click()
164        self.assertEqual(self.browser.headers['Status'], '200 Ok')
165        self.assertEqual(self.browser.url, self.manage_clearance_path)
166        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
167        self.browser.getControl("Save").click()
168        self.assertMatches('...Form has been saved...',
169                           self.browser.contents)
170        self.assertMatches('...First Sitting Record...',
171                           self.browser.contents)
172        # Managers can open clearance slip of ug students
173        self.browser.open(self.student_path + '/clearance_slip.pdf')
174        self.assertEqual(self.browser.headers['Status'], '200 Ok')
175        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
176        # There is no pg field in the clearance form
177        self.assertFalse('Second Higher Education Record'
178            in self.browser.contents)
179        # Now we change the study mode ...
180        self.certificate.study_mode = 'pg_ft'
181        self.browser.open(self.clearance_path)
182        # ... and additional pg clearance fields appear
183        self.assertMatches('...Second Higher Education Record...',
184                           self.browser.contents)
185        # But also fields from the ug form are displayed
186        self.assertMatches('...First Sitting Record...',
187                           self.browser.contents)
188        # Managers can open clearance slip of pg students
189        self.browser.open(self.student_path + '/clearance_slip.pdf')
190        self.assertEqual(self.browser.headers['Status'], '200 Ok')
191        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
192        # Managers can edit personal data. No fields are required.
193        self.browser.open(self.manage_personal_path)
194        self.browser.getControl("Save").click()
195        self.assertMatches('...Form has been saved...',
196                           self.browser.contents)
197
198    def test_logging(self):
199        self.student.nationality = u'DE'
200        valid_subj = NigeriaKofaUtils().EXAM_SUBJECTS_DICT.keys()[0]
201        valid_grade = NigeriaKofaUtils().EXAM_GRADES[0][0]
202        result_entry = ResultEntry(valid_subj, valid_grade)
203        self.student.fst_sit_results = [result_entry,]
204        # Managers can access the pages of students
205        # and can perform actions
206        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
207        # The student created in the base package is an ug student
208        self.browser.open(self.student_path)
209        self.browser.getLink("Clearance Data").click()
210        self.browser.getLink("Manage").click()
211        self.browser.getControl("Save").click()
212        logfile = os.path.join(
213            self.app['datacenter'].storage, 'logs', 'students.log')
214        logcontent = open(logfile).read()
215        self.assertFalse('saved: fst_sit_results' in logcontent)
216
217    def test_student_access(self):
218        # Students can edit clearance data
219        IWorkflowState(self.student).setState('cleared')
220        self.browser.open(self.login_path)
221        self.browser.getControl(name="form.login").value = self.student_id
222        self.browser.getControl(name="form.password").value = 'spwd'
223        self.browser.getControl("Login").click()
224        # Even in state admitted students can't change the portait if
225        # application slip exists.
226        IWorkflowState(self.student).setState('admitted')
227        self.browser.open(self.student_path)
228        self.assertTrue('Change portrait' in self.browser.contents)
229        file_store = getUtility(IExtFileStore)
230        applicant_slip = 'My application slip'
231        file_id = IFileStoreNameChooser(self.student).chooseName(
232            attr="application_slip.pdf")
233        file_store.createFile(file_id, StringIO(applicant_slip))
234        self.browser.open(self.student_path)
235        self.assertFalse('Change portrait' in self.browser.contents)
236        self.browser.open(self.student_path + '/change_portrait')
237        self.assertTrue('The requested form is locked' in self.browser.contents)
238
239        # Student can view and edit clearance data if clearance has started ...
240        IWorkflowInfo(self.student).fireTransition('start_clearance')
241        self.student.officer_comment = u'Fill properly'
242        self.browser.getLink("Clearance Data").click()
243        self.assertTrue("Officer's Comment" in self.browser.contents)
244        # Students can't edit officer's comment
245        self.browser.getLink("Edit").click()
246        self.assertFalse("Officer's Comment" in self.browser.contents)
247        self.assertTrue('Save' in self.browser.contents)
248        # ... and request clearance if nationality field has been filled.
249        self.browser.getControl("Save and request clearance").click()
250        self.assertMatches('...Required input is missing...',
251                           self.browser.contents)
252        self.student.nationality = u'DE'
253        self.browser.open(self.edit_clearance_path)
254        self.browser.getControl("Save and request clearance").click()
255        self.assertMatches('...Clearance has been requested...',
256                           self.browser.contents)
257
258        # Students can edit personal data. Some fields are required.
259        self.browser.open(self.personal_path)
260        self.assertTrue('Updated' in self.browser.contents)
261        self.browser.getLink("Edit").click()
262        self.assertEqual(self.browser.headers['Status'], '200 Ok')
263        self.assertEqual(self.browser.url, self.edit_personal_path)
264        self.browser.getControl("Save").click()
265        self.assertMatches('...Required input is missing...',
266                           self.browser.contents)
267        self.browser.getControl(name="form.perm_address").value = 'My address!'
268        self.browser.getControl("Save").click()
269        self.assertMatches('...Required input is missing...',
270                           self.browser.contents)
271        # Ok, let's give up and fill the rest.
272        self.browser.getControl(name="form.next_kin_name").value = 'My Mutti'
273        self.browser.getControl(name="form.next_kin_relation").value = 'mother'
274        self.browser.getControl(name="form.next_kin_address").value = 'sweet home'
275        self.browser.getControl(name="form.next_kin_phone.country").value = ['+234']
276        self.browser.getControl(name="form.next_kin_phone.ext").value = '45678'
277        self.browser.getControl("Save").click()
278        self.assertMatches('...Form has been saved...',
279                           self.browser.contents)
280
281    def test_manage_upload_file(self):
282        # Managers can upload a file via the StudentClearanceManageFormPage
283        # The image is stored even if form has errors
284        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
285        self.browser.open(self.manage_clearance_path)
286        # Managers can add and delete a file
287        self.browser.open(self.manage_clearance_path)
288        image = open(SAMPLE_IMAGE, 'rb')
289        ctrl = self.browser.getControl(name='birthcertificateupload')
290        file_ctrl = ctrl.mech_control
291        file_ctrl.add_file(image, filename='my_acceptance_letter.jpg')
292        self.browser.getControl(
293            name='upload_acceptanceletterupload').click()
294        # Uups, we used the wrong 'Browse' field
295        self.assertFalse(
296            '<a target="image" href="acc_let">'
297            in self.browser.contents)
298        ctrl = self.browser.getControl(name='acceptanceletterupload')
299        file_ctrl = ctrl.mech_control
300        image.seek(0)
301        file_ctrl.add_file(image, filename='my_acceptance_letter.jpg')
302        self.browser.getControl(
303            name='upload_acceptanceletterupload').click()
304        self.assertTrue(
305            'http://localhost/app/students/K1000000/acc_let'
306            in self.browser.contents)
307        self.browser.getControl(
308            name='delete_acceptanceletterupload').click()
309        self.assertTrue(
310            'acc_let deleted'
311            in self.browser.contents)
312
313    def test_student_expired_personal_data(self):
314        # Login
315        IWorkflowState(self.student).setState('school fee paid')
316        delta = timedelta(days=180)
317        self.student.personal_updated = datetime.utcnow() - delta
318        self.browser.open(self.login_path)
319        self.browser.getControl(name="form.login").value = self.student_id
320        self.browser.getControl(name="form.password").value = 'spwd'
321        self.browser.getControl("Login").click()
322        self.assertEqual(self.browser.url, self.student_path)
323        self.assertTrue(
324            'You logged in' in self.browser.contents)
325        # Students don't see personal_updated field in edit form
326        self.browser.open(self.edit_personal_path)
327        self.assertFalse('Updated' in self.browser.contents)
328        self.browser.open(self.personal_path)
329        self.assertTrue('Updated' in self.browser.contents)
330        self.browser.getLink("Logout").click()
331        delta = timedelta(days=181)
332        self.student.personal_updated = datetime.utcnow() - delta
333        self.browser.open(self.login_path)
334        self.browser.getControl(name="form.login").value = self.student_id
335        self.browser.getControl(name="form.password").value = 'spwd'
336        self.browser.getControl("Login").click()
337        self.assertEqual(self.browser.url, self.edit_personal_path)
338        self.assertTrue(
339            'Your personal data record is outdated.' in self.browser.contents)
340
341    def test_lga_nationality(self):
342        self.student.nationality = u'DE'
343        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
344        self.browser.open(self.manage_clearance_path)
345        self.browser.getControl(name="form.lga").value = ['abia_aba_north']
346        self.browser.getControl("Save").click()
347        self.assertTrue(
348            'Nationalty and LGA are contradictory.' in self.browser.contents)
349        self.browser.getControl(name="form.nationality").value = ['NG']
350        self.browser.getControl("Save").click()
351        self.assertTrue(
352            'Form has been saved' in self.browser.contents)
353
354    def test_financial_clearance(self):
355        self.app['users'].addUser('mrbursary', SECRET)
356        self.app['users']['mrbursary'].email = 'mrbursary@foo.ng'
357        self.app['users']['mrbursary'].title = u'Carlo Pitter'
358        # Clearance officers needs to get
359        # the StudentsOfficer site role
360        prmglobal = IPrincipalRoleManager(self.app)
361        prmglobal.assignRoleToPrincipal('waeup.StudentsOfficer', 'mrbursary')
362        # Assign BursaryOfficer role
363        prmglobal.assignRoleToPrincipal('waeup.BursaryOfficer', 'mrbursary')
364        # and FinancialClearanceOfficer role
365        prmglobal.assignRoleToPrincipal(
366            'waeup.FinancialClearanceOfficer', 'mrbursary')
367        # Login
368        self.browser.open(self.login_path)
369        self.browser.getControl(name="form.login").value = 'mrbursary'
370        self.browser.getControl(name="form.password").value = SECRET
371        self.browser.getControl("Login").click()
372        # BO can see his roles
373        self.browser.getLink("My Roles").click()
374        self.assertMatches(
375            '...<div>Bursary Officer</div>...',
376            self.browser.contents)
377        # BO can view student record
378        self.browser.open(self.student_path)
379        # BO can see clearance button ...
380        self.assertTrue(
381            'Clear student financially' in self.browser.contents)
382        # ... but not withdraw button
383        self.assertFalse(
384            'Withdraw financial clearance' in self.browser.contents)
385        # BO can clear student
386        self.browser.getLink("Clear student financially").click()
387        self.assertTrue(
388            'Student has been financially cleared' in self.browser.contents)
389        # Name of BO and date have been stored
390        self.assertEqual(self.student.financially_cleared_by, 'Carlo Pitter')
391        self.assertMatches(
392            '<YYYY-MM-DD hh:mm:ss>',
393            self.student.financial_clearance_date.strftime(
394                "%Y-%m-%d %H:%M:%S"))
395        # BO can't see clearance button ...
396        self.assertFalse(
397            'Clear student financially' in self.browser.contents)
398        # ... but withdraw button and can withdraw clearance
399        self.browser.getLink("Withdraw financial clearance").click()
400        self.assertTrue(
401            'Financial clearance withdrawn' in self.browser.contents)
402        # Name of BO and date have been deleted
403        self.assertEqual(self.student.financially_cleared_by, None)
404        self.assertEqual(self.student.financial_clearance_date, None)
405        # Clearance is logged
406        logfile = os.path.join(
407            self.app['datacenter'].storage, 'logs', 'students.log')
408        logcontent = open(logfile).read()
409        self.assertTrue(
410            'mrbursary - kofacustom.nigeria.students.browser.ClearStudentFinancially'
411            ' - K1000000 - financially cleared' in logcontent)
412        self.assertTrue(
413            'mrbursary - kofacustom.nigeria.students.browser.WithdrawFinancialClearance'
414            ' - K1000000 - financial clearance withdrawn' in logcontent)
415        # Clearance is also stored in the history
416        self.browser.open(self.history_path)
417        self.assertMatches(
418            '...2016-01-16 15:50:48 WAT - Financially cleared by Carlo Pitter...',
419            self.browser.contents)
420        self.assertMatches(
421            '...2016-01-16 15:50:48 WAT - Financial clearance withdrawn by Carlo Pitter...',
422            self.browser.contents)
423
424    def test_fiancial_clearance_pdf_slip(self):
425        payment1 = createObject(u'waeup.StudentOnlinePayment')
426        timestamp = ("%d" % int(time()*10000))[1:]
427        payment1.p_id = "LSCNEW-2-4153206270" # the longest possible p_id
428        payment1.p_category = 'schoolfee'
429        payment1.p_item = u'My School Fee'
430        payment1.p_session = 2015
431        payment1.p_level = 100
432        payment1.p_current = True
433        payment1.amount_auth = 23456.9
434        payment1.approve()
435        payment2 = createObject(u'waeup.StudentOnlinePayment')
436        timestamp = ("%d" % int(time()*10000))[1:]
437        payment2.p_id = "p%s" % timestamp
438        payment2.p_category = 'clearance'
439        payment2.p_item = u'My Clearance Fee'
440        payment2.p_session = 2015
441        payment2.p_level = 100
442        payment2.p_current = True
443        payment2.amount_auth = 5678.6
444        payment2.approve()
445        self.student['payments'][payment1.p_id] = payment1
446        self.student['payments'][payment2.p_id] = payment2
447        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
448        self.browser.open(self.student_path + '/clear_financially')
449        self.browser.getLink("Download fee payment history").click()
450        self.assertEqual(self.browser.headers['Status'], '200 Ok')
451        self.assertEqual(self.browser.headers['Content-Type'],
452                         'application/pdf')
453        path = os.path.join(samples_dir(), 'fee_payment_history.pdf')
454        open(path, 'wb').write(self.browser.contents)
455        print "Sample PDF fee_payment_history.pdf written to %s" % path
456        return
457
458    def test_provisionally_cleared(self):
459        # Students can edit clearance data
460        IWorkflowState(self.student).setState('school fee paid')
461        self.browser.open(self.login_path)
462        self.browser.getControl(name="form.login").value = self.student_id
463        self.browser.getControl(name="form.password").value = 'spwd'
464        self.browser.getControl("Login").click()
465        # Student can't access the clearance form
466        self.browser.open(self.student_path)
467        self.browser.getLink("Clearance Data").click()
468        # Student can't open clearance edit form before starting clearance
469        self.browser.open(self.student_path + '/cedit')
470        self.assertMatches('...The requested form is locked...',
471                           self.browser.contents)
472        self.assertFalse(
473            '<h1 class="kofa-content-label">Edit clearance data</h1>'
474            in self.browser.contents)
475        # If provisionally_cleared is set they can
476        self.student.provisionally_cleared = True
477        self.browser.getLink("Clearance Data").click()
478        self.browser.getLink("Edit").click()
479        self.assertTrue(
480            '<h1 class="kofa-content-label">Edit clearance data</h1>'
481            in self.browser.contents)
482
483    def test_student_redirect_camefrom(self):
484        payment1 = createObject(u'waeup.StudentOnlinePayment')
485        payment1.p_id = "p4153206270"
486        payment1.p_category = 'schoolfee'
487        payment1.p_item = u'My School Fee'
488        payment1.p_session = 2015
489        payment1.p_level = 100
490        payment1.p_current = True
491        payment1.amount_auth = 23456.9
492        payment1.approve()
493        self.student['payments'][payment1.p_id] = payment1
494        self.browser.open(self.login_path)
495        self.browser.getControl(name="form.login").value = self.student_id
496        self.browser.getControl(name="form.password").value = 'spwd'
497        self.browser.getControl("Login").click()
498        self.assertMatches(
499            '...You logged in...', self.browser.contents)
500        camefrom = '/students/K1000000/studycourse'
501        self.browser.open(self.login_path + '?camefrom=%s' % camefrom)
502        # nothing happens
503        self.assertEqual(self.browser.url, self.login_path + '?camefrom=%s' % camefrom)
504        camefrom = '/students/K1000000/payments/%s/request_webservice' % payment1.p_id
505        # Kofa redirects to request_webservice and then returns to payment ticket index view
506        self.browser.open(self.login_path + '?camefrom=%s' % camefrom)
507        self.assertEqual(
508            self.browser.url,
509            'http://localhost/app/students/K1000000/payments/p4153206270/@@index')
Note: See TracBrowser for help on using the repository browser.