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

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

Also previous_verdict and entry_session must be set.

  • Property svn:keywords set to Id
File size: 13.8 KB
Line 
1## $Id: test_student.py 9132 2012-08-31 14:52:33Z 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        success = self.student.transfer(self.certificate2,
270            current_session=2013, entry_session=2010)
271        self.assertEqual(self.student['studycourse_1'].certificate.code, 'CERT1')
272        self.assertEqual(self.student['studycourse'].certificate.code, 'CERT2')
273        self.assertEqual(self.student['studycourse_1'].current_session, 2012)
274        self.assertEqual(self.student['studycourse'].current_session, 2013)
275        self.assertEqual(self.student['studycourse_1'].__name__, 'studycourse_1')
276        logfile = os.path.join(
277            self.app['datacenter'].storage, 'logs', 'students.log')
278        logcontent = open(logfile).read()
279        self.assertTrue('system - K1000000 - transferred from CERT1 to CERT2'
280            in logcontent)
281
282        # The students_catalog has been updated.
283        cat = queryUtility(ICatalog, name='students_catalog')
284        results = cat.searchResults(certcode=('CERT1', 'CERT1'))
285        results = [x for x in results]
286        self.assertEqual(len(results), 0)
287        results = cat.searchResults(certcode=('CERT2', 'CERT2'))
288        results = [x for x in results]
289        self.assertEqual(len(results), 1)
290        assert results[0] is self.app['students'][self.student.student_id]
291        results = cat.searchResults(current_session=(2013,2013))
292        results = [x for x in results]
293        self.assertEqual(len(results), 1)
294        assert results[0] is self.app['students'][self.student.student_id]
295
296        # Students can be transferred (only) two times.
297        success = self.student.transfer(self.certificate,
298            current_session=2013, entry_session=2010)
299        self.assertTrue(success)
300        success = self.student.transfer(self.certificate2,
301            current_session=2013, entry_session=2010)
302        self.assertFalse(success)
303        self.assertEqual([i for i in self.student.keys()],
304            [u'accommodation', u'payments', u'studycourse',
305             u'studycourse_1', u'studycourse_2'])
306
307        # The students_catalog has been updated again.
308        cat = queryUtility(ICatalog, name='students_catalog')
309        results = cat.searchResults(certcode=('CERT1', 'CERT1'))
310        results = [x for x in results]
311        self.assertEqual(len(results), 1)
312        assert results[0] is self.app['students'][self.student.student_id]
313        return
314
315class StudentFactoryTest(FunctionalTestCase):
316
317    layer = FunctionalLayer
318
319    def setUp(self):
320        super(StudentFactoryTest, self).setUp()
321        self.factory = StudentFactory()
322
323    def tearDown(self):
324        super(StudentFactoryTest, self).tearDown()
325
326    def test_interfaces(self):
327        verify.verifyClass(IFactory, StudentFactory)
328        verify.verifyObject(IFactory, self.factory)
329
330    def test_factory(self):
331        obj = self.factory()
332        assert isinstance(obj, Student)
333
334    def test_getInterfaces(self):
335        implemented_by = self.factory.getInterfaces()
336        assert implemented_by.isOrExtends(IStudent)
Note: See TracBrowser for help on using the repository browser.