source: main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/tests/test_accesscode.py @ 9913

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

Archive also the owner of an accesscode.

  • Property svn:keywords set to Id
File size: 16.2 KB
Line 
1## $Id: test_accesscode.py 9260 2012-09-30 20:28:32Z 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##
18import doctest
19import os
20import re
21import shutil
22import tempfile
23import unittest
24
25from datetime import datetime
26from hurry.workflow.interfaces import InvalidTransitionError, IWorkflowState
27from zope.component import getUtility
28from zope.component.hooks import setSite, clearSite
29from zope.interface.verify import verifyObject, verifyClass
30from zope.testing import renormalizing
31from waeup.kofa.app import University
32from waeup.kofa.interfaces import IObjectHistory, IKofaPluggable, IKofaUtils
33from waeup.kofa.testing import (
34    FunctionalLayer, FunctionalTestCase, setUp, tearDown, getRootFolder)
35from waeup.kofa.accesscodes.accesscode import (
36    AccessCodeBatch, get_access_code, invalidate_accesscode, AccessCode,
37    disable_accesscode, reenable_accesscode, fire_transition,
38    AccessCodeBatchContainer, AccessCodePlugin)
39from waeup.kofa.accesscodes.interfaces import (
40    IAccessCode, IAccessCodeBatch,  IAccessCodeBatchContainer,)
41from waeup.kofa.accesscodes.workflow import INITIALIZED, USED, DISABLED
42
43
44optionflags = (
45    doctest.REPORT_NDIFF + doctest.ELLIPSIS + doctest.NORMALIZE_WHITESPACE)
46
47class AccessCodeHelpersTests(FunctionalTestCase):
48    # Tests for helpers like get_access_code, disable_accesscode, ...
49
50    layer = FunctionalLayer
51
52    def setUp(self):
53        super(AccessCodeHelpersTests, self).setUp()
54
55        # Prepopulate ZODB
56        app = University()
57        self.dc_root = tempfile.mkdtemp()
58        app['datacenter'].setStoragePath(self.dc_root)
59
60        # Prepopulate the ZODB...
61        self.getRootFolder()['app'] = app
62        self.app = self.getRootFolder()['app']
63
64        # Create batch
65        batch = AccessCodeBatch('now', 'manfred', 'APP', 6.6, 0)
66        self.app['accesscodes'].addBatch(batch)
67
68        # Fill batch with accesscodes
69        batch.addAccessCode(0, '11111111')
70        batch.addAccessCode(1, '22222222')
71        batch.addAccessCode(2, '33333333')
72        self.ac1 = batch.getAccessCode('APP-1-11111111')
73        self.ac2 = batch.getAccessCode('APP-1-22222222')
74        self.ac3 = batch.getAccessCode('APP-1-33333333')
75
76        setSite(self.app)
77        return
78
79    def tearDown(self):
80        shutil.rmtree(self.dc_root)
81        super(AccessCodeHelpersTests, self).tearDown()
82        return
83
84    def test_get_access_code(self):
85        ac = get_access_code('APP-1-11111111')
86        assert ac is self.ac1
87
88    def test_get_access_code_not_string(self):
89        ac = get_access_code(object())
90        assert ac is None
91
92    def test_get_access_code_no_proper_pin(self):
93        ac = get_access_code('APP-without_pin')
94        assert ac is None
95
96    def test_get_access_code_invalid_batch_num(self):
97        ac = get_access_code('APP-invalid-11111111')
98        assert ac is None
99
100    def test_get_access_code_invalid_pin(self):
101        ac = get_access_code('APP-1-notexistent')
102        assert ac is None
103
104    def test_invalidate_accesscode(self):
105        assert self.ac1.state != USED
106        result = invalidate_accesscode('APP-1-11111111')
107        assert self.ac1.state == USED
108        assert result is True
109
110    def test_disable_accesscode_unused(self):
111        # we can disable initialized acs
112        assert self.ac1.state != USED
113        disable_accesscode('APP-1-11111111')
114        assert self.ac1.state == DISABLED
115
116    def test_disable_accesscode_used(self):
117        # we can disable already used acs
118        assert self.ac1.state != DISABLED
119        invalidate_accesscode('APP-1-11111111')
120        disable_accesscode('APP-1-11111111')
121        assert self.ac1.state == DISABLED
122
123    def test_reenable_accesscode(self):
124        # we can reenable disabled acs
125        disable_accesscode('APP-1-11111111')
126        result = reenable_accesscode('APP-1-11111111')
127        assert result is True
128        assert self.ac1.state != USED
129
130    def test_fire_transition(self):
131        # we can fire transitions generally
132        fire_transition('APP-1-11111111', 'use')
133        assert IWorkflowState(self.ac1).getState() is USED
134
135    def test_fire_transition_toward(self):
136        # the `toward` keyword is respected
137        fire_transition('APP-1-11111111', DISABLED, toward=True)
138        assert IWorkflowState(self.ac1).getState() is DISABLED
139
140    def test_fire_transition_no_site(self):
141        # when no site is available, we will get a TypeError
142        clearSite()
143        self.assertRaises(
144            KeyError,
145            fire_transition, 'APP-1-11111111', 'use')
146
147    def test_fire_transition_broken_ac_id(self):
148        # if we get an invalid access code id (of wrong format) we get
149        # ValueErrors
150        self.assertRaises(
151            ValueError,
152            fire_transition, '11111111', 'use')
153
154    def test_fire_transition_invalid_batch_id(self):
155        # if we request a non-existent batch_id, we'll get a KeyError
156        self.assertRaises(
157            KeyError,
158            fire_transition, 'FOO-1-11111111', 'use')
159
160    def test_fire_transition_invalid_ac(self):
161        # if we request a non-exitent access-code, we'll get a KeyError
162        self.assertRaises(
163            KeyError,
164            fire_transition, 'APP-1-NONSENSE', 'use')
165
166    def test_fire_transition_undef_trans_id(self):
167        # asking for undefined transition id means a KeyError
168        self.assertRaises(
169            KeyError,
170            fire_transition, 'APP-1-11111111', 'nonsense')
171
172    def test_fire_transition_invalid_transition(self):
173        # asking for a forbidden transition will result in
174        # InvalidTransitionError
175        self.assertRaises(
176            InvalidTransitionError,
177            fire_transition, 'APP-1-11111111', 'init') # already initialized
178
179    def test_fire_transition_comment(self):
180        # when we request a comment, it will also appear in history
181        fire_transition('APP-1-11111111', 'use', comment='Hi there!')
182        history = IObjectHistory(self.ac1)
183        msgs = history.messages
184        assert 'Hi there!' in msgs[-1]
185
186    def test_fire_transition_no_comment(self):
187        # without comment, the history should be without trailing garbage
188        fire_transition('APP-1-11111111', 'use')
189        history = IObjectHistory(self.ac1)
190        msgs = history.messages
191        assert msgs[-1].endswith('used by system')
192
193class AccessCodeTests(FunctionalTestCase):
194    # Tests for AccessCode class
195
196    layer = FunctionalLayer
197
198    def setUp(self):
199        super(AccessCodeTests, self).setUp()
200
201        # Prepopulate ZODB
202        app = University()
203        self.dc_root = tempfile.mkdtemp()
204        app['datacenter'].setStoragePath(self.dc_root)
205
206        # Prepopulate the ZODB...
207        self.getRootFolder()['app'] = app
208        self.app = self.getRootFolder()['app']
209
210        # Create batch
211        batch = AccessCodeBatch('now', 'manfred', 'APP', 6.6, 0)
212        self.app['accesscodes'].addBatch(batch)
213
214        # Fill batch with accesscodes
215        batch.addAccessCode(0, '11111111')
216
217        self.ac1 = batch.getAccessCode('APP-1-11111111')
218        setSite(self.app)
219        return
220
221    def tearDown(self):
222        shutil.rmtree(self.dc_root)
223        super(AccessCodeTests, self).tearDown()
224        return
225
226    def test_iface(self):
227        # AccessCodes fullfill their iface promises.
228        ac = AccessCode('1', '12345678')
229        assert verifyObject(IAccessCode, ac)
230        assert verifyClass(IAccessCode, AccessCode)
231
232    def test_history(self):
233        # Access codes have a history.
234        match = re.match(
235            '^....-..-.. ..:..:.. .+ - initialized by system',
236            self.ac1.history)
237        assert match is not None
238
239    def test_cost(self):
240        # The cost of an access code will be stored by handle_batch_added
241        # right after the batch has been added to the ZODB. Thus after
242        # creation of the batch, cost is still 0.0
243        cost = self.ac1.cost
244        assert cost == 0.0
245
246class AccessCodeBatchTests(FunctionalTestCase):
247    # Tests for AccessCodeBatch class
248
249    layer = FunctionalLayer
250
251    def setUp(self):
252        super(AccessCodeBatchTests, self).setUp()
253
254        # Prepopulate ZODB
255        app = University()
256        self.dc_root = tempfile.mkdtemp()
257        app['datacenter'].setStoragePath(self.dc_root)
258
259        # Prepopulate the ZODB...
260        self.getRootFolder()['app'] = app
261        self.app = self.getRootFolder()['app']
262
263        batch = AccessCodeBatch(    # create batch with zero entries
264            datetime.utcnow(), 'testuser', 'FOO', 9.99, 0)
265        self.app['accesscodes'].addBatch(batch)
266
267        self.ac1 = AccessCode(0, '11111111')
268        self.ac1.cost = 2345.0
269        self.ac2 = AccessCode(1, '22222222')
270        self.ac3 = AccessCode(2, '33333333')
271        batch['FOO-1-11111111'] = self.ac1
272        batch['FOO-1-22222222'] = self.ac2
273        batch['FOO-1-33333333'] = self.ac3
274        self.batch = batch
275
276        setSite(self.app)
277        return
278
279    def tearDown(self):
280        shutil.rmtree(self.dc_root)
281        super(AccessCodeBatchTests, self).tearDown()
282        return
283
284    def test_iface(self):
285        batch = AccessCodeBatch(
286            datetime(2009, 12, 23), 'Fred','APP', 12.12, 3, num=10)
287        assert verifyObject(IAccessCodeBatch, batch)
288        assert verifyClass(IAccessCodeBatch, AccessCodeBatch)
289
290    def test_container_contents(self):
291        assert 'SFE-0' in self.app['accesscodes']
292        assert 'HOS-0' in self.app['accesscodes']
293        assert 'CLR-0' in self.app['accesscodes']
294
295    def test_csv_export(self):
296        # Make sure CSV export of accesscodes works
297        batch = self.batch
298        invalidate_accesscode('FOO-1-11111111', comment='comment with "quotes"')
299        disable_accesscode('FOO-1-33333333')
300        basename = batch.archive()
301        result_path = os.path.join(batch._getStoragePath(), basename)
302        expected = '''
303"prefix","serial","ac","state","history","cost","owner"
304"FOO","9.99","1","0"
305"FOO","0","FOO-1-11111111","used","<YYYY-MM-DD hh:mm:ss> UTC - ...","2345.0",""
306"FOO","1","FOO-1-22222222","initialized","<YYYY-MM-DD hh:mm:ss> UTC - ...",""
307"FOO","2","FOO-1-33333333","disabled","<YYYY-MM-DD hh:mm:ss> UTC - ...",""
308'''[1:]
309        contents = open(result_path, 'rb').read()
310        self.assertMatches(expected, contents)
311
312class AccessCodeBatchContainerTests(FunctionalTestCase):
313    # Tests for AccessCodeContainer class
314
315    layer = FunctionalLayer
316
317    def setUp(self):
318        super(AccessCodeBatchContainerTests, self).setUp()
319
320        # Prepopulate ZODB
321        app = University()
322        self.dc_root = tempfile.mkdtemp()
323        app['datacenter'].setStoragePath(self.dc_root)
324
325        # Prepopulate the ZODB...
326        self.getRootFolder()['app'] = app
327        self.app = self.getRootFolder()['app']
328
329        self.import_sample1_src = os.path.join(
330            os.path.dirname(__file__), 'sample_import.csv')
331
332        batch = AccessCodeBatch(    # create batch with zero entries
333            datetime.now(), 'testuser', 'BAR', 9.99, 0)
334        self.app['accesscodes'].addBatch(batch)
335
336        self.ac1 = AccessCode(0, '11111111')
337        self.ac2 = AccessCode(1, '22222222')
338        self.ac3 = AccessCode(2, '33333333')
339        batch['BAR-1-11111111'] = self.ac1
340        batch['BAR-1-22222222'] = self.ac2
341        batch['BAR-1-33333333'] = self.ac3
342        self.batch = batch
343
344        setSite(self.app)
345        return
346
347    def tearDown(self):
348        shutil.rmtree(self.dc_root)
349        super(AccessCodeBatchContainerTests, self).tearDown()
350        return
351
352    def test_iface(self):
353        accesscodes = AccessCodeBatchContainer()
354        assert verifyObject(IAccessCodeBatchContainer, accesscodes)
355        assert verifyClass(IAccessCodeBatchContainer, AccessCodeBatchContainer)
356
357    def test_csv_import(self):
358        # Make sure we can reimport sample data from local sample_import.csv
359        batchcontainer = self.app['accesscodes']
360        shutil.copyfile(        # Copy sample to import dir
361            os.path.join(os.path.dirname(__file__), 'sample_import.csv'),
362            os.path.join(batchcontainer._getStoragePath(), 'sample_import.csv')
363            )
364        batchcontainer.reimport('sample_import.csv')
365        batch = batchcontainer.get(u'FOO-1', None)
366        self.assertTrue(batch is not None)
367        keys = [x for x in batch.keys()]
368        self.assertEqual(
369            keys,
370            [u'FOO-1-11111111', u'FOO-1-22222222', u'FOO-1-33333333'])
371        # Also cost has been stored correctly
372        self.assertEqual(batch['FOO-1-11111111'].cost,1000.0)
373
374    def test_getAccessCode(self):
375        batchcontainer = self.app['accesscodes']
376        result1 = batchcontainer.getAccessCode('BAR-1-11111111')
377        result2 = batchcontainer.getAccessCode('BAR-1-not-existent')
378        assert isinstance(result1, AccessCode)
379        assert result2 is None
380
381    def test_disableAccessCode(self):
382        batchcontainer = self.app['accesscodes']
383        result1 = batchcontainer.disable('BAR-1-11111111')
384        result2 = batchcontainer.disable('BAR-1-not-existent')
385        assert self.ac1.state is DISABLED
386        assert result2 is None
387
388    def test_enableAccessCode(self):
389        batchcontainer = self.app['accesscodes']
390        batchcontainer.disable('BAR-1-11111111')
391        result1 = batchcontainer.enable('BAR-1-11111111')
392        result2 = batchcontainer.enable('BAR-1-not-existent')
393        assert self.ac1.state is INITIALIZED
394        assert result2 is None
395
396class AccessCodePluginTests(FunctionalTestCase):
397    # Tests for AccessCodeContainer class
398
399    layer = FunctionalLayer
400
401    def setUp(self):
402        super(AccessCodePluginTests, self).setUp()
403
404        # Prepopulate ZODB
405        app = University()
406        self.dc_root = tempfile.mkdtemp()
407        app['datacenter'].setStoragePath(self.dc_root)
408
409        # Prepopulate the ZODB...
410        self.getRootFolder()['app'] = app
411        self.app = self.getRootFolder()['app']
412
413    def tearDown(self):
414        shutil.rmtree(self.dc_root)
415        super(AccessCodePluginTests, self).tearDown()
416        return
417
418    def test_iface(self):
419        plugin = AccessCodePlugin()
420        assert verifyObject(IKofaPluggable, plugin)
421        assert verifyClass(IKofaPluggable, AccessCodePlugin)
422
423    def test_update_w_ac_container(self):
424        # The plugin changes nothing, if there is already a container
425        plugin = AccessCodePlugin()
426        site = self.app
427        logger = site.logger
428        accesscodes = site['accesscodes']
429        plugin.update(site, 'app', logger)
430        assert site['accesscodes'] is accesscodes
431
432    def test_update_wo_ac_container(self):
433        # The plugin creates a new accesscodes container if it is missing
434        plugin = AccessCodePlugin()
435        site = self.app
436        logger = site.logger
437        del site['accesscodes']
438        plugin.update(site, 'app', logger)
439        assert 'accesscodes' in site
440
441checker = renormalizing.RENormalizing([
442        (re.compile('[\d]{10}'), '<10-DIGITS>'),
443        ])
444
445
446def test_suite():
447    suite = unittest.TestSuite()
448    for testcase in [
449        AccessCodeHelpersTests,
450        AccessCodeTests,
451        AccessCodeBatchTests,
452        AccessCodeBatchContainerTests,
453        AccessCodePluginTests,
454        ]:
455        suite.addTests(unittest.TestLoader().loadTestsFromTestCase(testcase))
456    for filename in [
457        #'accesscodes.txt',
458        'browser.txt'
459        ]:
460        path = os.path.join(
461            os.path.dirname(os.path.dirname(__file__)), filename)
462        test = doctest.DocFileSuite(
463            path,
464            module_relative=False,
465            setUp=setUp, tearDown=tearDown,
466            globs = dict(getRootFolder = getRootFolder),
467            optionflags = doctest.ELLIPSIS + doctest.NORMALIZE_WHITESPACE,
468            checker = checker,
469            )
470        test.layer = FunctionalLayer
471        suite.addTest(test)
472    return suite
Note: See TracBrowser for help on using the repository browser.