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

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

Implement HostelProcessor?.

  • Property svn:keywords set to Id
File size: 20.2 KB
RevLine 
[7195]1## $Id: tests.py 9202 2012-09-19 10:17:44Z henrik $
2##
[6951]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.
[7195]8##
[6951]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.
[7195]13##
[6951]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"""
[6988]19Tests for hostels and their UI components.
[6951]20"""
[9197]21import os
[6961]22import shutil
23import tempfile
[7003]24import grok
[8686]25import pytz
26from datetime import datetime, timedelta
[7003]27from zope.event import notify
[6951]28from zope.interface.verify import verifyClass, verifyObject
[6961]29from zope.component.hooks import setSite, clearSite
30from zope.testbrowser.testing import Browser
31from zope.security.interfaces import Unauthorized
[6972]32from zope.catalog.interfaces import ICatalog
33from zope.component import queryUtility
[7811]34from waeup.kofa.app import University
35from waeup.kofa.hostels.interfaces import (
[6963]36    IHostelsContainer, IHostel, IBed)
[7811]37from waeup.kofa.hostels.container import HostelsContainer
38from waeup.kofa.hostels.hostel import Hostel, Bed
[9202]39from waeup.kofa.hostels.batching import HostelProcessor
[9200]40from waeup.kofa.hostels.export import BedExporter, HostelExporter
[7811]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
[6951]45
[9202]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
[6951]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            )
[6963]75        self.assertTrue(
76            verifyClass(
77                IBed, Bed)
78            )
79        self.assertTrue(
80            verifyObject(
81                IBed, Bed())
82            )
[6951]83        return
84
85    def test_base(self):
86        # We cannot call the fundamental methods of a base in that case
87        container = HostelsContainer()
[7077]88        hostel = Hostel()
[6951]89        self.assertRaises(
90            NotImplementedError, container.archive)
91        self.assertRaises(
92            NotImplementedError, container.clear)
[7077]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)
[8686]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)
[6961]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
[7045]128        # Add student with subobjects
129        student = Student()
[7357]130        student.firstname = u'Anna'
131        student.lastname = u'Tester'
[7045]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
[7068]140        # The students_catalog must be informed that the
141        # session attribute has changed
142        notify(grok.ObjectModifiedEvent(self.student))
[7045]143
144        # Set accommodation_session
[8685]145        self.app['hostels'].accommodation_session = 2004
[7045]146
[6972]147        # Create a hostel
148        hostel = Hostel()
[6973]149        hostel.hostel_id = u'hall-x'
[6972]150        self.app['hostels'][hostel.hostel_id] = hostel
151
152        # Create a bed
153        bed = Bed()
[9200]154        bed.bed_id = u'hall_block_room_bed'
[6972]155        bed.bed_number = 1
[9200]156        bed.bed_type = u'a_b_c'
[6972]157        self.app['hostels'][hostel.hostel_id][bed.bed_id] = bed
158
[6961]159        self.container_path = 'http://localhost/app/hostels'
[7068]160        self.student_path = 'http://localhost/app/students/%s' % self.student_id
[6961]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
[9202]169        self.logfile = os.path.join(
170            self.app['datacenter'].storage, 'logs', 'hostels.log')
171
[6961]172    def tearDown(self):
173        super(HostelsFullSetup, self).tearDown()
174        clearSite()
175        shutil.rmtree(self.dc_root)
176
[6972]177class BedCatalogTests(HostelsFullSetup):
178
179    layer = FunctionalLayer
180
181    def test_get_catalog(self):
[7045]182        # We can get a beds catalog if we wish
[6972]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')
[9200]189        results = cat.searchResults(bed_type=(u'a_b_c', u'a_b_c'))
[6972]190        results = [x for x in results] # Turn results generator into list
191        assert len(results) == 1
[9200]192        assert results[0] is self.app['hostels']['hall-x']['hall_block_room_bed']
[6972]193
[7003]194    def test_search_by_owner(self):
195        # We can find a certain bed
[9200]196        myobj = self.app['hostels']['hall-x']['hall_block_room_bed']
[7003]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
[9200]203        assert results[0] is self.app['hostels']['hall-x']['hall_block_room_bed']
[7003]204
[6961]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
[6985]215    def test_add_search_edit_delete_manage_hostels(self):
[6961]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)
[6988]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)
[6973]230        hall = self.app['hostels']['hall-1']
[6961]231        hall.blocks_for_female = ['A','B']
[6973]232        self.browser.open(self.container_path + '/hall-1')
[6961]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)
[6973]236        self.browser.open(self.container_path + '/hall-1/manage')
[6985]237        self.browser.getControl(name="form.rooms_per_floor").value = '1'
[6962]238        self.browser.getControl("Save").click()
239        self.assertTrue('Form has been saved' in self.browser.contents)
[6985]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']
[6988]245        hall.beds_for_fresh = ['A']
246        hall.beds_for_returning = ['B']
247        hall.beds_for_final = ['C']
[6985]248        hall.beds_for_all = ['D','E']
249        self.browser.getControl("Update all beds").click()
[6988]250        expected = '...0 empty beds removed, 10 beds added, 0 occupied beds modified ()...'
[6985]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
[6988]257        # Reserve bed
258        self.browser.getControl("Switch reservation", index=0).click()
259        self.assertTrue('No item selected' in self.browser.contents)
[6985]260        ctrl = self.browser.getControl(name='val_id')
[6988]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
[6985]264        ctrl.getControl(value='hall-1_A_101_D').selected = True
265        self.browser.getControl("Switch reservation", index=0).click()
[6988]266        self.assertTrue('Successfully switched beds: hall-1_A_101_A (reserved)'
[6985]267            in self.browser.contents)
[6988]268        assert self.app['hostels']['hall-1'][
[6985]269            'hall-1_A_101_D'].bed_type == 'regular_female_reserved'
[7802]270        self.assertTrue('<div>A_101_A</div>' in self.browser.contents)
271
[6985]272        # Change hostel configuration
273        hall.beds_for_all = ['D']
274        self.browser.getControl("Update all beds").click()
[6988]275        expected = '...10 empty beds removed, 8 beds added, 0 occupied beds modified...'
[6985]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]
[6988]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)
[7045]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')
[7068]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')
[7045]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,
[8234]308          u' -- booking cancelled on <YYYY-MM-DD hh:mm:ss> UTC --')
[7070]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)
[9196]315        # Managers can manually allocate students after cancellation
[7068]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)
[7070]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,
[7484]325            self.container_path + '/hall-1/@@manage?tab2')
[7070]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)
[9196]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)
[6985]352        # Remove entire hostel
[6962]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)
[6985]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
[9197]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.
[9202]394        logcontent = open(self.logfile).read()
[9197]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)
[9200]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            )
[9202]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))
[9200]464        return
Note: See TracBrowser for help on using the repository browser.