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

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

Use addBedticket properly.

Implement maintenance fee payment in base package and ensure that maintenance (rent) can only be paid if bed has been booked in current session.

  • Property svn:keywords set to Id
File size: 21.3 KB
Line 
1## $Id: tests.py 9423 2012-10-26 07:55:45Z 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.booking_session = 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 eligible students after cancellation
316        self.browser.open(self.container_path + '/hall-1/hall-1_A_101_A')
317        # 'not occupied' is not accepted
318        self.browser.getControl("Save").click()
319        self.assertMatches(
320            "...No valid student id...",
321            self.browser.contents)
322        # Invalid student ids are not accepted
323        self.browser.getControl(name="form.owner").value = 'nonsense'
324        self.browser.getControl("Save").click()
325        self.assertMatches(
326            "...Either student does not exist or student "
327            "is not in accommodation session...",
328            self.browser.contents)
329        self.browser.getControl(name="form.owner").value = self.student_id
330        self.browser.getControl("Save").click()
331        self.assertMatches("...Form has been saved...", self.browser.contents)
332        # Students can only be allocated once
333        self.browser.open(self.container_path + '/hall-1/hall-1_A_101_B')
334        self.browser.getControl(name="form.owner").value = self.student_id
335        self.browser.getControl("Save").click()
336        self.assertMatches(
337            "...This student resides in bed hall-1_A_101_A...",
338            self.browser.contents)
339        # If we open the same form again, we will be redirected to hostel
340        # manage page. Beds must be released first before they can be
341        # allocated to other students.
342        self.browser.open(self.container_path + '/hall-1/hall-1_A_101_A')
343        self.assertEqual(self.browser.url,
344            self.container_path + '/hall-1/@@manage?tab2')
345        # Updating the beds again will not affect the allocation and also
346        # the bed numbering remains the same
347        old_number = self.app['hostels']['hall-1']['hall-1_A_101_A'].bed_number
348        old_owner = self.app['hostels']['hall-1']['hall-1_A_101_A'].owner
349        self.browser.getControl("Update all beds").click()
350        # 7 beds have been removed and re-added, 1 bed remains untouched
351        expected = '...7 empty beds removed, 7 beds added, 0 occupied beds modified...'
352        self.assertMatches(expected,self.browser.contents)
353        new_number = self.app['hostels']['hall-1']['hall-1_A_101_A'].bed_number
354        new_owner = self.app['hostels']['hall-1']['hall-1_A_101_A'].owner
355        self.assertEqual(new_number, old_number)
356        self.assertEqual(new_owner, old_owner)
357        # If we change the bed type of an allocated bed, the modification will
358        # be indicated
359        hall.blocks_for_female = ['B']
360        hall.blocks_for_male = ['A']
361        self.browser.getControl("Update all beds").click()
362        expected = '...7 empty beds removed, 7 beds added, ' + \
363            '1 occupied beds modified (hall-1_A_101_A )...'
364        self.assertMatches(expected,self.browser.contents)
365        new_number = self.app['hostels']['hall-1']['hall-1_A_101_A'].bed_number
366        # Also the number of the bed has changed.
367        self.assertFalse(new_number == old_number)
368        # The number of occupied beds are displayed on container page.
369        self.browser.open(self.container_path)
370        self.assertTrue('1 of 8' in self.browser.contents)
371        # Remove entire hostel
372        self.browser.open(self.manage_container_path)
373        ctrl = self.browser.getControl(name='val_id')
374        value = ctrl.options[0]
375        ctrl.getControl(value=value).selected = True
376        self.browser.getControl("Remove selected", index=0).click()
377        self.assertTrue('Successfully removed' in self.browser.contents)
378        # Catalog is empty
379        results = cat.searchResults(
380            bed_type=('regular_female_all', 'regular_female_all'))
381        results = [x for x in results]
382        assert len(results) == 0
383
384    def test_clear_hostels(self):
385        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
386        self.browser.open(self.container_path)
387        self.browser.getLink("Manage accommodation").click()
388        self.browser.getControl("Add hostel").click()
389        self.browser.getControl("Create hostel").click()
390        hall = self.app['hostels']['hall-1']
391        hall.blocks_for_female = ['A','B']
392        hall.rooms_per_floor = 1
393        hall.beds_for_fresh = ['A']
394        hall.beds_for_returning = ['B']
395        hall.beds_for_final = ['C']
396        hall.beds_for_all = ['D','E']
397        self.browser.open(self.container_path + '/hall-1/manage')
398        self.browser.getControl("Update all beds").click()
399        cat = queryUtility(ICatalog, name='beds_catalog')
400        results = cat.searchResults(bed_type=(None, None))
401        self.assertEqual(len(results), 11)
402        self.browser.getControl("Clear hostel").click()
403        self.assertEqual(len(self.app['hostels']['hall-1']), 0)
404        # Only the bed in hall-x remains in the catalog.
405        results = cat.searchResults(bed_type=(None, None))
406        self.assertEqual(len(results), 1)
407        # We can clear all hostels at the same time.
408        self.browser.open(self.manage_container_path)
409        self.browser.getControl("Clear all hostels").click()
410        results = cat.searchResults(bed_type=(None, None))
411        self.assertEqual(len(results), 0)
412        # Both actions have been logged.
413        logcontent = open(self.logfile).read()
414        self.assertTrue('INFO - zope.mgr - hostels.browser.HostelManageFormPage'
415                        ' - hall-1 - cleared' in logcontent)
416        self.assertTrue('zope.mgr - hostels.browser.HostelsContainerManagePage'
417                        ' - hostels - all hostels cleared' in logcontent)
418
419class ExportTests(HostelsFullSetup):
420
421    layer = FunctionalLayer
422
423    def setUp(self):
424        super(ExportTests, self).setUp()
425        self.workdir = tempfile.mkdtemp()
426        self.outfile = os.path.join(self.workdir, 'myoutput.csv')
427        return
428
429    def test_export_hostels(self):
430        exporter = HostelExporter()
431        exporter.export_all(self.app, self.outfile)
432        result = open(self.outfile, 'rb').read()
433        self.assertEqual(
434            result,
435            'beds_for_all,beds_for_final,beds_for_fresh,beds_for_pre,'
436            'beds_for_returning,beds_reserved,blocks_for_female,'
437            'blocks_for_male,floors_per_block,hostel_id,hostel_name,'
438            'rooms_per_floor,sort_id,special_handling\r\n,,,,,[],,,1,'
439            'hall-x,Hall 1,2,10,regular\r\n'
440            )
441        return
442
443    def test_export_beds(self):
444        exporter = BedExporter()
445        exporter.export_all(self.app, self.outfile)
446        result = open(self.outfile, 'rb').read()
447        self.assertEqual(
448            result,
449            'bed_id,bed_number,bed_type,owner,hall,block,room,bed,'
450            'special_handling,sex,bt\r\nhall_block_room_bed,1,a_b_c,,'
451            'hall,block,room,bed,a,b,c\r\n'
452            )
453        return
454
455    def tearDown(self):
456        super(ExportTests, self).tearDown()
457        clearSite()
458        shutil.rmtree(os.path.dirname(self.outfile))
459
460class HostelProcessorTest(HostelsFullSetup):
461
462    layer = FunctionalLayer
463
464    def test_import(self):
465        self.processor = HostelProcessor()
466        self.workdir = tempfile.mkdtemp()
467        self.csv_file = os.path.join(self.workdir, 'sample_hostel_data.csv')
468        open(self.csv_file, 'wb').write(HOSTEL_SAMPLE_DATA)
469        num, num_warns, fin_file, fail_file = self.processor.doImport(
470            self.csv_file, HOSTEL_HEADER_FIELDS)
471        self.assertEqual(num_warns,0)
472        self.assertEqual(len(self.app['hostels'].keys()), 11) # including hall-x
473        self.assertEqual(self.app['hostels'][
474            'block-a-upper-hostel'].hostel_id,'block-a-upper-hostel')
475        self.assertEqual(self.app['hostels'][
476            'block-a-upper-hostel'].beds_for_final, ['A', 'B'])
477        logcontent = open(self.logfile).read()
478        self.assertTrue(
479            "Hostel Processor - block-a-upper-hostel - "
480            "Record updated: beds_for_pre=['G'], floors_per_block=1, "
481            "beds_for_final=['A', 'B'], rooms_per_floor=32, "
482            "blocks_for_male=[], hostel_id=block-a-upper-hostel, "
483            "sort_id=20, beds_for_returning=['C', 'D'], "
484            "hostel_name=Block A Upper Hostel, beds_for_fresh=['E', 'F'], "
485            "blocks_for_female=['A'], beds_for_all=[], beds_reserved=[]"
486            in logcontent)
487        shutil.rmtree(os.path.dirname(fin_file))
488        shutil.rmtree(self.workdir)
489        return
Note: See TracBrowser for help on using the repository browser.