source: main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_student.py @ 14761

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

Add maint_payment_made method to BedTicket? class.

  • Property svn:keywords set to Id
File size: 17.0 KB
Line 
1## $Id: test_student.py 13314 2015-10-13 17:29:07Z henrik $
2##
3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18"""Tests for students and related.
19"""
20import os
21import re
22import unittest
23import grok
24from cStringIO import StringIO
25from datetime import tzinfo
26from zope.component import getUtility, queryUtility, createObject
27from zope.catalog.interfaces import ICatalog
28from zope.component.interfaces import IFactory
29from zope.event import notify
30from zope.interface import verify
31from zope.schema.interfaces import RequiredMissing
32from waeup.kofa.interfaces import IExtFileStore, IFileStoreNameChooser
33from waeup.kofa.students.student import (
34    Student, StudentFactory, handle_student_removed, path_from_studid)
35from waeup.kofa.students.studycourse import StudentStudyCourse
36from waeup.kofa.students.studylevel import StudentStudyLevel, CourseTicket
37from waeup.kofa.students.payments import StudentPaymentsContainer
38from waeup.kofa.students.accommodation import StudentAccommodation, BedTicket
39from waeup.kofa.students.interfaces import (
40    IStudent, IStudentStudyCourse, IStudentPaymentsContainer,
41    IStudentAccommodation, IStudentStudyLevel, ICourseTicket, IBedTicket,
42    IStudentNavigation, IStudentsUtils)
43from waeup.kofa.students.tests.test_batching import StudentImportExportSetup
44from waeup.kofa.testing import FunctionalLayer, FunctionalTestCase
45from waeup.kofa.university.department import Department
46
47class HelperTests(unittest.TestCase):
48    # Tests for helper functions in student module.
49
50    def test_path_from_studid(self):
51        # make sure we get predictable paths from student ids.
52        self.assertEqual(
53            path_from_studid('K1000000'), u'01000/K1000000')
54        self.assertEqual(
55            path_from_studid('K1234567'), u'01234/K1234567')
56        self.assertEqual(
57            path_from_studid('K12345678'), u'12345/K12345678')
58        # The algorithm works also for overlong numbers, just to be
59        # sure.
60        self.assertEqual(
61            path_from_studid('K123456789'), u'123456/K123456789')
62        # low numbers (< 10**6) are treated special: they get max. of
63        # 10,000 entries. That's mainly because of old students
64        # migrated into our portal.
65        self.assertEqual(
66            path_from_studid('KM123456'), u'00120/KM123456')
67        return
68
69class StudentTest(FunctionalTestCase):
70
71    layer = FunctionalLayer
72
73    def setUp(self):
74        super(StudentTest, self).setUp()
75        self.student = Student()
76        self.student.firstname = u'Anna'
77        self.student.lastname = u'Tester'
78        self.studycourse = StudentStudyCourse()
79        self.studylevel = StudentStudyLevel()
80        self.studylevel.level_session = 2015
81        self.courseticket = CourseTicket()
82        self.payments = StudentPaymentsContainer()
83        self.accommodation = StudentAccommodation()
84        self.bedticket = BedTicket()
85        return
86
87    def tearDown(self):
88        super(StudentTest, self).tearDown()
89        return
90
91    def test_interfaces(self):
92        verify.verifyClass(IStudent, Student)
93        verify.verifyClass(IStudentNavigation, Student)
94        verify.verifyObject(IStudent, self.student)
95        verify.verifyObject(IStudentNavigation, self.student)
96
97        verify.verifyClass(IStudentStudyCourse, StudentStudyCourse)
98        verify.verifyClass(IStudentNavigation, StudentStudyCourse)
99        verify.verifyObject(IStudentStudyCourse, self.studycourse)
100        verify.verifyObject(IStudentNavigation, self.studycourse)
101
102        verify.verifyClass(IStudentStudyLevel, StudentStudyLevel)
103        verify.verifyClass(IStudentNavigation, StudentStudyLevel)
104        verify.verifyObject(IStudentStudyLevel, self.studylevel)
105        verify.verifyObject(IStudentNavigation, self.studylevel)
106
107        verify.verifyClass(ICourseTicket, CourseTicket)
108        verify.verifyClass(IStudentNavigation, CourseTicket)
109        verify.verifyObject(ICourseTicket, self.courseticket)
110        verify.verifyObject(IStudentNavigation, self.courseticket)
111
112        verify.verifyClass(IStudentPaymentsContainer, StudentPaymentsContainer)
113        verify.verifyClass(IStudentNavigation, StudentPaymentsContainer)
114        verify.verifyObject(IStudentPaymentsContainer, self.payments)
115        verify.verifyObject(IStudentNavigation, self.payments)
116
117        verify.verifyClass(IStudentAccommodation, StudentAccommodation)
118        verify.verifyClass(IStudentNavigation, StudentAccommodation)
119        verify.verifyObject(IStudentAccommodation, self.accommodation)
120        verify.verifyObject(IStudentNavigation, self.accommodation)
121
122        verify.verifyClass(IBedTicket, BedTicket)
123        verify.verifyClass(IStudentNavigation, BedTicket)
124        verify.verifyObject(IBedTicket, self.bedticket)
125        verify.verifyObject(IStudentNavigation, self.bedticket)
126        return
127
128    def test_base(self):
129        department = Department()
130        studycourse = StudentStudyCourse()
131        self.assertRaises(
132            TypeError, studycourse.addStudentStudyLevel, department)
133        studylevel = StudentStudyLevel()
134        self.assertRaises(
135            TypeError, studylevel.addCourseTicket, department, department)
136
137    def test_booking_date(self):
138        isinstance(self.bedticket.booking_date.tzinfo, tzinfo)
139        self.assertEqual(self.bedticket.booking_date.tzinfo, None)
140        return
141
142    def test_maint_payment_made(self):
143        self.assertFalse(self.bedticket.maint_payment_made)
144        return
145
146
147class StudentRemovalTests(StudentImportExportSetup):
148    # Test handle_student_removed
149    #
150    # This is a complex action updating several CSV files and moving
151    # stored files to a backup location.
152    #
153    # These tests make no assumptions about the CSV files except that
154    # they contain a deletion timestamp at end of each data row
155
156    layer = FunctionalLayer
157
158    def setUp(self):
159        super(StudentRemovalTests, self).setUp()
160        self.setup_for_export()
161        return
162
163    def create_passport_img(self, student):
164        # create some passport file for `student`
165        storage = getUtility(IExtFileStore)
166        image_path = os.path.join(os.path.dirname(__file__), 'test_image.jpg')
167        self.image_contents = open(image_path, 'rb').read()
168        file_id = IFileStoreNameChooser(student).chooseName(
169            attr='passport.jpg')
170        storage.createFile(file_id, StringIO(self.image_contents))
171
172    def test_backup_single_student_data(self):
173        # when a single student is removed, the data is backed up.
174        self.setup_student(self.student)
175        # Add a fake image
176        self.create_passport_img(self.student)
177        handle_student_removed(self.student, None)
178        del_dir = self.app['datacenter'].deleted_path
179        del_img_path = os.path.join(
180            del_dir, 'media', 'students', '00110', 'A111111',
181            'passport_A111111.jpg')
182
183        # The image was copied over
184        self.assertTrue(os.path.isfile(del_img_path))
185        self.assertEqual(
186            open(del_img_path, 'rb').read(),
187            self.image_contents)
188
189        # The student data were put into CSV files
190        STUDENT_BACKUP_EXPORTER_NAMES = getUtility(
191            IStudentsUtils).STUDENT_BACKUP_EXPORTER_NAMES
192
193        for name in STUDENT_BACKUP_EXPORTER_NAMES:
194            csv_path = os.path.join(del_dir, '%s.csv' % name)
195            self.assertTrue(os.path.isfile(csv_path))
196            contents = open(csv_path, 'rb').read().split('\r\n')
197            # We expect 3 lines output including a linebreak at end of file.
198            self.assertEqual(len(contents), 3)
199        return
200
201    def test_backup_append_csv(self):
202        # when several students are removed, existing CSVs are appended
203        self.setup_student(self.student)
204        # Add a fake image
205        self.create_passport_img(self.student)
206        del_dir = self.app['datacenter'].deleted_path
207        # put fake data into students.csv with trailing linebreak
208        students_csv = os.path.join(del_dir, 'students.csv')
209        open(students_csv, 'wb').write('line1\r\nline2\r\n')
210        handle_student_removed(self.student, None)
211        contents = open(students_csv, 'rb').read().split('\r\n')
212        # there should be 4 lines in result csv (including trailing linebreak)
213        self.assertEqual(len(contents), 4)
214        return
215
216    def test_old_files_removed(self):
217        # make sure old files are not accessible any more
218        self.setup_student(self.student)
219        # Add a fake image
220        self.create_passport_img(self.student)
221        # make sure we can access the image before removal
222        file_store = getUtility(IExtFileStore)
223        image = file_store.getFileByContext(self.student, attr='passport.jpg')
224        self.assertTrue(image is not None)
225
226        # remove image (hopefully)
227        handle_student_removed(self.student, None)
228
229        # the is not accessible anymore
230        image = file_store.getFileByContext(self.student, attr='passport.jpg')
231        self.assertEqual(image, None)
232        return
233
234    def test_csv_file_entries_have_timestamp(self):
235        # each row in written csv files has a ``del_date`` column to
236        # tell when the associated student was deleted
237        self.setup_student(self.student)
238        del_dir = self.app['datacenter'].deleted_path
239        students_csv = os.path.join(del_dir, 'students.csv')
240        handle_student_removed(self.student, None)
241        contents = open(students_csv, 'rb').read().split('\r\n')
242        # the CSV header ends with a ``del_date`` column
243        self.assertTrue(contents[0].endswith(',del_date'))
244        # each line ends with an UTC timestamp
245        timestamp = contents[1][-23:]
246        self.assertTrue(re.match(
247            '^\d\d-\d\d-\d\d \d\d:\d\d:\d\d\+00:00$', timestamp))
248        return
249
250class StudentTransferTests(StudentImportExportSetup):
251
252    layer = FunctionalLayer
253
254    def setUp(self):
255        super(StudentTransferTests, self).setUp()
256
257        # Add additional certificate
258        self.certificate2 = createObject('waeup.Certificate')
259        self.certificate2.code = 'CERT2'
260        self.certificate2.application_category = 'basic'
261        self.certificate2.start_level = 200
262        self.certificate2.end_level = 500
263        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
264            self.certificate2)
265
266        # Add student with subobjects
267        student = Student()
268        self.app['students'].addStudent(student)
269        student = self.setup_student(student)
270        notify(grok.ObjectModifiedEvent(student))
271        self.student = self.app['students'][student.student_id]
272        return
273
274    def test_transfer_student(self):
275        self.assertRaises(
276            RequiredMissing, self.student.transfer, self.certificate2)
277        error = self.student.transfer(self.certificate2, current_session=1000)
278        self.assertTrue(error == -1)
279        error = self.student.transfer(self.certificate2, current_session=2013)
280        self.assertTrue(error == None)
281        self.assertEqual(self.student['studycourse_1'].certificate.code, 'CERT1')
282        self.assertEqual(self.student['studycourse'].certificate.code, 'CERT2')
283        self.assertEqual(self.student['studycourse_1'].current_session, 2012)
284        self.assertEqual(self.student['studycourse'].current_session, 2013)
285        self.assertEqual(self.student['studycourse'].entry_session,
286            self.student['studycourse_1'].entry_session)
287        self.assertEqual(self.student['studycourse_1'].__name__, 'studycourse_1')
288        logfile = os.path.join(
289            self.app['datacenter'].storage, 'logs', 'students.log')
290        logcontent = open(logfile).read()
291        self.assertTrue('system - K1000000 - transferred from CERT1 to CERT2'
292            in logcontent)
293        messages = ' '.join(self.student.history.messages)
294        self.assertMatches(
295            '...<YYYY-MM-DD hh:mm:ss> UTC - '
296            'Transferred from CERT1 to CERT2 by system', messages)
297
298        # The students_catalog has been updated.
299        cat = queryUtility(ICatalog, name='students_catalog')
300        results = cat.searchResults(certcode=('CERT1', 'CERT1'))
301        results = [x for x in results]
302        self.assertEqual(len(results), 0)
303        results = cat.searchResults(certcode=('CERT2', 'CERT2'))
304        results = [x for x in results]
305        self.assertEqual(len(results), 1)
306        assert results[0] is self.app['students'][self.student.student_id]
307        results = cat.searchResults(current_session=(2013,2013))
308        results = [x for x in results]
309        self.assertEqual(len(results), 1)
310        assert results[0] is self.app['students'][self.student.student_id]
311
312        # studycourse_1 is the previous course.
313        self.assertFalse(self.student['studycourse'].is_previous)
314        self.assertTrue(self.student['studycourse_1'].is_previous)
315
316        # Students can be transferred (only) two times.
317        error = self.student.transfer(self.certificate,
318            current_session=2013)
319        self.assertTrue(error == None)
320        error = self.student.transfer(self.certificate2,
321            current_session=2013)
322        self.assertTrue(error == -3)
323        self.assertEqual([i for i in self.student.keys()],
324            [u'accommodation', u'payments', u'studycourse',
325             u'studycourse_1', u'studycourse_2'])
326
327        # The studycourse with highest order number is the previous
328        # course.
329        self.assertFalse(self.student['studycourse'].is_previous)
330        self.assertFalse(self.student['studycourse_1'].is_previous)
331        self.assertTrue(self.student['studycourse_2'].is_previous)
332
333        # The students_catalog has been updated again.
334        cat = queryUtility(ICatalog, name='students_catalog')
335        results = cat.searchResults(certcode=('CERT1', 'CERT1'))
336        results = [x for x in results]
337        self.assertEqual(len(results), 1)
338        assert results[0] is self.app['students'][self.student.student_id]
339
340        # Previous transfer can be successively reverted.
341        self.assertEqual(self.student['studycourse_2'].certificate.code, 'CERT2')
342        self.assertEqual(self.student['studycourse_1'].certificate.code, 'CERT1')
343        self.assertEqual(self.student['studycourse'].certificate.code, 'CERT1')
344        error = self.student.revert_transfer()
345        self.assertTrue(error == None)
346        self.assertEqual([i for i in self.student.keys()],
347            [u'accommodation', u'payments', u'studycourse',
348             u'studycourse_1'])
349        self.assertEqual(self.student['studycourse_1'].certificate.code, 'CERT1')
350        self.assertEqual(self.student['studycourse'].certificate.code, 'CERT2')
351        # The students_catalog has been updated again.
352        cat = queryUtility(ICatalog, name='students_catalog')
353        results = cat.searchResults(certcode=('CERT2', 'CERT2'))
354        results = [x for x in results]
355        self.assertEqual(len(results), 1)
356        assert results[0] is self.app['students'][self.student.student_id]
357        error = self.student.revert_transfer()
358        self.assertTrue(error == None)
359        self.assertEqual([i for i in self.student.keys()],
360            [u'accommodation', u'payments', u'studycourse'])
361        self.assertEqual(self.student['studycourse'].certificate.code, 'CERT1')
362        error = self.student.revert_transfer()
363        self.assertTrue(error == -1)
364        # The students_catalog has been updated again.
365        cat = queryUtility(ICatalog, name='students_catalog')
366        results = cat.searchResults(certcode=('CERT1', 'CERT1'))
367        results = [x for x in results]
368        self.assertEqual(len(results), 1)
369        assert results[0] is self.app['students'][self.student.student_id]
370        results = cat.searchResults(certcode=('CERT2', 'CERT2'))
371        results = [x for x in results]
372        self.assertEqual(len(results), 0)
373        logcontent = open(logfile).read()
374        self.assertTrue('system - K1000000 - transfer reverted'
375            in logcontent)
376        messages = ' '.join(self.student.history.messages)
377        self.assertTrue('Transfer reverted by system' in messages)
378        return
379
380class StudentFactoryTest(FunctionalTestCase):
381
382    layer = FunctionalLayer
383
384    def setUp(self):
385        super(StudentFactoryTest, self).setUp()
386        self.factory = StudentFactory()
387
388    def tearDown(self):
389        super(StudentFactoryTest, self).tearDown()
390
391    def test_interfaces(self):
392        verify.verifyClass(IFactory, StudentFactory)
393        verify.verifyObject(IFactory, self.factory)
394
395    def test_factory(self):
396        obj = self.factory()
397        assert isinstance(obj, Student)
398
399    def test_getInterfaces(self):
400        implemented_by = self.factory.getInterfaces()
401        assert implemented_by.isOrExtends(IStudent)
Note: See TracBrowser for help on using the repository browser.