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

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

Rename course referrers to certificate courses (according to the discussion long time ago).

Edit some button labels and titles.

Move code from view to content components.

  • Property svn:keywords set to Id
File size: 10.4 KB
Line 
1## $Id: test_student.py 8920 2012-07-05 14:48:51Z 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
23from cStringIO import StringIO
24from datetime import tzinfo
25from zope.component import getUtility
26from zope.component.interfaces import IFactory
27from zope.interface import verify
28from waeup.kofa.interfaces import IExtFileStore, IFileStoreNameChooser
29from waeup.kofa.students.export import EXPORTER_NAMES
30from waeup.kofa.students.student import (
31    Student, StudentFactory, handle_student_removed, path_from_studid)
32from waeup.kofa.students.studycourse import StudentStudyCourse
33from waeup.kofa.students.studylevel import StudentStudyLevel, CourseTicket
34from waeup.kofa.students.payments import StudentPaymentsContainer
35from waeup.kofa.students.accommodation import StudentAccommodation, BedTicket
36from waeup.kofa.students.interfaces import (
37    IStudent, IStudentStudyCourse, IStudentPaymentsContainer,
38    IStudentAccommodation, IStudentStudyLevel, ICourseTicket, IBedTicket,
39    IStudentNavigation)
40from waeup.kofa.students.tests.test_batching import StudentImportExportSetup
41from waeup.kofa.testing import FunctionalLayer, FunctionalTestCase
42from waeup.kofa.university.department import Department
43
44class HelperTests(unittest.TestCase):
45    # Tests for helper functions in student module.
46
47    def test_path_from_studid(self):
48        # make sure we get predictable paths from student ids.
49        self.assertEqual(
50            path_from_studid('K1000000'), u'01000/K1000000')
51        self.assertEqual(
52            path_from_studid('K1234567'), u'01234/K1234567')
53        self.assertEqual(
54            path_from_studid('K12345678'), u'12345/K12345678')
55        # The algorithm works also for overlong numbers, just to be
56        # sure.
57        self.assertEqual(
58            path_from_studid('K123456789'), u'123456/K123456789')
59        # low numbers (< 10**6) are treated special: they get max. of
60        # 10,000 entries. That's mainly because of old students
61        # migrated into our portal.
62        self.assertEqual(
63            path_from_studid('KM123456'), u'00120/KM123456')
64        return
65
66class StudentTest(FunctionalTestCase):
67
68    layer = FunctionalLayer
69
70    def setUp(self):
71        super(StudentTest, self).setUp()
72        self.student = Student()
73        self.student.firstname = u'Anna'
74        self.student.lastname = u'Tester'
75        self.studycourse = StudentStudyCourse()
76        self.studylevel = StudentStudyLevel()
77        self.courseticket = CourseTicket()
78        self.payments = StudentPaymentsContainer()
79        self.accommodation = StudentAccommodation()
80        self.bedticket = BedTicket()
81        return
82
83    def tearDown(self):
84        super(StudentTest, self).tearDown()
85        return
86
87    def test_interfaces(self):
88        verify.verifyClass(IStudent, Student)
89        verify.verifyClass(IStudentNavigation, Student)
90        verify.verifyObject(IStudent, self.student)
91        verify.verifyObject(IStudentNavigation, self.student)
92
93        verify.verifyClass(IStudentStudyCourse, StudentStudyCourse)
94        verify.verifyClass(IStudentNavigation, StudentStudyCourse)
95        verify.verifyObject(IStudentStudyCourse, self.studycourse)
96        verify.verifyObject(IStudentNavigation, self.studycourse)
97
98        verify.verifyClass(IStudentStudyLevel, StudentStudyLevel)
99        verify.verifyClass(IStudentNavigation, StudentStudyLevel)
100        verify.verifyObject(IStudentStudyLevel, self.studylevel)
101        verify.verifyObject(IStudentNavigation, self.studylevel)
102
103        verify.verifyClass(ICourseTicket, CourseTicket)
104        verify.verifyClass(IStudentNavigation, CourseTicket)
105        verify.verifyObject(ICourseTicket, self.courseticket)
106        verify.verifyObject(IStudentNavigation, self.courseticket)
107
108        verify.verifyClass(IStudentPaymentsContainer, StudentPaymentsContainer)
109        verify.verifyClass(IStudentNavigation, StudentPaymentsContainer)
110        verify.verifyObject(IStudentPaymentsContainer, self.payments)
111        verify.verifyObject(IStudentNavigation, self.payments)
112
113        verify.verifyClass(IStudentAccommodation, StudentAccommodation)
114        verify.verifyClass(IStudentNavigation, StudentAccommodation)
115        verify.verifyObject(IStudentAccommodation, self.accommodation)
116        verify.verifyObject(IStudentNavigation, self.accommodation)
117
118        verify.verifyClass(IBedTicket, BedTicket)
119        verify.verifyClass(IStudentNavigation, BedTicket)
120        verify.verifyObject(IBedTicket, self.bedticket)
121        verify.verifyObject(IStudentNavigation, self.bedticket)
122        return
123
124    def test_base(self):
125        department = Department()
126        studycourse = StudentStudyCourse()
127        self.assertRaises(
128            TypeError, studycourse.addStudentStudyLevel, department)
129        studylevel = StudentStudyLevel()
130        self.assertRaises(
131            TypeError, studylevel.addCourseTicket, department, department)
132
133    def test_booking_date(self):
134        isinstance(self.bedticket.booking_date.tzinfo, tzinfo)
135        self.assertEqual(self.bedticket.booking_date.tzinfo, None)
136        return
137
138class StudentRemovalTests(StudentImportExportSetup):
139    # Test handle_student_removed
140    #
141    # This is a complex action updating several CSV files and moving
142    # stored files to a backup location.
143    #
144    # These tests make no assumptions about the CSV files except that
145    # they contain a deletion timestamp at end of each data row
146
147    layer = FunctionalLayer
148
149    def setUp(self):
150        super(StudentRemovalTests, self).setUp()
151        self.setup_for_export()
152        return
153
154    def create_passport_img(self, student):
155        # create some passport file for `student`
156        storage = getUtility(IExtFileStore)
157        image_path = os.path.join(os.path.dirname(__file__), 'test_image.jpg')
158        self.image_contents = open(image_path, 'rb').read()
159        file_id = IFileStoreNameChooser(student).chooseName(
160            attr='passport.jpg')
161        storage.createFile(file_id, StringIO(self.image_contents))
162
163    def test_backup_single_student_data(self):
164        # when a single student is removed, the data is backed up.
165        self.setup_student(self.student)
166        # Add a fake image
167        self.create_passport_img(self.student)
168        handle_student_removed(self.student, None)
169        del_dir = self.app['datacenter'].deleted_path
170        del_img_path = os.path.join(
171            del_dir, 'media', 'students', '00110', 'A111111',
172            'passport_A111111.jpg')
173
174        # The image was copied over
175        self.assertTrue(os.path.isfile(del_img_path))
176        self.assertEqual(
177            open(del_img_path, 'rb').read(),
178            self.image_contents)
179
180        # The student data were put into CSV files
181        for name in EXPORTER_NAMES:
182            csv_path = os.path.join(del_dir, '%s.csv' % name)
183            self.assertTrue(os.path.isfile(csv_path))
184            contents = open(csv_path, 'rb').read().split('\r\n')
185            # We expect 3 lines output including a linebreak at end of file.
186            self.assertEqual(len(contents), 3)
187        return
188
189    def test_backup_append_csv(self):
190        # when several students are removed, existing CSVs are appended
191        self.setup_student(self.student)
192        # Add a fake image
193        self.create_passport_img(self.student)
194        del_dir = self.app['datacenter'].deleted_path
195        # put fake data into students.csv with trailing linebreak
196        students_csv = os.path.join(del_dir, 'students.csv')
197        open(students_csv, 'wb').write('line1\r\nline2\r\n')
198        handle_student_removed(self.student, None)
199        contents = open(students_csv, 'rb').read().split('\r\n')
200        # there should be 4 lines in result csv (including trailing linebreak)
201        self.assertEqual(len(contents), 4)
202        return
203
204    def test_old_files_removed(self):
205        # make sure old files are not accessible any more
206        self.setup_student(self.student)
207        # Add a fake image
208        self.create_passport_img(self.student)
209        # make sure we can access the image before removal
210        file_store = getUtility(IExtFileStore)
211        image = file_store.getFileByContext(self.student, attr='passport.jpg')
212        self.assertTrue(image is not None)
213
214        # remove image (hopefully)
215        handle_student_removed(self.student, None)
216
217        # the is not accessible anymore
218        image = file_store.getFileByContext(self.student, attr='passport.jpg')
219        self.assertEqual(image, None)
220        return
221
222    def test_csv_file_entries_have_timestamp(self):
223        # each row in written csv files has a ``del_date`` column to
224        # tell when the associated student was deleted
225        self.setup_student(self.student)
226        del_dir = self.app['datacenter'].deleted_path
227        students_csv = os.path.join(del_dir, 'students.csv')
228        handle_student_removed(self.student, None)
229        contents = open(students_csv, 'rb').read().split('\r\n')
230        # the CSV header ends with a ``del_date`` column
231        self.assertTrue(contents[0].endswith(',del_date'))
232        # each line ends with an UTC timestamp
233        timestamp = contents[1][-23:]
234        self.assertTrue(re.match(
235            '^\d\d-\d\d-\d\d \d\d:\d\d:\d\d\+00:00$', timestamp))
236        return
237
238
239class StudentFactoryTest(FunctionalTestCase):
240
241    layer = FunctionalLayer
242
243    def setUp(self):
244        super(StudentFactoryTest, self).setUp()
245        self.factory = StudentFactory()
246
247    def tearDown(self):
248        super(StudentFactoryTest, self).tearDown()
249
250    def test_interfaces(self):
251        verify.verifyClass(IFactory, StudentFactory)
252        verify.verifyObject(IFactory, self.factory)
253
254    def test_factory(self):
255        obj = self.factory()
256        assert isinstance(obj, Student)
257
258    def test_getInterfaces(self):
259        implemented_by = self.factory.getInterfaces()
260        assert implemented_by.isOrExtends(IStudent)
Note: See TracBrowser for help on using the repository browser.