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

Last change on this file since 17902 was 17412, checked in by Henrik Bettermann, 20 months ago

Add ReleaseExpiredAllocationsPage2 which allows to release unpaid beds in single hostels.

  • Property svn:keywords set to Id
File size: 35.9 KB
RevLine 
[7195]1## $Id: tests.py 17412 2023-05-17 18:21:10Z 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
[17313]33from zope.component import queryUtility, getUtility, createObject
[7811]34from waeup.kofa.app import University
35from waeup.kofa.hostels.interfaces import (
[13483]36    IHostelsContainer, IHostel, IBed, IHostelsUtils)
[13316]37from waeup.kofa.hostels.vocabularies import NOT_OCCUPIED
[7811]38from waeup.kofa.hostels.container import HostelsContainer
39from waeup.kofa.hostels.hostel import Hostel, Bed
[13432]40from waeup.kofa.hostels.batching import HostelProcessor, BedProcessor
[9200]41from waeup.kofa.hostels.export import BedExporter, HostelExporter
[7811]42from waeup.kofa.testing import (FunctionalLayer, FunctionalTestCase)
43from waeup.kofa.students.student import Student
44from waeup.kofa.students.accommodation import BedTicket
45from waeup.kofa.university.department import Department
[6951]46
[9202]47HOSTEL_SAMPLE_DATA = open(
48    os.path.join(os.path.dirname(__file__), 'sample_hostel_data.csv'),
49    'rb').read()
50
51HOSTEL_HEADER_FIELDS = HOSTEL_SAMPLE_DATA.split(
52    '\n')[0].split(',')
53
[13432]54BED_SAMPLE_DATA = open(
55    os.path.join(os.path.dirname(__file__), 'sample_bed_data.csv'),
56    'rb').read()
57
[17313]58BED_SAMPLE_DATA_2 = open(
59    os.path.join(os.path.dirname(__file__), 'sample_bed_data_2.csv'),
60    'rb').read()
61
[13432]62BED_HEADER_FIELDS = BED_SAMPLE_DATA.split(
63    '\n')[0].split(',')
64
[6951]65class HostelsContainerTestCase(FunctionalTestCase):
66
67    layer = FunctionalLayer
68
69    def test_interfaces(self):
70        # Make sure the correct interfaces are implemented.
71        self.assertTrue(
72            verifyClass(
73                IHostelsContainer, HostelsContainer)
74            )
75        self.assertTrue(
76            verifyObject(
77                IHostelsContainer, HostelsContainer())
78            )
79        self.assertTrue(
80            verifyClass(
81                IHostel, Hostel)
82            )
83        self.assertTrue(
84            verifyObject(
85                IHostel, Hostel())
86            )
[6963]87        self.assertTrue(
88            verifyClass(
89                IBed, Bed)
90            )
[13170]91        bed = Bed()
92        bed.bed_id = u'a_b_c_d'
93        bed.bed_type = u'a_b_c'
[6963]94        self.assertTrue(
95            verifyObject(
[13170]96                IBed, bed)
[6963]97            )
[6951]98        return
99
100    def test_base(self):
101        # We cannot call the fundamental methods of a base in that case
102        container = HostelsContainer()
[7077]103        hostel = Hostel()
104        # We cannot add arbitrary objects
105        department = Department()
106        self.assertRaises(
107            TypeError, container.addHostel, department)
108        self.assertRaises(
109            TypeError, hostel.addBed, department)
[8686]110        # Application is expired if startdate or enddate are not set
111        # or current datetime is outside application period.
112        self.assertTrue(container.expired)
113        delta = timedelta(days=10)
114        container.startdate = datetime.now(pytz.utc) - delta
115        self.assertTrue(container.expired)
116        container.enddate = datetime.now(pytz.utc) + delta
117        self.assertFalse(container.expired)
[6961]118
119class HostelsFullSetup(FunctionalTestCase):
120
121    def setUp(self):
122        super(HostelsFullSetup, self).setUp()
123
124        # Setup a sample site for each test
125        app = University()
126        self.dc_root = tempfile.mkdtemp()
127        app['datacenter'].setStoragePath(self.dc_root)
128
129        # Prepopulate the ZODB...
130        self.getRootFolder()['app'] = app
131        # we add the site immediately after creation to the
132        # ZODB. Catalogs and other local utilities are not setup
133        # before that step.
134        self.app = self.getRootFolder()['app']
135        # Set site here. Some of the following setup code might need
136        # to access grok.getSite() and should get our new app then
137        setSite(app)
138
[7045]139        # Add student with subobjects
140        student = Student()
[7357]141        student.firstname = u'Anna'
142        student.lastname = u'Tester'
[7045]143        student.reg_number = u'123'
144        student.matric_number = u'234'
145        student.sex = u'f'
146        self.app['students'].addStudent(student)
147        self.student_id = student.student_id
148        self.student = self.app['students'][self.student_id]
149        self.student['studycourse'].current_session = 2004
150        self.student['studycourse'].entry_session = 2004
[13440]151
[7068]152        # The students_catalog must be informed that the
153        # session attribute has changed
154        notify(grok.ObjectModifiedEvent(self.student))
[7045]155
156        # Set accommodation_session
[8685]157        self.app['hostels'].accommodation_session = 2004
[7045]158
[6972]159        # Create a hostel
160        hostel = Hostel()
[6973]161        hostel.hostel_id = u'hall-x'
[6972]162        self.app['hostels'][hostel.hostel_id] = hostel
163
164        # Create a bed
165        bed = Bed()
[17412]166        bed.bed_id = u'hall-x_block_room_bed'
[6972]167        bed.bed_number = 1
[13483]168        bed.bed_type = u'regular_male_fr'
[6972]169        self.app['hostels'][hostel.hostel_id][bed.bed_id] = bed
170
[6961]171        self.container_path = 'http://localhost/app/hostels'
[7068]172        self.student_path = 'http://localhost/app/students/%s' % self.student_id
[6961]173        self.manage_container_path = self.container_path + '/@@manage'
174        self.add_hostel_path = self.container_path + '/addhostel'
175
176        # Put the prepopulated site into test ZODB and prepare test
177        # browser
178        self.browser = Browser()
179        self.browser.handleErrors = False
180
[9202]181        self.logfile = os.path.join(
182            self.app['datacenter'].storage, 'logs', 'hostels.log')
183
[6961]184    def tearDown(self):
185        super(HostelsFullSetup, self).tearDown()
186        clearSite()
187        shutil.rmtree(self.dc_root)
188
[13316]189class HostelsContainerTests(HostelsFullSetup):
190
191    layer = FunctionalLayer
192
[17412]193    def test_release_expired_allocations_portalwide(self):
[15250]194        self.app['hostels'].allocation_expiration = 7
[13319]195        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
[13316]196        cat = queryUtility(ICatalog, name='beds_catalog')
197        bedticket = BedTicket()
198        bedticket.booking_session = 2004
199        bedticket.bed_coordinates = u'anything'
200        self.student['accommodation'].addBedTicket(bedticket)
201        self.app[
[17412]202            'hostels']['hall-x']['hall-x_block_room_bed'].owner = self.student_id
[13316]203        notify(grok.ObjectModifiedEvent(
[17412]204            self.app['hostels']['hall-x']['hall-x_block_room_bed']))
[13533]205        self.assertEqual(
[17412]206            self.app['hostels']['hall-x']['hall-x_block_room_bed'].bed_type,
[13533]207            'regular_male_fr')
[13316]208        results = cat.searchResults(owner=(self.student_id, self.student_id))
209        self.assertEqual(len(results), 1)
[13319]210        self.browser.open(self.container_path + '/releaseexpired')
[13529]211        self.assertTrue('Portal must be in maintenance mode for releasing expired bed allocations'
212            in self.browser.contents)
213        grok.getSite()['configuration'].maintmode_enabled_by = u'any_id'
214        self.browser.open(self.container_path + '/releaseexpired')
[13319]215        self.assertTrue('No bed released' in self.browser.contents)
[13316]216        delta = timedelta(days=10)
217        bedticket.booking_date = datetime.utcnow() - delta
[13319]218        self.browser.open(self.container_path + '/releaseexpired')
219        self.assertTrue(
[17412]220            'Successfully released beds: hall-x_block_room_bed (K1000000)'
[13319]221            in self.browser.contents)
[13316]222        results = cat.searchResults(owner=(self.student_id, self.student_id))
223        self.assertEqual(len(results), 0)
224        self.assertMatches(bedticket.display_coordinates,
225            '-- booking expired (2015-10-14 08:35:38 UTC) --')
[13533]226        # The owner has been removed and the bed reserved.
[13316]227        self.assertEqual(
[17412]228            self.app['hostels']['hall-x']['hall-x_block_room_bed'].owner,
[13316]229            NOT_OCCUPIED)
[13533]230        self.assertEqual(
[17412]231            self.app['hostels']['hall-x']['hall-x_block_room_bed'].bed_type,
[13533]232            'regular_male_reserved')
[13445]233        # Accommodation session can't be changed if hostels are not empty.
234        self.browser.open(self.manage_container_path)
235        self.browser.getControl(name="form.accommodation_session").value = ['2005']
236        self.browser.getControl("Save").click()
237        self.assertTrue(
238            'You can\'t change the booking session before clearing all hostels'
239            in self.browser.contents)
[13318]240        # Releasing is logged.
241        logcontent = open(self.logfile).read()
242        self.assertTrue(
[13319]243            'hostels.browser.ReleaseExpiredAllocationsPage - hostels - '
[17412]244            'released: hall-x_block_room_bed (K1000000)'
[13318]245            in logcontent)
[13316]246        return
247
[17412]248    def test_release_expired_allocations_in_hostel(self):
249        self.app['hostels'].allocation_expiration = 7
250        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
251        cat = queryUtility(ICatalog, name='beds_catalog')
252        bedticket = BedTicket()
253        bedticket.booking_session = 2004
254        bedticket.bed_coordinates = u'anything'
255        delta = timedelta(days=10)
256        bedticket.booking_date = datetime.utcnow() - delta
257        self.student['accommodation'].addBedTicket(bedticket)
258        self.app[
259            'hostels']['hall-x']['hall-x_block_room_bed'].owner = self.student_id
260        notify(grok.ObjectModifiedEvent(
261            self.app['hostels']['hall-x']['hall-x_block_room_bed']))
262        self.assertEqual(
263            self.app['hostels']['hall-x']['hall-x_block_room_bed'].bed_type,
264            'regular_male_fr')
265        results = cat.searchResults(owner=(self.student_id, self.student_id))
266        self.assertEqual(len(results), 1)
267        grok.getSite()['configuration'].maintmode_enabled_by = u'any_id'
268        self.browser.open(self.container_path + '/hall-x/releaseexpired2')
269        self.assertTrue(
270            'Successfully released beds: hall-x_block_room_bed (K1000000)'
271            in self.browser.contents)
272        results = cat.searchResults(owner=(self.student_id, self.student_id))
273        self.assertEqual(len(results), 0)
274        self.assertMatches(bedticket.display_coordinates,
275            '-- booking expired (2015-10-14 08:35:38 UTC) --')
276        # The owner has been removed and the bed reserved.
277        self.assertEqual(
278            self.app['hostels']['hall-x']['hall-x_block_room_bed'].owner,
279            NOT_OCCUPIED)
280        self.assertEqual(
281            self.app['hostels']['hall-x']['hall-x_block_room_bed'].bed_type,
282            'regular_male_reserved')
283        # Releasing is logged.
284        logcontent = open(self.logfile).read()
285        self.assertTrue(
286            'hostels.browser.ReleaseExpiredAllocationsPage2 - hall-x - '
287            'released: hall-x_block_room_bed (K1000000)'
288            in logcontent)
289        return
290
[13483]291    def test_bed_statistics(self):
292        utils = getUtility(IHostelsUtils)
293        self.app['hostels']['hall-x'][
[17412]294            'hall-x_block_room_bed'].owner = NOT_OCCUPIED
[13483]295        notify(grok.ObjectModifiedEvent(
[17412]296            self.app['hostels']['hall-x']['hall-x_block_room_bed']))
[13483]297        stats = utils.getBedStatistics()
298        self.assertEqual(stats,
[13484]299            {'regular_male_fr': (0, 1, 1),
300             'regular_female_fi': (0, 0, 0),
301             'regular_male_re': (0, 0, 0),
302             'regular_female_fr': (0, 0, 0),
303             'regular_female_all': (0, 0, 0),
304             'regular_female_re': (0, 0, 0),
305             'regular_female_reserved': (0, 0, 0),
306             'regular_male_reserved': (0, 0, 0),
307             'regular_male_fi': (0, 0, 0),
308             'regular_male_all': (0, 0, 0)}
[13483]309             )
310        self.app[
[17412]311            'hostels']['hall-x']['hall-x_block_room_bed'].owner = self.student_id
[13483]312        notify(grok.ObjectModifiedEvent(
[17412]313            self.app['hostels']['hall-x']['hall-x_block_room_bed']))
[13483]314        stats = utils.getBedStatistics()
315        self.assertEqual(stats,
[13484]316            {'regular_male_fr': (1, 0, 1),
317             'regular_female_fi': (0, 0, 0),
318             'regular_male_re': (0, 0, 0),
319             'regular_female_fr': (0, 0, 0),
320             'regular_female_all': (0, 0, 0),
321             'regular_female_re': (0, 0, 0),
322             'regular_female_reserved': (0, 0, 0),
323             'regular_male_reserved': (0, 0, 0),
324             'regular_male_fi': (0, 0, 0),
325             'regular_male_all': (0, 0, 0)}
[13483]326             )
327
[6972]328class BedCatalogTests(HostelsFullSetup):
329
330    layer = FunctionalLayer
331
332    def test_get_catalog(self):
[7045]333        # We can get a beds catalog if we wish
[6972]334        cat = queryUtility(ICatalog, name='beds_catalog')
335        assert cat is not None
336
337    def test_search_by_type(self):
338        # We can find a certain bed
339        cat = queryUtility(ICatalog, name='beds_catalog')
[13483]340        results = cat.searchResults(
341            bed_type=(u'regular_male_fr', u'regular_male_fr'))
[6972]342        results = [x for x in results] # Turn results generator into list
343        assert len(results) == 1
[17412]344        assert results[0] is self.app['hostels']['hall-x']['hall-x_block_room_bed']
[6972]345
[7003]346    def test_search_by_owner(self):
347        # We can find a certain bed
[17412]348        myobj = self.app['hostels']['hall-x']['hall-x_block_room_bed']
[7003]349        myobj.owner = u'abc'
350        notify(grok.ObjectModifiedEvent(myobj))
351        cat = queryUtility(ICatalog, name='beds_catalog')
352        results = cat.searchResults(owner=(u'abc', u'abc'))
353        results = [x for x in results] # Turn results generator into list
354        assert len(results) == 1
[17412]355        assert results[0] is self.app['hostels']['hall-x']['hall-x_block_room_bed']
[7003]356
[6961]357class HostelsUITests(HostelsFullSetup):
358
359    layer = FunctionalLayer
360
361    def test_anonymous_access(self):
362        # Anonymous users can't access hostels containers
363        self.assertRaises(
364            Unauthorized, self.browser.open, self.manage_container_path)
365        return
366
[6985]367    def test_add_search_edit_delete_manage_hostels(self):
[6961]368        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
369        self.browser.open(self.container_path)
370        self.browser.getLink("Manage accommodation").click()
371        self.assertEqual(self.browser.headers['Status'], '200 Ok')
372        self.assertEqual(self.browser.url, self.manage_container_path)
373        self.browser.getControl("Add hostel").click()
374        self.assertEqual(self.browser.headers['Status'], '200 Ok')
375        self.assertEqual(self.browser.url, self.add_hostel_path)
376        self.browser.getControl("Create hostel").click()
377        self.assertEqual(self.browser.headers['Status'], '200 Ok')
378        self.assertTrue('Hostel created' in self.browser.contents)
[6988]379        self.browser.open(self.container_path + '/addhostel')
380        self.browser.getControl("Create hostel").click()
381        self.assertTrue('The hostel already exists' in self.browser.contents)
[6973]382        hall = self.app['hostels']['hall-1']
[6961]383        hall.blocks_for_female = ['A','B']
[6973]384        self.browser.open(self.container_path + '/hall-1')
[13276]385        expected = '...<ul id="form.blocks_for_female" ><li>Block A</li>...'
[6961]386        self.assertMatches(expected,self.browser.contents)
[6973]387        self.browser.open(self.container_path + '/hall-1/manage')
[6985]388        self.browser.getControl(name="form.rooms_per_floor").value = '1'
[6962]389        self.browser.getControl("Save").click()
390        self.assertTrue('Form has been saved' in self.browser.contents)
[6985]391        # Since the testbrowser does not support Javascrip the
392        # save action cleared the settings above and we have to set them
[13176]393        # again.
[6985]394        self.assertTrue(len(hall.blocks_for_female) == 0)
395        hall.blocks_for_female = ['A','B']
[6988]396        hall.beds_for_fresh = ['A']
397        hall.beds_for_returning = ['B']
398        hall.beds_for_final = ['C']
[6985]399        hall.beds_for_all = ['D','E']
400        self.browser.getControl("Update all beds").click()
[13529]401        self.assertTrue('Portal must be in maintenance mode for bed updates'
402            in self.browser.contents)
403        grok.getSite()['configuration'].maintmode_enabled_by = u'any_id'
404        self.browser.getControl("Update all beds").click()
[6988]405        expected = '...0 empty beds removed, 10 beds added, 0 occupied beds modified ()...'
[6985]406        self.assertMatches(expected,self.browser.contents)
407        cat = queryUtility(ICatalog, name='beds_catalog')
408        results = cat.searchResults(
409            bed_type=('regular_female_all', 'regular_female_all'))
[9448]410        results = [(x.bed_id, x.bed_type) for x in results]
411        self.assertEqual(results,
412            [(u'hall-1_A_101_D', u'regular_female_all'),
413             (u'hall-1_A_101_E', u'regular_female_all'),
414             (u'hall-1_B_101_D', u'regular_female_all'),
415             (u'hall-1_B_101_E', u'regular_female_all')])
[13176]416        # Reserve beds.
[6988]417        self.browser.getControl("Switch reservation", index=0).click()
418        self.assertTrue('No item selected' in self.browser.contents)
[6985]419        ctrl = self.browser.getControl(name='val_id')
[6988]420        ctrl.getControl(value='hall-1_A_101_A').selected = True
421        ctrl.getControl(value='hall-1_A_101_B').selected = True
422        ctrl.getControl(value='hall-1_A_101_C').selected = True
[6985]423        ctrl.getControl(value='hall-1_A_101_D').selected = True
424        self.browser.getControl("Switch reservation", index=0).click()
[6988]425        self.assertTrue('Successfully switched beds: hall-1_A_101_A (reserved)'
[6985]426            in self.browser.contents)
[9448]427        self.assertEqual(self.app['hostels']['hall-1'][
428            'hall-1_A_101_D'].bed_type, 'regular_female_reserved')
[13176]429        # The catalog has been updated.
[9448]430        results = cat.searchResults(
431            bed_type=('regular_female_all', 'regular_female_all'))
432        results = [(x.bed_id, x.bed_type) for x in results]
433        self.assertEqual(results,
434            [(u'hall-1_A_101_E', u'regular_female_all'),
435             (u'hall-1_B_101_D', u'regular_female_all'),
436             (u'hall-1_B_101_E', u'regular_female_all')])
437        results = cat.searchResults(
438            bed_type=('regular_female_reserved', 'regular_female_reserved'))
439        results = [(x.bed_id, x.bed_type) for x in results]
440        self.assertEqual(results,
441            [(u'hall-1_A_101_A', u'regular_female_reserved'),
442             (u'hall-1_A_101_B', u'regular_female_reserved'),
443             (u'hall-1_A_101_C', u'regular_female_reserved'),
444             (u'hall-1_A_101_D', u'regular_female_reserved')])
[17313]445        # Block beds.
446        self.browser.getControl("Switch blockade", index=0).click()
447        self.assertTrue('No item selected' in self.browser.contents)
448        ctrl = self.browser.getControl(name='val_id')
449        ctrl.getControl(value='hall-1_A_101_A').selected = True
450        ctrl.getControl(value='hall-1_A_101_B').selected = True
451        ctrl.getControl(value='hall-1_A_101_C').selected = True
452        ctrl.getControl(value='hall-1_A_101_D').selected = True
453        self.browser.getControl("Switch blockade", index=0).click()
454        self.assertTrue('Successfully switched beds: hall-1_A_101_A (blocked)'
455            in self.browser.contents)
456        self.assertEqual(self.app['hostels']['hall-1'][
457            'hall-1_A_101_D'].bed_type, 'regular_female_blocked')
458        # The catalog has been updated.
459        results = cat.searchResults(
460            bed_type=('regular_female_all', 'regular_female_all'))
461        results = [(x.bed_id, x.bed_type) for x in results]
462        self.assertEqual(results,
463            [(u'hall-1_A_101_E', u'regular_female_all'),
464             (u'hall-1_B_101_D', u'regular_female_all'),
465             (u'hall-1_B_101_E', u'regular_female_all')])
466        results = cat.searchResults(
467            bed_type=('regular_female_blocked', 'regular_female_blocked'))
468        results = [(x.bed_id, x.bed_type) for x in results]
469        self.assertEqual(results,
470            [(u'hall-1_A_101_A', u'regular_female_blocked'),
471             (u'hall-1_A_101_B', u'regular_female_blocked'),
472             (u'hall-1_A_101_C', u'regular_female_blocked'),
473             (u'hall-1_A_101_D', u'regular_female_blocked')])
[13176]474        # Change hostel configuration with one bed booked.
[9448]475        hall['hall-1_A_101_E'].owner = u'anyid'
476        notify(grok.ObjectModifiedEvent(hall['hall-1_A_101_E']))
477        hall.beds_for_fresh = ['A', 'E']
[6985]478        hall.beds_for_all = ['D']
479        self.browser.getControl("Update all beds").click()
[9448]480        expected = '...9 empty beds removed, 9 beds added, 1 occupied beds modified...'
[6985]481        self.assertMatches(expected,self.browser.contents)
[13176]482        # Updating beds (including booked beds!) does update catalog.
[6985]483        results = cat.searchResults(
484            bed_type=('regular_female_all', 'regular_female_all'))
[9448]485        results = [(x.bed_id, x.bed_type) for x in results]
[13346]486        # The reservation of hall-1_A_101_D has been cancelled.
[9448]487        self.assertEqual(results,
[13346]488            [(u'hall-1_A_101_D', u'regular_female_all'),
489             (u'hall-1_B_101_D', u'regular_female_all')])
[13176]490        # Release bed which has previously been booked.
[7045]491        bedticket = BedTicket()
[9423]492        bedticket.booking_session = 2004
[7045]493        bedticket.bed_coordinates = u'anything'
494        self.student['accommodation'].addBedTicket(bedticket)
495        self.app['hostels']['hall-1']['hall-1_A_101_D'].owner = self.student_id
[13315]496        notify(grok.ObjectModifiedEvent(self.app['hostels']['hall-1']['hall-1_A_101_D']))
[7045]497        self.browser.open(self.container_path + '/hall-1/manage')
498        ctrl = self.browser.getControl(name='val_id')
[7068]499        self.browser.getControl("Release selected beds", index=0).click()
500        self.assertMatches("...No item selected...", self.browser.contents)
501        ctrl = self.browser.getControl(name='val_id')
[7045]502        ctrl.getControl(value='hall-1_A_101_D').selected = True
503        self.browser.getControl("Release selected beds", index=0).click()
504        self.assertMatches(
505          '...Successfully released beds: hall-1_A_101_D (%s)...' % self.student_id,
506          self.browser.contents)
507        self.assertMatches(bedticket.bed_coordinates,
[8234]508          u' -- booking cancelled on <YYYY-MM-DD hh:mm:ss> UTC --')
[13316]509        # The catalog has been updated.
[13315]510        results = cat.searchResults(owner=(self.student_id, self.student_id))
511        assert len(results) == 0
512        # If we release a free bed, nothing will happen.
[7070]513        ctrl = self.browser.getControl(name='val_id')
514        ctrl.getControl(value='hall-1_A_101_D').selected = True
515        self.browser.getControl("Release selected beds", index=0).click()
516        self.assertMatches(
517          '...No allocated bed selected...', self.browser.contents)
[13176]518        # Managers can manually allocate eligible students after cancellation.
[7068]519        self.browser.open(self.container_path + '/hall-1/hall-1_A_101_A')
[13176]520        # 'not occupied' is not accepted.
[9416]521        self.browser.getControl("Save").click()
522        self.assertMatches(
523            "...No valid student id...",
524            self.browser.contents)
[13176]525        # Invalid student ids are not accepted.
[9414]526        self.browser.getControl(name="form.owner").value = 'nonsense'
[7068]527        self.browser.getControl("Save").click()
[9414]528        self.assertMatches(
[9416]529            "...Either student does not exist or student "
[17176]530            "is not eligible to book accommodation...",
[9414]531            self.browser.contents)
532        self.browser.getControl(name="form.owner").value = self.student_id
533        self.browser.getControl("Save").click()
[7068]534        self.assertMatches("...Form has been saved...", self.browser.contents)
[13176]535        # Students can only be allocated once.
[9414]536        self.browser.open(self.container_path + '/hall-1/hall-1_A_101_B')
537        self.browser.getControl(name="form.owner").value = self.student_id
538        self.browser.getControl("Save").click()
539        self.assertMatches(
540            "...This student resides in bed hall-1_A_101_A...",
541            self.browser.contents)
[7070]542        # If we open the same form again, we will be redirected to hostel
543        # manage page. Beds must be released first before they can be
544        # allocated to other students.
545        self.browser.open(self.container_path + '/hall-1/hall-1_A_101_A')
546        self.assertEqual(self.browser.url,
[11254]547            self.container_path + '/hall-1/@@manage#tab2')
[7070]548        # Updating the beds again will not affect the allocation and also
[13176]549        # the bed numbering remains the same.
[7070]550        old_number = self.app['hostels']['hall-1']['hall-1_A_101_A'].bed_number
551        old_owner = self.app['hostels']['hall-1']['hall-1_A_101_A'].owner
552        self.browser.getControl("Update all beds").click()
[9448]553        # 8 beds have been removed and re-added, 2 beds remains untouched
[13176]554        # because they are occupied.
[9448]555        expected = '...8 empty beds removed, 8 beds added, 0 occupied beds modified...'
[7070]556        self.assertMatches(expected,self.browser.contents)
557        new_number = self.app['hostels']['hall-1']['hall-1_A_101_A'].bed_number
558        new_owner = self.app['hostels']['hall-1']['hall-1_A_101_A'].owner
559        self.assertEqual(new_number, old_number)
560        self.assertEqual(new_owner, old_owner)
561        # If we change the bed type of an allocated bed, the modification will
[13176]562        # be indicated.
[7070]563        hall.blocks_for_female = ['B']
564        hall.blocks_for_male = ['A']
565        self.browser.getControl("Update all beds").click()
[9448]566        expected = '...8 empty beds removed, 8 beds added, ' + \
567            '2 occupied beds modified (hall-1_A_101_A, hall-1_A_101_E, )...'
[7070]568        self.assertMatches(expected,self.browser.contents)
569        new_number = self.app['hostels']['hall-1']['hall-1_A_101_A'].bed_number
570        # Also the number of the bed has changed.
571        self.assertFalse(new_number == old_number)
[9414]572        # The number of occupied beds are displayed on container page.
[9196]573        self.browser.open(self.container_path)
[9448]574        self.assertTrue('2 of 10' in self.browser.contents)
[13440]575        bedticket.bed = self.app['hostels']['hall-1']['hall-1_A_101_A']
[13484]576        # Managers can open the bed statistics page
577        self.browser.getLink("Bed statistics").click()
578        self.assertTrue('Bed Statistics</h1>' in self.browser.contents)
[13176]579        # Remove entire hostel.
[6962]580        self.browser.open(self.manage_container_path)
581        ctrl = self.browser.getControl(name='val_id')
582        value = ctrl.options[0]
583        ctrl.getControl(value=value).selected = True
584        self.browser.getControl("Remove selected", index=0).click()
585        self.assertTrue('Successfully removed' in self.browser.contents)
[13176]586        # Catalog is empty.
[6985]587        results = cat.searchResults(
588            bed_type=('regular_female_all', 'regular_female_all'))
589        results = [x for x in results]
590        assert len(results) == 0
[13440]591        # Bed has been removed from bedticket
592        self.assertEqual(bedticket.bed, None)
[12894]593        # Actions are logged.
594        logcontent = open(self.logfile).read()
595        self.assertTrue(
596            'hall-1 - 9 empty beds removed, 9 beds added, 1 occupied '
597            'beds modified (hall-1_A_101_E, )'
598            in logcontent)
[9197]599
600    def test_clear_hostels(self):
[13529]601        grok.getSite()['configuration'].maintmode_enabled_by = u'any_id'
[9197]602        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
603        self.browser.open(self.container_path)
604        self.browser.getLink("Manage accommodation").click()
605        self.browser.getControl("Add hostel").click()
606        self.browser.getControl("Create hostel").click()
607        hall = self.app['hostels']['hall-1']
608        hall.blocks_for_female = ['A','B']
609        hall.rooms_per_floor = 1
610        hall.beds_for_fresh = ['A']
611        hall.beds_for_returning = ['B']
612        hall.beds_for_final = ['C']
613        hall.beds_for_all = ['D','E']
614        self.browser.open(self.container_path + '/hall-1/manage')
615        self.browser.getControl("Update all beds").click()
616        cat = queryUtility(ICatalog, name='beds_catalog')
617        results = cat.searchResults(bed_type=(None, None))
618        self.assertEqual(len(results), 11)
619        self.browser.getControl("Clear hostel").click()
620        self.assertEqual(len(self.app['hostels']['hall-1']), 0)
621        # Only the bed in hall-x remains in the catalog.
622        results = cat.searchResults(bed_type=(None, None))
623        self.assertEqual(len(results), 1)
624        # We can clear all hostels at the same time.
625        self.browser.open(self.manage_container_path)
626        self.browser.getControl("Clear all hostels").click()
627        results = cat.searchResults(bed_type=(None, None))
628        self.assertEqual(len(results), 0)
629        # Both actions have been logged.
[9202]630        logcontent = open(self.logfile).read()
[9197]631        self.assertTrue('INFO - zope.mgr - hostels.browser.HostelManageFormPage'
632                        ' - hall-1 - cleared' in logcontent)
633        self.assertTrue('zope.mgr - hostels.browser.HostelsContainerManagePage'
634                        ' - hostels - all hostels cleared' in logcontent)
[9200]635
636class ExportTests(HostelsFullSetup):
637
638    layer = FunctionalLayer
639
640    def setUp(self):
641        super(ExportTests, self).setUp()
642        self.workdir = tempfile.mkdtemp()
643        self.outfile = os.path.join(self.workdir, 'myoutput.csv')
644        return
645
646    def test_export_hostels(self):
647        exporter = HostelExporter()
648        exporter.export_all(self.app, self.outfile)
649        result = open(self.outfile, 'rb').read()
650        self.assertEqual(
651            result,
652            'beds_for_all,beds_for_final,beds_for_fresh,beds_for_pre,'
[13346]653            'beds_for_returning,blocks_for_female,'
[10680]654            'blocks_for_male,floors_per_block,hostel_id,hostel_name,maint_fee,'
[13346]655            'rooms_per_floor,sort_id,special_handling\r\n[],[],[],[],[],[],[],'
656            '1,hall-x,Hall 1,0.0,2,10,regular\r\n'
[9200]657            )
658        return
659
660    def test_export_beds(self):
661        exporter = BedExporter()
662        exporter.export_all(self.app, self.outfile)
663        result = open(self.outfile, 'rb').read()
664        self.assertEqual(
665            result,
666            'bed_id,bed_number,bed_type,owner,hall,block,room,bed,'
[17412]667            'special_handling,sex,bt\r\nhall-x_block_room_bed,1,regular_male_fr,,'
668            'hall-x,block,room,bed,regular,male,fr\r\n'
[9200]669            )
[9202]670        return
671
[9283]672    def tearDown(self):
673        super(ExportTests, self).tearDown()
674        clearSite()
675        shutil.rmtree(os.path.dirname(self.outfile))
676
[13432]677class HostelBedProcessorTest(HostelsFullSetup):
[9202]678
679    layer = FunctionalLayer
680
681    def test_import(self):
682        self.processor = HostelProcessor()
683        self.workdir = tempfile.mkdtemp()
[9283]684        self.csv_file = os.path.join(self.workdir, 'sample_hostel_data.csv')
[9202]685        open(self.csv_file, 'wb').write(HOSTEL_SAMPLE_DATA)
686        num, num_warns, fin_file, fail_file = self.processor.doImport(
687            self.csv_file, HOSTEL_HEADER_FIELDS)
688        self.assertEqual(num_warns,0)
689        self.assertEqual(len(self.app['hostels'].keys()), 11) # including hall-x
690        self.assertEqual(self.app['hostels'][
691            'block-a-upper-hostel'].hostel_id,'block-a-upper-hostel')
692        self.assertEqual(self.app['hostels'][
693            'block-a-upper-hostel'].beds_for_final, ['A', 'B'])
694        logcontent = open(self.logfile).read()
695        self.assertTrue(
[9706]696            "Hostel Processor - sample_hostel_data - block-a-upper-hostel - "
697            "updated: "
698            "beds_for_pre=['G'], floors_per_block=1, "
[13346]699            "special_handling=regular, "
[9202]700            "beds_for_final=['A', 'B'], rooms_per_floor=32, "
[12981]701            "hostel_id=block-a-upper-hostel, "
[9202]702            "sort_id=20, beds_for_returning=['C', 'D'], "
703            "hostel_name=Block A Upper Hostel, beds_for_fresh=['E', 'F'], "
[12981]704            "blocks_for_female=['A']"
[9202]705            in logcontent)
706        shutil.rmtree(os.path.dirname(fin_file))
[9283]707        shutil.rmtree(self.workdir)
[13346]708        # The manage page can be opened.
709        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
710        self.browser.open(self.container_path + '/block-a-upper-hostel')
711        self.browser.getLink("Manage").click()
712        self.assertEqual(self.browser.headers['Status'], '200 Ok')
[10683]713        return
714
[13432]715    def test_import_update_hostel_and_beds(self):
[10683]716        self.processor = HostelProcessor()
717        self.workdir = tempfile.mkdtemp()
718        self.csv_file = os.path.join(self.workdir, 'sample_hostel_data.csv')
719        open(self.csv_file, 'wb').write(HOSTEL_SAMPLE_DATA)
720        num, num_warns, fin_file, fail_file = self.processor.doImport(
721            self.csv_file, HOSTEL_HEADER_FIELDS)
722        # We import the same file in update mode
723        num, num_warns, fin_file, fail_file = self.processor.doImport(
724            self.csv_file, HOSTEL_HEADER_FIELDS, 'update')
725        self.assertEqual(num_warns,0)
726        logcontent = open(self.logfile).read()
727        self.assertTrue(
728            "Hostel Processor - sample_hostel_data - block-a-upper-hostel - "
729            "updated: "
730            "beds_for_pre=['G'], floors_per_block=1, "
[13346]731            "special_handling=regular, "
[10683]732            "beds_for_final=['A', 'B'], rooms_per_floor=32, "
[12981]733            "hostel_id=block-a-upper-hostel, "
[10683]734            "sort_id=20, beds_for_returning=['C', 'D'], "
735            "hostel_name=Block A Upper Hostel, beds_for_fresh=['E', 'F'], "
[12981]736            "blocks_for_female=['A']"
[10683]737            in logcontent)
[13432]738        # Update the beds of first hostel
739        self.app['hostels']['block-a-upper-hostel'].updateBeds()
740        # Import beds
741        self.processor = BedProcessor()
742        self.csv_file = os.path.join(self.workdir, 'sample_bed_data.csv')
743        open(self.csv_file, 'wb').write(BED_SAMPLE_DATA)
744        num, num_warns, fin_file, fail_file = self.processor.doImport(
745            self.csv_file, BED_HEADER_FIELDS, 'update')
746        self.assertEqual(num_warns,2)
747        fail_file_content = open(fail_file).read()
748        logcontent = open(self.logfile).read()
749        self.assertEqual(
[17313]750            'owner,bed_id,hostel_id,reserved,blocked,--ERRORS--\r\n'
751            '<IGNORE>,block-a-upper-hostel_A_101_C,block-a-upper-hostel,2,0,'
[13432]752            'reserved: invalid value\r\n'
[17313]753            'K999,block-a-upper-hostel_A_101_E,block-a-upper-hostel,0,0,'
[13432]754            'owner: student does not exist\r\n',
755            fail_file_content)
756        self.assertTrue(
757            'system - Bed Processor (update only) - sample_bed_data - '
[17313]758            'block-a-upper-hostel_A_101_A - updated: blocked'
[13432]759            in logcontent)
760        self.assertTrue(
761            'system - Bed Processor (update only) - sample_bed_data - '
762            'block-a-upper-hostel_A_101_B - updated: reserved'
763            in logcontent)
764        self.assertTrue(
765            'system - Bed Processor (update only) - sample_bed_data - '
766            'block-a-upper-hostel_A_101_D - updated: owner=K1000000'
767            in logcontent)
768        self.assertEqual(
769            self.app['hostels']['block-a-upper-hostel']
770                    ['block-a-upper-hostel_A_101_D'].owner, 'K1000000')
771        self.assertEqual(
772            self.app['hostels']['block-a-upper-hostel']
773                    ['block-a-upper-hostel_A_101_B'].bed_type,
774                    'regular_female_reserved')
[17313]775        # Add another student K1000001
776        student = createObject('waeup.Student')
777        student.firstname = u'Claus'
778        student.lastname = u'Tester'
779        self.app['students'].addStudent(student)
780        # Try to allocate this student to a blocked bed
781
782        open(self.csv_file, 'wb').write(BED_SAMPLE_DATA_2)
783        num, num_warns, fin_file, fail_file = self.processor.doImport(
784            self.csv_file, BED_HEADER_FIELDS, 'update')
785        fail_file_content = open(fail_file).read()
786        self.assertEqual(
787            'owner,bed_id,hostel_id,reserved,blocked,--ERRORS--\r\n'
788            'K1000001,block-a-upper-hostel_A_101_A,block-a-upper-hostel,'
789            '<IGNORE>,<IGNORE>,Bed is blocked.\r\n',
790            fail_file_content)
[10683]791        shutil.rmtree(os.path.dirname(fin_file))
792        shutil.rmtree(self.workdir)
[13432]793        return
Note: See TracBrowser for help on using the repository browser.