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

Last change on this file since 13621 was 13620, checked in by Henrik Bettermann, 9 years ago

Add fields, permissions, browser views and buttons to enable financial clearance by bursory officers.

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