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

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

Rework logging of batch processing. Remove redundant text but add name of import file.

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