source: main/waeup.kofa/branches/uli-rm-bootstrap/src/waeup/kofa/accesscodes/tests/test_accesscode.py @ 17294

Last change on this file since 17294 was 12944, checked in by Henrik Bettermann, 10 years ago

Remove doctest. accesscode.txt is really outdated and has been replaced by Python tests.

  • Property svn:keywords set to Id
File size: 16.3 KB
Line 
1## $Id: test_accesscode.py 12944 2015-05-15 04:57:20Z 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_existing_batches(self):
358        self.assertEqual(sorted(self.app['accesscodes'].keys()),
359            [u'BAR-1', u'CLR-0', u'HOS-0', u'SFE-0', u'TSC-0'])
360
361    def test_csv_import(self):
362        # Make sure we can reimport sample data from local sample_import.csv
363        batchcontainer = self.app['accesscodes']
364        shutil.copyfile(        # Copy sample to import dir
365            os.path.join(os.path.dirname(__file__), 'sample_import.csv'),
366            os.path.join(batchcontainer._getStoragePath(), 'sample_import.csv')
367            )
368        batchcontainer.reimport('sample_import.csv')
369        batch = batchcontainer.get(u'FOO-1', None)
370        self.assertTrue(batch is not None)
371        keys = [x for x in batch.keys()]
372        self.assertEqual(
373            keys,
374            [u'FOO-1-11111111', u'FOO-1-22222222', u'FOO-1-33333333'])
375        # Also cost has been stored correctly
376        self.assertEqual(batch['FOO-1-11111111'].cost,1000.0)
377
378    def test_getAccessCode(self):
379        batchcontainer = self.app['accesscodes']
380        result1 = batchcontainer.getAccessCode('BAR-1-11111111')
381        result2 = batchcontainer.getAccessCode('BAR-1-not-existent')
382        assert isinstance(result1, AccessCode)
383        assert result2 is None
384
385    def test_disableAccessCode(self):
386        batchcontainer = self.app['accesscodes']
387        result1 = batchcontainer.disable('BAR-1-11111111')
388        result2 = batchcontainer.disable('BAR-1-not-existent')
389        assert self.ac1.state is DISABLED
390        assert result2 is None
391
392    def test_enableAccessCode(self):
393        batchcontainer = self.app['accesscodes']
394        batchcontainer.disable('BAR-1-11111111')
395        result1 = batchcontainer.enable('BAR-1-11111111')
396        result2 = batchcontainer.enable('BAR-1-not-existent')
397        assert self.ac1.state is INITIALIZED
398        assert result2 is None
399
400class AccessCodePluginTests(FunctionalTestCase):
401    # Tests for AccessCodeContainer class
402
403    layer = FunctionalLayer
404
405    def setUp(self):
406        super(AccessCodePluginTests, self).setUp()
407
408        # Prepopulate ZODB
409        app = University()
410        self.dc_root = tempfile.mkdtemp()
411        app['datacenter'].setStoragePath(self.dc_root)
412
413        # Prepopulate the ZODB...
414        self.getRootFolder()['app'] = app
415        self.app = self.getRootFolder()['app']
416
417    def tearDown(self):
418        shutil.rmtree(self.dc_root)
419        super(AccessCodePluginTests, self).tearDown()
420        return
421
422    def test_iface(self):
423        plugin = AccessCodePlugin()
424        assert verifyObject(IKofaPluggable, plugin)
425        assert verifyClass(IKofaPluggable, AccessCodePlugin)
426
427    def test_update_w_ac_container(self):
428        # The plugin changes nothing, if there is already a container
429        plugin = AccessCodePlugin()
430        site = self.app
431        logger = site.logger
432        accesscodes = site['accesscodes']
433        plugin.update(site, 'app', logger)
434        assert site['accesscodes'] is accesscodes
435
436    def test_update_wo_ac_container(self):
437        # The plugin creates a new accesscodes container if it is missing
438        plugin = AccessCodePlugin()
439        site = self.app
440        logger = site.logger
441        del site['accesscodes']
442        plugin.update(site, 'app', logger)
443        assert 'accesscodes' in site
444
445checker = renormalizing.RENormalizing([
446        (re.compile('[\d]{10}'), '<10-DIGITS>'),
447        ])
448
449
450def test_suite():
451    suite = unittest.TestSuite()
452    for testcase in [
453        AccessCodeHelpersTests,
454        AccessCodeTests,
455        AccessCodeBatchTests,
456        AccessCodeBatchContainerTests,
457        AccessCodePluginTests,
458        ]:
459        suite.addTests(unittest.TestLoader().loadTestsFromTestCase(testcase))
460    for filename in [
461        '../doctests/accesscode_browser.txt'
462        ]:
463        path = os.path.join(
464            os.path.dirname(os.path.dirname(__file__)), filename)
465        test = doctest.DocFileSuite(
466            path,
467            module_relative=False,
468            setUp=setUp, tearDown=tearDown,
469            globs = dict(getRootFolder = getRootFolder),
470            optionflags = doctest.ELLIPSIS + doctest.NORMALIZE_WHITESPACE,
471            checker = checker,
472            )
473        test.layer = FunctionalLayer
474        suite.addTest(test)
475    return suite
Note: See TracBrowser for help on using the repository browser.