source: main/waeup.kofa/branches/uli-diazo-themed/src/waeup/kofa/students/tests/test_student.py @ 11089

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

Define is_previous method.

  • Property svn:keywords set to Id
File size: 16.8 KB
Line 
1## $Id: test_student.py 10059 2013-04-05 08:11:29Z 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.export import EXPORTER_NAMES
34from waeup.kofa.students.student import (
35    Student, StudentFactory, handle_student_removed, path_from_studid)
36from waeup.kofa.students.studycourse import StudentStudyCourse
37from waeup.kofa.students.studylevel import StudentStudyLevel, CourseTicket
38from waeup.kofa.students.payments import StudentPaymentsContainer
39from waeup.kofa.students.accommodation import StudentAccommodation, BedTicket
40from waeup.kofa.students.interfaces import (
41    IStudent, IStudentStudyCourse, IStudentPaymentsContainer,
42    IStudentAccommodation, IStudentStudyLevel, ICourseTicket, IBedTicket,
43    IStudentNavigation)
44from waeup.kofa.students.tests.test_batching import StudentImportExportSetup
45from waeup.kofa.testing import FunctionalLayer, FunctionalTestCase
46from waeup.kofa.university.department import Department
47
48class HelperTests(unittest.TestCase):
49    # Tests for helper functions in student module.
50
51    def test_path_from_studid(self):
52        # make sure we get predictable paths from student ids.
53        self.assertEqual(
54            path_from_studid('K1000000'), u'01000/K1000000')
55        self.assertEqual(
56            path_from_studid('K1234567'), u'01234/K1234567')
57        self.assertEqual(
58            path_from_studid('K12345678'), u'12345/K12345678')
59        # The algorithm works also for overlong numbers, just to be
60        # sure.
61        self.assertEqual(
62            path_from_studid('K123456789'), u'123456/K123456789')
63        # low numbers (< 10**6) are treated special: they get max. of
64        # 10,000 entries. That's mainly because of old students
65        # migrated into our portal.
66        self.assertEqual(
67            path_from_studid('KM123456'), u'00120/KM123456')
68        return
69
70class StudentTest(FunctionalTestCase):
71
72    layer = FunctionalLayer
73
74    def setUp(self):
75        super(StudentTest, self).setUp()
76        self.student = Student()
77        self.student.firstname = u'Anna'
78        self.student.lastname = u'Tester'
79        self.studycourse = StudentStudyCourse()
80        self.studylevel = StudentStudyLevel()
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
142class StudentRemovalTests(StudentImportExportSetup):
143    # Test handle_student_removed
144    #
145    # This is a complex action updating several CSV files and moving
146    # stored files to a backup location.
147    #
148    # These tests make no assumptions about the CSV files except that
149    # they contain a deletion timestamp at end of each data row
150
151    layer = FunctionalLayer
152
153    def setUp(self):
154        super(StudentRemovalTests, self).setUp()
155        self.setup_for_export()
156        return
157
158    def create_passport_img(self, student):
159        # create some passport file for `student`
160        storage = getUtility(IExtFileStore)
161        image_path = os.path.join(os.path.dirname(__file__), 'test_image.jpg')
162        self.image_contents = open(image_path, 'rb').read()
163        file_id = IFileStoreNameChooser(student).chooseName(
164            attr='passport.jpg')
165        storage.createFile(file_id, StringIO(self.image_contents))
166
167    def test_backup_single_student_data(self):
168        # when a single student is removed, the data is backed up.
169        self.setup_student(self.student)
170        # Add a fake image
171        self.create_passport_img(self.student)
172        handle_student_removed(self.student, None)
173        del_dir = self.app['datacenter'].deleted_path
174        del_img_path = os.path.join(
175            del_dir, 'media', 'students', '00110', 'A111111',
176            'passport_A111111.jpg')
177
178        # The image was copied over
179        self.assertTrue(os.path.isfile(del_img_path))
180        self.assertEqual(
181            open(del_img_path, 'rb').read(),
182            self.image_contents)
183
184        # The student data were put into CSV files
185        for name in EXPORTER_NAMES:
186            csv_path = os.path.join(del_dir, '%s.csv' % name)
187            self.assertTrue(os.path.isfile(csv_path))
188            contents = open(csv_path, 'rb').read().split('\r\n')
189            # We expect 3 lines output including a linebreak at end of file.
190            self.assertEqual(len(contents), 3)
191        return
192
193    def test_backup_append_csv(self):
194        # when several students are removed, existing CSVs are appended
195        self.setup_student(self.student)
196        # Add a fake image
197        self.create_passport_img(self.student)
198        del_dir = self.app['datacenter'].deleted_path
199        # put fake data into students.csv with trailing linebreak
200        students_csv = os.path.join(del_dir, 'students.csv')
201        open(students_csv, 'wb').write('line1\r\nline2\r\n')
202        handle_student_removed(self.student, None)
203        contents = open(students_csv, 'rb').read().split('\r\n')
204        # there should be 4 lines in result csv (including trailing linebreak)
205        self.assertEqual(len(contents), 4)
206        return
207
208    def test_old_files_removed(self):
209        # make sure old files are not accessible any more
210        self.setup_student(self.student)
211        # Add a fake image
212        self.create_passport_img(self.student)
213        # make sure we can access the image before removal
214        file_store = getUtility(IExtFileStore)
215        image = file_store.getFileByContext(self.student, attr='passport.jpg')
216        self.assertTrue(image is not None)
217
218        # remove image (hopefully)
219        handle_student_removed(self.student, None)
220
221        # the is not accessible anymore
222        image = file_store.getFileByContext(self.student, attr='passport.jpg')
223        self.assertEqual(image, None)
224        return
225
226    def test_csv_file_entries_have_timestamp(self):
227        # each row in written csv files has a ``del_date`` column to
228        # tell when the associated student was deleted
229        self.setup_student(self.student)
230        del_dir = self.app['datacenter'].deleted_path
231        students_csv = os.path.join(del_dir, 'students.csv')
232        handle_student_removed(self.student, None)
233        contents = open(students_csv, 'rb').read().split('\r\n')
234        # the CSV header ends with a ``del_date`` column
235        self.assertTrue(contents[0].endswith(',del_date'))
236        # each line ends with an UTC timestamp
237        timestamp = contents[1][-23:]
238        self.assertTrue(re.match(
239            '^\d\d-\d\d-\d\d \d\d:\d\d:\d\d\+00:00$', timestamp))
240        return
241
242class StudentTransferTests(StudentImportExportSetup):
243
244    layer = FunctionalLayer
245
246    def setUp(self):
247        super(StudentTransferTests, self).setUp()
248
249        # Add additional certificate
250        self.certificate2 = createObject('waeup.Certificate')
251        self.certificate2.code = 'CERT2'
252        self.certificate2.application_category = 'basic'
253        self.certificate2.start_level = 200
254        self.certificate2.end_level = 500
255        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
256            self.certificate2)
257
258        # Add student with subobjects
259        student = Student()
260        self.app['students'].addStudent(student)
261        student = self.setup_student(student)
262        notify(grok.ObjectModifiedEvent(student))
263        self.student = self.app['students'][student.student_id]
264        return
265
266    def test_transfer_student(self):
267        self.assertRaises(
268            RequiredMissing, self.student.transfer, self.certificate2)
269        error = self.student.transfer(self.certificate2, current_session=1000)
270        self.assertTrue(error == -1)
271        error = self.student.transfer(self.certificate2, current_session=2013)
272        self.assertTrue(error == None)
273        self.assertEqual(self.student['studycourse_1'].certificate.code, 'CERT1')
274        self.assertEqual(self.student['studycourse'].certificate.code, 'CERT2')
275        self.assertEqual(self.student['studycourse_1'].current_session, 2012)
276        self.assertEqual(self.student['studycourse'].current_session, 2013)
277        self.assertEqual(self.student['studycourse'].entry_session,
278            self.student['studycourse_1'].entry_session)
279        self.assertEqual(self.student['studycourse_1'].__name__, 'studycourse_1')
280        logfile = os.path.join(
281            self.app['datacenter'].storage, 'logs', 'students.log')
282        logcontent = open(logfile).read()
283        self.assertTrue('system - K1000000 - transferred from CERT1 to CERT2'
284            in logcontent)
285        messages = ' '.join(self.student.history.messages)
286        self.assertMatches(
287            '...<YYYY-MM-DD hh:mm:ss> UTC - '
288            'Transferred from CERT1 to CERT2 by system', messages)
289
290        # The students_catalog has been updated.
291        cat = queryUtility(ICatalog, name='students_catalog')
292        results = cat.searchResults(certcode=('CERT1', 'CERT1'))
293        results = [x for x in results]
294        self.assertEqual(len(results), 0)
295        results = cat.searchResults(certcode=('CERT2', 'CERT2'))
296        results = [x for x in results]
297        self.assertEqual(len(results), 1)
298        assert results[0] is self.app['students'][self.student.student_id]
299        results = cat.searchResults(current_session=(2013,2013))
300        results = [x for x in results]
301        self.assertEqual(len(results), 1)
302        assert results[0] is self.app['students'][self.student.student_id]
303
304        # studycourse_1 is the previous course.
305        self.assertFalse(self.student['studycourse'].is_previous)
306        self.assertTrue(self.student['studycourse_1'].is_previous)
307
308        # Students can be transferred (only) two times.
309        error = self.student.transfer(self.certificate,
310            current_session=2013)
311        self.assertTrue(error == None)
312        error = self.student.transfer(self.certificate2,
313            current_session=2013)
314        self.assertTrue(error == -3)
315        self.assertEqual([i for i in self.student.keys()],
316            [u'accommodation', u'payments', u'studycourse',
317             u'studycourse_1', u'studycourse_2'])
318
319        # The studycourse with highest order number is the previous
320        # course.
321        self.assertFalse(self.student['studycourse'].is_previous)
322        self.assertFalse(self.student['studycourse_1'].is_previous)
323        self.assertTrue(self.student['studycourse_2'].is_previous)
324
325        # The students_catalog has been updated again.
326        cat = queryUtility(ICatalog, name='students_catalog')
327        results = cat.searchResults(certcode=('CERT1', 'CERT1'))
328        results = [x for x in results]
329        self.assertEqual(len(results), 1)
330        assert results[0] is self.app['students'][self.student.student_id]
331
332        # Previous transfer can be successively reverted.
333        self.assertEqual(self.student['studycourse_2'].certificate.code, 'CERT2')
334        self.assertEqual(self.student['studycourse_1'].certificate.code, 'CERT1')
335        self.assertEqual(self.student['studycourse'].certificate.code, 'CERT1')
336        error = self.student.revert_transfer()
337        self.assertTrue(error == None)
338        self.assertEqual([i for i in self.student.keys()],
339            [u'accommodation', u'payments', u'studycourse',
340             u'studycourse_1'])
341        self.assertEqual(self.student['studycourse_1'].certificate.code, 'CERT1')
342        self.assertEqual(self.student['studycourse'].certificate.code, 'CERT2')
343        # The students_catalog has been updated again.
344        cat = queryUtility(ICatalog, name='students_catalog')
345        results = cat.searchResults(certcode=('CERT2', 'CERT2'))
346        results = [x for x in results]
347        self.assertEqual(len(results), 1)
348        assert results[0] is self.app['students'][self.student.student_id]
349        error = self.student.revert_transfer()
350        self.assertTrue(error == None)
351        self.assertEqual([i for i in self.student.keys()],
352            [u'accommodation', u'payments', u'studycourse'])
353        self.assertEqual(self.student['studycourse'].certificate.code, 'CERT1')
354        error = self.student.revert_transfer()
355        self.assertTrue(error == -1)
356        # The students_catalog has been updated again.
357        cat = queryUtility(ICatalog, name='students_catalog')
358        results = cat.searchResults(certcode=('CERT1', 'CERT1'))
359        results = [x for x in results]
360        self.assertEqual(len(results), 1)
361        assert results[0] is self.app['students'][self.student.student_id]
362        results = cat.searchResults(certcode=('CERT2', 'CERT2'))
363        results = [x for x in results]
364        self.assertEqual(len(results), 0)
365        logcontent = open(logfile).read()
366        self.assertTrue('system - K1000000 - transfer reverted'
367            in logcontent)
368        messages = ' '.join(self.student.history.messages)
369        self.assertTrue('Transfer reverted by system' in messages)
370        return
371
372class StudentFactoryTest(FunctionalTestCase):
373
374    layer = FunctionalLayer
375
376    def setUp(self):
377        super(StudentFactoryTest, self).setUp()
378        self.factory = StudentFactory()
379
380    def tearDown(self):
381        super(StudentFactoryTest, self).tearDown()
382
383    def test_interfaces(self):
384        verify.verifyClass(IFactory, StudentFactory)
385        verify.verifyObject(IFactory, self.factory)
386
387    def test_factory(self):
388        obj = self.factory()
389        assert isinstance(obj, Student)
390
391    def test_getInterfaces(self):
392        implemented_by = self.factory.getInterfaces()
393        assert implemented_by.isOrExtends(IStudent)
Note: See TracBrowser for help on using the repository browser.