source: main/waeup.kofa/trunk/src/waeup/kofa/hostels/tests.py @ 9250

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

Implement HostelProcessor?.

  • Property svn:keywords set to Id
File size: 20.2 KB
Line 
1## $Id: tests.py 9202 2012-09-19 10:17:44Z 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"""
19Tests for hostels and their UI components.
20"""
21import os
22import shutil
23import tempfile
24import grok
25import pytz
26from datetime import datetime, timedelta
27from zope.event import notify
28from zope.interface.verify import verifyClass, verifyObject
29from zope.component.hooks import setSite, clearSite
30from zope.testbrowser.testing import Browser
31from zope.security.interfaces import Unauthorized
32from zope.catalog.interfaces import ICatalog
33from zope.component import queryUtility
34from waeup.kofa.app import University
35from waeup.kofa.hostels.interfaces import (
36    IHostelsContainer, IHostel, IBed)
37from waeup.kofa.hostels.container import HostelsContainer
38from waeup.kofa.hostels.hostel import Hostel, Bed
39from waeup.kofa.hostels.batching import HostelProcessor
40from waeup.kofa.hostels.export import BedExporter, HostelExporter
41from waeup.kofa.testing import (FunctionalLayer, FunctionalTestCase)
42from waeup.kofa.students.student import Student
43from waeup.kofa.students.accommodation import BedTicket
44from waeup.kofa.university.department import Department
45
46HOSTEL_SAMPLE_DATA = open(
47    os.path.join(os.path.dirname(__file__), 'sample_hostel_data.csv'),
48    'rb').read()
49
50HOSTEL_HEADER_FIELDS = HOSTEL_SAMPLE_DATA.split(
51    '\n')[0].split(',')
52
53class HostelsContainerTestCase(FunctionalTestCase):
54
55    layer = FunctionalLayer
56
57    def test_interfaces(self):
58        # Make sure the correct interfaces are implemented.
59        self.assertTrue(
60            verifyClass(
61                IHostelsContainer, HostelsContainer)
62            )
63        self.assertTrue(
64            verifyObject(
65                IHostelsContainer, HostelsContainer())
66            )
67        self.assertTrue(
68            verifyClass(
69                IHostel, Hostel)
70            )
71        self.assertTrue(
72            verifyObject(
73                IHostel, Hostel())
74            )
75        self.assertTrue(
76            verifyClass(
77                IBed, Bed)
78            )
79        self.assertTrue(
80            verifyObject(
81                IBed, Bed())
82            )
83        return
84
85    def test_base(self):
86        # We cannot call the fundamental methods of a base in that case
87        container = HostelsContainer()
88        hostel = Hostel()
89        self.assertRaises(
90            NotImplementedError, container.archive)
91        self.assertRaises(
92            NotImplementedError, container.clear)
93        # We cannot add arbitrary objects
94        department = Department()
95        self.assertRaises(
96            TypeError, container.addHostel, department)
97        self.assertRaises(
98            TypeError, hostel.addBed, department)
99        # Application is expired if startdate or enddate are not set
100        # or current datetime is outside application period.
101        self.assertTrue(container.expired)
102        delta = timedelta(days=10)
103        container.startdate = datetime.now(pytz.utc) - delta
104        self.assertTrue(container.expired)
105        container.enddate = datetime.now(pytz.utc) + delta
106        self.assertFalse(container.expired)
107
108class HostelsFullSetup(FunctionalTestCase):
109
110    def setUp(self):
111        super(HostelsFullSetup, self).setUp()
112
113        # Setup a sample site for each test
114        app = University()
115        self.dc_root = tempfile.mkdtemp()
116        app['datacenter'].setStoragePath(self.dc_root)
117
118        # Prepopulate the ZODB...
119        self.getRootFolder()['app'] = app
120        # we add the site immediately after creation to the
121        # ZODB. Catalogs and other local utilities are not setup
122        # before that step.
123        self.app = self.getRootFolder()['app']
124        # Set site here. Some of the following setup code might need
125        # to access grok.getSite() and should get our new app then
126        setSite(app)
127
128        # Add student with subobjects
129        student = Student()
130        student.firstname = u'Anna'
131        student.lastname = u'Tester'
132        student.reg_number = u'123'
133        student.matric_number = u'234'
134        student.sex = u'f'
135        self.app['students'].addStudent(student)
136        self.student_id = student.student_id
137        self.student = self.app['students'][self.student_id]
138        self.student['studycourse'].current_session = 2004
139        self.student['studycourse'].entry_session = 2004
140        # The students_catalog must be informed that the
141        # session attribute has changed
142        notify(grok.ObjectModifiedEvent(self.student))
143
144        # Set accommodation_session
145        self.app['hostels'].accommodation_session = 2004
146
147        # Create a hostel
148        hostel = Hostel()
149        hostel.hostel_id = u'hall-x'
150        self.app['hostels'][hostel.hostel_id] = hostel
151
152        # Create a bed
153        bed = Bed()
154        bed.bed_id = u'hall_block_room_bed'
155        bed.bed_number = 1
156        bed.bed_type = u'a_b_c'
157        self.app['hostels'][hostel.hostel_id][bed.bed_id] = bed
158
159        self.container_path = 'http://localhost/app/hostels'
160        self.student_path = 'http://localhost/app/students/%s' % self.student_id
161        self.manage_container_path = self.container_path + '/@@manage'
162        self.add_hostel_path = self.container_path + '/addhostel'
163
164        # Put the prepopulated site into test ZODB and prepare test
165        # browser
166        self.browser = Browser()
167        self.browser.handleErrors = False
168
169        self.logfile = os.path.join(
170            self.app['datacenter'].storage, 'logs', 'hostels.log')
171
172    def tearDown(self):
173        super(HostelsFullSetup, self).tearDown()
174        clearSite()
175        shutil.rmtree(self.dc_root)
176
177class BedCatalogTests(HostelsFullSetup):
178
179    layer = FunctionalLayer
180
181    def test_get_catalog(self):
182        # We can get a beds catalog if we wish
183        cat = queryUtility(ICatalog, name='beds_catalog')
184        assert cat is not None
185
186    def test_search_by_type(self):
187        # We can find a certain bed
188        cat = queryUtility(ICatalog, name='beds_catalog')
189        results = cat.searchResults(bed_type=(u'a_b_c', u'a_b_c'))
190        results = [x for x in results] # Turn results generator into list
191        assert len(results) == 1
192        assert results[0] is self.app['hostels']['hall-x']['hall_block_room_bed']
193
194    def test_search_by_owner(self):
195        # We can find a certain bed
196        myobj = self.app['hostels']['hall-x']['hall_block_room_bed']
197        myobj.owner = u'abc'
198        notify(grok.ObjectModifiedEvent(myobj))
199        cat = queryUtility(ICatalog, name='beds_catalog')
200        results = cat.searchResults(owner=(u'abc', u'abc'))
201        results = [x for x in results] # Turn results generator into list
202        assert len(results) == 1
203        assert results[0] is self.app['hostels']['hall-x']['hall_block_room_bed']
204
205class HostelsUITests(HostelsFullSetup):
206
207    layer = FunctionalLayer
208
209    def test_anonymous_access(self):
210        # Anonymous users can't access hostels containers
211        self.assertRaises(
212            Unauthorized, self.browser.open, self.manage_container_path)
213        return
214
215    def test_add_search_edit_delete_manage_hostels(self):
216        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
217        self.browser.open(self.container_path)
218        self.browser.getLink("Manage accommodation").click()
219        self.assertEqual(self.browser.headers['Status'], '200 Ok')
220        self.assertEqual(self.browser.url, self.manage_container_path)
221        self.browser.getControl("Add hostel").click()
222        self.assertEqual(self.browser.headers['Status'], '200 Ok')
223        self.assertEqual(self.browser.url, self.add_hostel_path)
224        self.browser.getControl("Create hostel").click()
225        self.assertEqual(self.browser.headers['Status'], '200 Ok')
226        self.assertTrue('Hostel created' in self.browser.contents)
227        self.browser.open(self.container_path + '/addhostel')
228        self.browser.getControl("Create hostel").click()
229        self.assertTrue('The hostel already exists' in self.browser.contents)
230        hall = self.app['hostels']['hall-1']
231        hall.blocks_for_female = ['A','B']
232        self.browser.open(self.container_path + '/hall-1')
233        expected = '''...<ul id="form.blocks_for_female" ><li>Block A</li>
234<li>Block B</li></ul>...'''
235        self.assertMatches(expected,self.browser.contents)
236        self.browser.open(self.container_path + '/hall-1/manage')
237        self.browser.getControl(name="form.rooms_per_floor").value = '1'
238        self.browser.getControl("Save").click()
239        self.assertTrue('Form has been saved' in self.browser.contents)
240        # Since the testbrowser does not support Javascrip the
241        # save action cleared the settings above and we have to set them
242        # again
243        self.assertTrue(len(hall.blocks_for_female) == 0)
244        hall.blocks_for_female = ['A','B']
245        hall.beds_for_fresh = ['A']
246        hall.beds_for_returning = ['B']
247        hall.beds_for_final = ['C']
248        hall.beds_for_all = ['D','E']
249        self.browser.getControl("Update all beds").click()
250        expected = '...0 empty beds removed, 10 beds added, 0 occupied beds modified ()...'
251        self.assertMatches(expected,self.browser.contents)
252        cat = queryUtility(ICatalog, name='beds_catalog')
253        results = cat.searchResults(
254            bed_type=('regular_female_all', 'regular_female_all'))
255        results = [x for x in results]
256        assert len(results) == 4
257        # Reserve bed
258        self.browser.getControl("Switch reservation", index=0).click()
259        self.assertTrue('No item selected' in self.browser.contents)
260        ctrl = self.browser.getControl(name='val_id')
261        ctrl.getControl(value='hall-1_A_101_A').selected = True
262        ctrl.getControl(value='hall-1_A_101_B').selected = True
263        ctrl.getControl(value='hall-1_A_101_C').selected = True
264        ctrl.getControl(value='hall-1_A_101_D').selected = True
265        self.browser.getControl("Switch reservation", index=0).click()
266        self.assertTrue('Successfully switched beds: hall-1_A_101_A (reserved)'
267            in self.browser.contents)
268        assert self.app['hostels']['hall-1'][
269            'hall-1_A_101_D'].bed_type == 'regular_female_reserved'
270        self.assertTrue('<div>A_101_A</div>' in self.browser.contents)
271
272        # Change hostel configuration
273        hall.beds_for_all = ['D']
274        self.browser.getControl("Update all beds").click()
275        expected = '...10 empty beds removed, 8 beds added, 0 occupied beds modified...'
276        self.assertMatches(expected,self.browser.contents)
277        results = cat.searchResults(
278            bed_type=('regular_female_all', 'regular_female_all'))
279        results = [x for x in results]
280        assert len(results) == 1
281        # Unreserve bed
282        ctrl = self.browser.getControl(name='val_id')
283        ctrl.getControl(value='hall-1_A_101_A').selected = True
284        ctrl.getControl(value='hall-1_A_101_B').selected = True
285        ctrl.getControl(value='hall-1_A_101_C').selected = True
286        ctrl.getControl(value='hall-1_A_101_D').selected = True
287        self.browser.getControl("Switch reservation", index=0).click()
288        assert self.app['hostels']['hall-1'][
289            'hall-1_A_101_D'].bed_type == 'regular_female_all'
290        self.assertFalse(expected in self.browser.contents)
291        # Release bed which has previously been booked
292        bedticket = BedTicket()
293        bedticket.ticket_id = u'2004'
294        bedticket.bed_coordinates = u'anything'
295        self.student['accommodation'].addBedTicket(bedticket)
296        self.app['hostels']['hall-1']['hall-1_A_101_D'].owner = self.student_id
297        self.browser.open(self.container_path + '/hall-1/manage')
298        ctrl = self.browser.getControl(name='val_id')
299        self.browser.getControl("Release selected beds", index=0).click()
300        self.assertMatches("...No item selected...", self.browser.contents)
301        ctrl = self.browser.getControl(name='val_id')
302        ctrl.getControl(value='hall-1_A_101_D').selected = True
303        self.browser.getControl("Release selected beds", index=0).click()
304        self.assertMatches(
305          '...Successfully released beds: hall-1_A_101_D (%s)...' % self.student_id,
306          self.browser.contents)
307        self.assertMatches(bedticket.bed_coordinates,
308          u' -- booking cancelled on <YYYY-MM-DD hh:mm:ss> UTC --')
309        # If we release a free be, nothing will happen
310        ctrl = self.browser.getControl(name='val_id')
311        ctrl.getControl(value='hall-1_A_101_D').selected = True
312        self.browser.getControl("Release selected beds", index=0).click()
313        self.assertMatches(
314          '...No allocated bed selected...', self.browser.contents)
315        # Managers can manually allocate students after cancellation
316        self.browser.open(self.container_path + '/hall-1/hall-1_A_101_A')
317        self.browser.getControl(name="form.owner").value = [self.student_id]
318        self.browser.getControl("Save").click()
319        self.assertMatches("...Form has been saved...", self.browser.contents)
320        # If we open the same form again, we will be redirected to hostel
321        # manage page. Beds must be released first before they can be
322        # allocated to other students.
323        self.browser.open(self.container_path + '/hall-1/hall-1_A_101_A')
324        self.assertEqual(self.browser.url,
325            self.container_path + '/hall-1/@@manage?tab2')
326        # Updating the beds again will not affect the allocation and also
327        # the bed numbering remains the same
328        old_number = self.app['hostels']['hall-1']['hall-1_A_101_A'].bed_number
329        old_owner = self.app['hostels']['hall-1']['hall-1_A_101_A'].owner
330        self.browser.getControl("Update all beds").click()
331        # 7 beds have been removed and re-added, 1 bed remains untouched
332        expected = '...7 empty beds removed, 7 beds added, 0 occupied beds modified...'
333        self.assertMatches(expected,self.browser.contents)
334        new_number = self.app['hostels']['hall-1']['hall-1_A_101_A'].bed_number
335        new_owner = self.app['hostels']['hall-1']['hall-1_A_101_A'].owner
336        self.assertEqual(new_number, old_number)
337        self.assertEqual(new_owner, old_owner)
338        # If we change the bed type of an allocated bed, the modification will
339        # be indicated
340        hall.blocks_for_female = ['B']
341        hall.blocks_for_male = ['A']
342        self.browser.getControl("Update all beds").click()
343        expected = '...7 empty beds removed, 7 beds added, ' + \
344            '1 occupied beds modified (hall-1_A_101_A )...'
345        self.assertMatches(expected,self.browser.contents)
346        new_number = self.app['hostels']['hall-1']['hall-1_A_101_A'].bed_number
347        # Also the number of the bed has changed.
348        self.assertFalse(new_number == old_number)
349        # The number of occupied bed are displayed on container page.
350        self.browser.open(self.container_path)
351        self.assertTrue('1 of 8' in self.browser.contents)
352        # Remove entire hostel
353        self.browser.open(self.manage_container_path)
354        ctrl = self.browser.getControl(name='val_id')
355        value = ctrl.options[0]
356        ctrl.getControl(value=value).selected = True
357        self.browser.getControl("Remove selected", index=0).click()
358        self.assertTrue('Successfully removed' in self.browser.contents)
359        # Catalog is empty
360        results = cat.searchResults(
361            bed_type=('regular_female_all', 'regular_female_all'))
362        results = [x for x in results]
363        assert len(results) == 0
364
365    def test_clear_hostels(self):
366        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
367        self.browser.open(self.container_path)
368        self.browser.getLink("Manage accommodation").click()
369        self.browser.getControl("Add hostel").click()
370        self.browser.getControl("Create hostel").click()
371        hall = self.app['hostels']['hall-1']
372        hall.blocks_for_female = ['A','B']
373        hall.rooms_per_floor = 1
374        hall.beds_for_fresh = ['A']
375        hall.beds_for_returning = ['B']
376        hall.beds_for_final = ['C']
377        hall.beds_for_all = ['D','E']
378        self.browser.open(self.container_path + '/hall-1/manage')
379        self.browser.getControl("Update all beds").click()
380        cat = queryUtility(ICatalog, name='beds_catalog')
381        results = cat.searchResults(bed_type=(None, None))
382        self.assertEqual(len(results), 11)
383        self.browser.getControl("Clear hostel").click()
384        self.assertEqual(len(self.app['hostels']['hall-1']), 0)
385        # Only the bed in hall-x remains in the catalog.
386        results = cat.searchResults(bed_type=(None, None))
387        self.assertEqual(len(results), 1)
388        # We can clear all hostels at the same time.
389        self.browser.open(self.manage_container_path)
390        self.browser.getControl("Clear all hostels").click()
391        results = cat.searchResults(bed_type=(None, None))
392        self.assertEqual(len(results), 0)
393        # Both actions have been logged.
394        logcontent = open(self.logfile).read()
395        self.assertTrue('INFO - zope.mgr - hostels.browser.HostelManageFormPage'
396                        ' - hall-1 - cleared' in logcontent)
397        self.assertTrue('zope.mgr - hostels.browser.HostelsContainerManagePage'
398                        ' - hostels - all hostels cleared' in logcontent)
399
400class ExportTests(HostelsFullSetup):
401
402    layer = FunctionalLayer
403
404    def setUp(self):
405        super(ExportTests, self).setUp()
406        self.workdir = tempfile.mkdtemp()
407        self.outfile = os.path.join(self.workdir, 'myoutput.csv')
408        return
409
410    def test_export_hostels(self):
411        exporter = HostelExporter()
412        exporter.export_all(self.app, self.outfile)
413        result = open(self.outfile, 'rb').read()
414        self.assertEqual(
415            result,
416            'beds_for_all,beds_for_final,beds_for_fresh,beds_for_pre,'
417            'beds_for_returning,beds_reserved,blocks_for_female,'
418            'blocks_for_male,floors_per_block,hostel_id,hostel_name,'
419            'rooms_per_floor,sort_id,special_handling\r\n,,,,,[],,,1,'
420            'hall-x,Hall 1,2,10,regular\r\n'
421            )
422        return
423
424    def test_export_beds(self):
425        exporter = BedExporter()
426        exporter.export_all(self.app, self.outfile)
427        result = open(self.outfile, 'rb').read()
428        self.assertEqual(
429            result,
430            'bed_id,bed_number,bed_type,owner,hall,block,room,bed,'
431            'special_handling,sex,bt\r\nhall_block_room_bed,1,a_b_c,,'
432            'hall,block,room,bed,a,b,c\r\n'
433            )
434        return
435
436class HostelProcessorTest(HostelsFullSetup):
437
438    layer = FunctionalLayer
439
440    def test_import(self):
441        self.processor = HostelProcessor()
442        self.workdir = tempfile.mkdtemp()
443        self.csv_file = os.path.join(self.workdir, 'sample_student_data.csv')
444        open(self.csv_file, 'wb').write(HOSTEL_SAMPLE_DATA)
445        num, num_warns, fin_file, fail_file = self.processor.doImport(
446            self.csv_file, HOSTEL_HEADER_FIELDS)
447        self.assertEqual(num_warns,0)
448        self.assertEqual(len(self.app['hostels'].keys()), 11) # including hall-x
449        self.assertEqual(self.app['hostels'][
450            'block-a-upper-hostel'].hostel_id,'block-a-upper-hostel')
451        self.assertEqual(self.app['hostels'][
452            'block-a-upper-hostel'].beds_for_final, ['A', 'B'])
453        logcontent = open(self.logfile).read()
454        self.assertTrue(
455            "Hostel Processor - block-a-upper-hostel - "
456            "Record updated: beds_for_pre=['G'], floors_per_block=1, "
457            "beds_for_final=['A', 'B'], rooms_per_floor=32, "
458            "blocks_for_male=[], hostel_id=block-a-upper-hostel, "
459            "sort_id=20, beds_for_returning=['C', 'D'], "
460            "hostel_name=Block A Upper Hostel, beds_for_fresh=['E', 'F'], "
461            "blocks_for_female=['A'], beds_for_all=[], beds_reserved=[]"
462            in logcontent)
463        shutil.rmtree(os.path.dirname(fin_file))
464        return
Note: See TracBrowser for help on using the repository browser.