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

Last change on this file since 17782 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
Line 
1## $Id: tests.py 17412 2023-05-17 18:21:10Z 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, getUtility, createObject
34from waeup.kofa.app import University
35from waeup.kofa.hostels.interfaces import (
36    IHostelsContainer, IHostel, IBed, IHostelsUtils)
37from waeup.kofa.hostels.vocabularies import NOT_OCCUPIED
38from waeup.kofa.hostels.container import HostelsContainer
39from waeup.kofa.hostels.hostel import Hostel, Bed
40from waeup.kofa.hostels.batching import HostelProcessor, BedProcessor
41from waeup.kofa.hostels.export import BedExporter, HostelExporter
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
46
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
54BED_SAMPLE_DATA = open(
55    os.path.join(os.path.dirname(__file__), 'sample_bed_data.csv'),
56    'rb').read()
57
58BED_SAMPLE_DATA_2 = open(
59    os.path.join(os.path.dirname(__file__), 'sample_bed_data_2.csv'),
60    'rb').read()
61
62BED_HEADER_FIELDS = BED_SAMPLE_DATA.split(
63    '\n')[0].split(',')
64
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            )
87        self.assertTrue(
88            verifyClass(
89                IBed, Bed)
90            )
91        bed = Bed()
92        bed.bed_id = u'a_b_c_d'
93        bed.bed_type = u'a_b_c'
94        self.assertTrue(
95            verifyObject(
96                IBed, bed)
97            )
98        return
99
100    def test_base(self):
101        # We cannot call the fundamental methods of a base in that case
102        container = HostelsContainer()
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)
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)
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
139        # Add student with subobjects
140        student = Student()
141        student.firstname = u'Anna'
142        student.lastname = u'Tester'
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
151
152        # The students_catalog must be informed that the
153        # session attribute has changed
154        notify(grok.ObjectModifiedEvent(self.student))
155
156        # Set accommodation_session
157        self.app['hostels'].accommodation_session = 2004
158
159        # Create a hostel
160        hostel = Hostel()
161        hostel.hostel_id = u'hall-x'
162        self.app['hostels'][hostel.hostel_id] = hostel
163
164        # Create a bed
165        bed = Bed()
166        bed.bed_id = u'hall-x_block_room_bed'
167        bed.bed_number = 1
168        bed.bed_type = u'regular_male_fr'
169        self.app['hostels'][hostel.hostel_id][bed.bed_id] = bed
170
171        self.container_path = 'http://localhost/app/hostels'
172        self.student_path = 'http://localhost/app/students/%s' % self.student_id
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
181        self.logfile = os.path.join(
182            self.app['datacenter'].storage, 'logs', 'hostels.log')
183
184    def tearDown(self):
185        super(HostelsFullSetup, self).tearDown()
186        clearSite()
187        shutil.rmtree(self.dc_root)
188
189class HostelsContainerTests(HostelsFullSetup):
190
191    layer = FunctionalLayer
192
193    def test_release_expired_allocations_portalwide(self):
194        self.app['hostels'].allocation_expiration = 7
195        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
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[
202            'hostels']['hall-x']['hall-x_block_room_bed'].owner = self.student_id
203        notify(grok.ObjectModifiedEvent(
204            self.app['hostels']['hall-x']['hall-x_block_room_bed']))
205        self.assertEqual(
206            self.app['hostels']['hall-x']['hall-x_block_room_bed'].bed_type,
207            'regular_male_fr')
208        results = cat.searchResults(owner=(self.student_id, self.student_id))
209        self.assertEqual(len(results), 1)
210        self.browser.open(self.container_path + '/releaseexpired')
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')
215        self.assertTrue('No bed released' in self.browser.contents)
216        delta = timedelta(days=10)
217        bedticket.booking_date = datetime.utcnow() - delta
218        self.browser.open(self.container_path + '/releaseexpired')
219        self.assertTrue(
220            'Successfully released beds: hall-x_block_room_bed (K1000000)'
221            in self.browser.contents)
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) --')
226        # The owner has been removed and the bed reserved.
227        self.assertEqual(
228            self.app['hostels']['hall-x']['hall-x_block_room_bed'].owner,
229            NOT_OCCUPIED)
230        self.assertEqual(
231            self.app['hostels']['hall-x']['hall-x_block_room_bed'].bed_type,
232            'regular_male_reserved')
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)
240        # Releasing is logged.
241        logcontent = open(self.logfile).read()
242        self.assertTrue(
243            'hostels.browser.ReleaseExpiredAllocationsPage - hostels - '
244            'released: hall-x_block_room_bed (K1000000)'
245            in logcontent)
246        return
247
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
291    def test_bed_statistics(self):
292        utils = getUtility(IHostelsUtils)
293        self.app['hostels']['hall-x'][
294            'hall-x_block_room_bed'].owner = NOT_OCCUPIED
295        notify(grok.ObjectModifiedEvent(
296            self.app['hostels']['hall-x']['hall-x_block_room_bed']))
297        stats = utils.getBedStatistics()
298        self.assertEqual(stats,
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)}
309             )
310        self.app[
311            'hostels']['hall-x']['hall-x_block_room_bed'].owner = self.student_id
312        notify(grok.ObjectModifiedEvent(
313            self.app['hostels']['hall-x']['hall-x_block_room_bed']))
314        stats = utils.getBedStatistics()
315        self.assertEqual(stats,
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)}
326             )
327
328class BedCatalogTests(HostelsFullSetup):
329
330    layer = FunctionalLayer
331
332    def test_get_catalog(self):
333        # We can get a beds catalog if we wish
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')
340        results = cat.searchResults(
341            bed_type=(u'regular_male_fr', u'regular_male_fr'))
342        results = [x for x in results] # Turn results generator into list
343        assert len(results) == 1
344        assert results[0] is self.app['hostels']['hall-x']['hall-x_block_room_bed']
345
346    def test_search_by_owner(self):
347        # We can find a certain bed
348        myobj = self.app['hostels']['hall-x']['hall-x_block_room_bed']
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
355        assert results[0] is self.app['hostels']['hall-x']['hall-x_block_room_bed']
356
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
367    def test_add_search_edit_delete_manage_hostels(self):
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)
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)
382        hall = self.app['hostels']['hall-1']
383        hall.blocks_for_female = ['A','B']
384        self.browser.open(self.container_path + '/hall-1')
385        expected = '...<ul id="form.blocks_for_female" ><li>Block A</li>...'
386        self.assertMatches(expected,self.browser.contents)
387        self.browser.open(self.container_path + '/hall-1/manage')
388        self.browser.getControl(name="form.rooms_per_floor").value = '1'
389        self.browser.getControl("Save").click()
390        self.assertTrue('Form has been saved' in self.browser.contents)
391        # Since the testbrowser does not support Javascrip the
392        # save action cleared the settings above and we have to set them
393        # again.
394        self.assertTrue(len(hall.blocks_for_female) == 0)
395        hall.blocks_for_female = ['A','B']
396        hall.beds_for_fresh = ['A']
397        hall.beds_for_returning = ['B']
398        hall.beds_for_final = ['C']
399        hall.beds_for_all = ['D','E']
400        self.browser.getControl("Update all beds").click()
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()
405        expected = '...0 empty beds removed, 10 beds added, 0 occupied beds modified ()...'
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'))
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')])
416        # Reserve beds.
417        self.browser.getControl("Switch reservation", index=0).click()
418        self.assertTrue('No item selected' in self.browser.contents)
419        ctrl = self.browser.getControl(name='val_id')
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
423        ctrl.getControl(value='hall-1_A_101_D').selected = True
424        self.browser.getControl("Switch reservation", index=0).click()
425        self.assertTrue('Successfully switched beds: hall-1_A_101_A (reserved)'
426            in self.browser.contents)
427        self.assertEqual(self.app['hostels']['hall-1'][
428            'hall-1_A_101_D'].bed_type, 'regular_female_reserved')
429        # The catalog has been updated.
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')])
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')])
474        # Change hostel configuration with one bed booked.
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']
478        hall.beds_for_all = ['D']
479        self.browser.getControl("Update all beds").click()
480        expected = '...9 empty beds removed, 9 beds added, 1 occupied beds modified...'
481        self.assertMatches(expected,self.browser.contents)
482        # Updating beds (including booked beds!) does update catalog.
483        results = cat.searchResults(
484            bed_type=('regular_female_all', 'regular_female_all'))
485        results = [(x.bed_id, x.bed_type) for x in results]
486        # The reservation of hall-1_A_101_D has been cancelled.
487        self.assertEqual(results,
488            [(u'hall-1_A_101_D', u'regular_female_all'),
489             (u'hall-1_B_101_D', u'regular_female_all')])
490        # Release bed which has previously been booked.
491        bedticket = BedTicket()
492        bedticket.booking_session = 2004
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
496        notify(grok.ObjectModifiedEvent(self.app['hostels']['hall-1']['hall-1_A_101_D']))
497        self.browser.open(self.container_path + '/hall-1/manage')
498        ctrl = self.browser.getControl(name='val_id')
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')
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,
508          u' -- booking cancelled on <YYYY-MM-DD hh:mm:ss> UTC --')
509        # The catalog has been updated.
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.
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)
518        # Managers can manually allocate eligible students after cancellation.
519        self.browser.open(self.container_path + '/hall-1/hall-1_A_101_A')
520        # 'not occupied' is not accepted.
521        self.browser.getControl("Save").click()
522        self.assertMatches(
523            "...No valid student id...",
524            self.browser.contents)
525        # Invalid student ids are not accepted.
526        self.browser.getControl(name="form.owner").value = 'nonsense'
527        self.browser.getControl("Save").click()
528        self.assertMatches(
529            "...Either student does not exist or student "
530            "is not eligible to book accommodation...",
531            self.browser.contents)
532        self.browser.getControl(name="form.owner").value = self.student_id
533        self.browser.getControl("Save").click()
534        self.assertMatches("...Form has been saved...", self.browser.contents)
535        # Students can only be allocated once.
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)
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,
547            self.container_path + '/hall-1/@@manage#tab2')
548        # Updating the beds again will not affect the allocation and also
549        # the bed numbering remains the same.
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()
553        # 8 beds have been removed and re-added, 2 beds remains untouched
554        # because they are occupied.
555        expected = '...8 empty beds removed, 8 beds added, 0 occupied beds modified...'
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
562        # be indicated.
563        hall.blocks_for_female = ['B']
564        hall.blocks_for_male = ['A']
565        self.browser.getControl("Update all beds").click()
566        expected = '...8 empty beds removed, 8 beds added, ' + \
567            '2 occupied beds modified (hall-1_A_101_A, hall-1_A_101_E, )...'
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)
572        # The number of occupied beds are displayed on container page.
573        self.browser.open(self.container_path)
574        self.assertTrue('2 of 10' in self.browser.contents)
575        bedticket.bed = self.app['hostels']['hall-1']['hall-1_A_101_A']
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)
579        # Remove entire hostel.
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)
586        # Catalog is empty.
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
591        # Bed has been removed from bedticket
592        self.assertEqual(bedticket.bed, None)
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)
599
600    def test_clear_hostels(self):
601        grok.getSite()['configuration'].maintmode_enabled_by = u'any_id'
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.
630        logcontent = open(self.logfile).read()
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)
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,'
653            'beds_for_returning,blocks_for_female,'
654            'blocks_for_male,floors_per_block,hostel_id,hostel_name,maint_fee,'
655            'rooms_per_floor,sort_id,special_handling\r\n[],[],[],[],[],[],[],'
656            '1,hall-x,Hall 1,0.0,2,10,regular\r\n'
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,'
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'
669            )
670        return
671
672    def tearDown(self):
673        super(ExportTests, self).tearDown()
674        clearSite()
675        shutil.rmtree(os.path.dirname(self.outfile))
676
677class HostelBedProcessorTest(HostelsFullSetup):
678
679    layer = FunctionalLayer
680
681    def test_import(self):
682        self.processor = HostelProcessor()
683        self.workdir = tempfile.mkdtemp()
684        self.csv_file = os.path.join(self.workdir, 'sample_hostel_data.csv')
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(
696            "Hostel Processor - sample_hostel_data - block-a-upper-hostel - "
697            "updated: "
698            "beds_for_pre=['G'], floors_per_block=1, "
699            "special_handling=regular, "
700            "beds_for_final=['A', 'B'], rooms_per_floor=32, "
701            "hostel_id=block-a-upper-hostel, "
702            "sort_id=20, beds_for_returning=['C', 'D'], "
703            "hostel_name=Block A Upper Hostel, beds_for_fresh=['E', 'F'], "
704            "blocks_for_female=['A']"
705            in logcontent)
706        shutil.rmtree(os.path.dirname(fin_file))
707        shutil.rmtree(self.workdir)
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')
713        return
714
715    def test_import_update_hostel_and_beds(self):
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, "
731            "special_handling=regular, "
732            "beds_for_final=['A', 'B'], rooms_per_floor=32, "
733            "hostel_id=block-a-upper-hostel, "
734            "sort_id=20, beds_for_returning=['C', 'D'], "
735            "hostel_name=Block A Upper Hostel, beds_for_fresh=['E', 'F'], "
736            "blocks_for_female=['A']"
737            in logcontent)
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(
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,'
752            'reserved: invalid value\r\n'
753            'K999,block-a-upper-hostel_A_101_E,block-a-upper-hostel,0,0,'
754            'owner: student does not exist\r\n',
755            fail_file_content)
756        self.assertTrue(
757            'system - Bed Processor (update only) - sample_bed_data - '
758            'block-a-upper-hostel_A_101_A - updated: blocked'
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')
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)
791        shutil.rmtree(os.path.dirname(fin_file))
792        shutil.rmtree(self.workdir)
793        return
Note: See TracBrowser for help on using the repository browser.