source: main/waeup.ikoba/trunk/src/waeup/ikoba/utils/tests/test_converters.py @ 17388

Last change on this file since 17388 was 14022, checked in by Henrik Bettermann, 8 years ago

Use defaultFactory.

  • Property svn:keywords set to Id
File size: 15.5 KB
Line 
1## $Id: test_converters.py 14022 2016-07-07 06:45:09Z 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 converterts.
20"""
21import datetime
22import shutil
23import tempfile
24import unittest
25from decimal import Decimal
26from zope import schema
27from zope.component import provideUtility
28from zope.component.factory import Factory
29from zope.component.hooks import clearSite
30from zope.component.interfaces import IFactory
31from zope.formlib import form
32from zope.interface import (
33    Interface, implements, invariant, Invalid, implementedBy, verify)
34
35from waeup.ikoba.app import Company
36from waeup.ikoba.testing import FunctionalLayer, FunctionalTestCase
37from waeup.ikoba.interfaces import (
38    SimpleIkobaVocabulary, IFieldConverter,
39    DELETION_MARKER, IGNORE_MARKER)
40from waeup.ikoba.products.productoptions import ProductOptionField
41from waeup.ikoba.utils.converters import (
42    IObjectConverter, IFieldConverter, DefaultFieldConverter,
43    ListFieldConverter, PhoneNumberFieldConverter, ProductOptionConverter,
44    DefaultObjectConverter)
45from waeup.ikoba.utils.helpers import attrs_to_fields
46
47colors = SimpleIkobaVocabulary(
48    ('Red', u'red'),
49    ('Green', u'green'),
50    ('Blue', u'blue'),
51    )
52car_nums = SimpleIkobaVocabulary(
53    ('None', 0),
54    ('One', 1),
55    ('Two', 2),
56    ('Three', 3),
57    )
58class IContact(Interface):
59    """Sample interface for sample content type used here in tests.
60    """
61    name = schema.TextLine(
62        title = u'Name',
63        default = u'Manfred',
64        readonly = True,
65        )
66    age = schema.Int(
67        title = u'Age',
68        default = 23,
69        required = True,
70        )
71    city = schema.TextLine(
72        title = u'City',
73        required = True,
74        )
75    vip = schema.Bool(
76        title = u'Celebrity',
77        default = False,
78        required = True,
79        )
80    birthday = schema.Date(
81        title = u'Birthday',
82        default = None,
83        )
84    fav_color = schema.Choice(
85        title = u'Favourite color',
86        default = u'red',
87        vocabulary = colors,
88        )
89    num_cars = schema.Choice(
90        title = u'Number of cars owned',
91        default = None,
92        vocabulary = car_nums,
93        )
94    options = schema.List(
95        title = u'Options',
96        value_type = ProductOptionField(),
97        required = True,
98        defaultFactory=list,
99        )
100    friends = schema.List(
101        title = u'Friends',
102        value_type = schema.TextLine(
103            title = u'Name',
104            )
105        )
106
107    @invariant
108    def kevinIsYoung(contact):
109        if contact.age > 16 and contact.name == 'Kevin':
110            raise Invalid('Kevins are age 16 or below.')
111
112class Contact(object):
113    """Sample content type.
114    """
115    implements(IContact)
116Contact = attrs_to_fields(Contact)
117
118form_fields_select = form.Fields(IContact).select('name', 'vip')
119form_fields_omit = form.Fields(IContact).omit('name', 'vip')
120
121class TestContacts(unittest.TestCase):
122    # make sure the Contact class inhere works as expected
123
124    def test_default_lists_not_shared(self):
125        # 'grades' is a Contact property with empty list as default.
126        # Ensure lists are unique if set as default this test fails if
127        # `default` is set to empty list in interface.
128        c1 = Contact()
129        c2 = Contact()
130        c1.options.append('foo')
131        self.assertTrue(hasattr(c2, 'options'))
132        self.assertTrue(isinstance(c2.options, list))
133        self.assertEqual(c2.options, [])
134
135class FieldConverterTests(unittest.TestCase):
136
137    def test_iface(self):
138        # make sure we fullfill interface contracts
139        obj1 = DefaultFieldConverter(None)
140        obj2 = ListFieldConverter(None)
141        obj3 = PhoneNumberFieldConverter(None)
142        obj4 = ProductOptionConverter(None)
143        verify.verifyObject(IFieldConverter, obj1)
144        verify.verifyObject(IFieldConverter, obj2)
145        verify.verifyObject(IFieldConverter, obj3)
146        verify.verifyObject(IFieldConverter, obj4)
147        verify.verifyClass(IFieldConverter, DefaultFieldConverter)
148        verify.verifyClass(IFieldConverter, ListFieldConverter)
149        verify.verifyClass(IFieldConverter, PhoneNumberFieldConverter)
150        verify.verifyClass(IFieldConverter, ProductOptionConverter)
151        return
152
153class ConverterTests(FunctionalTestCase):
154
155    layer = FunctionalLayer
156
157    def setUp(self):
158        super(ConverterTests, self).setUp()
159
160        # Setup a sample site for each test
161        app = Company()
162        self.dc_root = tempfile.mkdtemp()
163        app['datacenter'].setStoragePath(self.dc_root)
164
165        # Prepopulate the ZODB...
166        self.getRootFolder()['app'] = app
167        self.app = self.getRootFolder()['app']
168
169        self.workdir = tempfile.mkdtemp()
170
171        # Create a factory for contacts and register it as global utility
172        factory = Factory(Contact)
173        provideUtility(factory, IFactory, 'contact')
174        return
175
176    def tearDown(self):
177        super(ConverterTests, self).tearDown()
178        shutil.rmtree(self.workdir)
179        shutil.rmtree(self.dc_root)
180        clearSite()
181        return
182
183    def test_iface(self):
184        # make sure we fullfill interface contracts
185        obj = DefaultObjectConverter(IContact)
186        verify.verifyObject(IObjectConverter, obj)
187        verify.verifyClass(IObjectConverter, DefaultObjectConverter)
188        return
189
190    def test_valid_data(self):
191        contact = Contact()
192        contact.age = 33
193        input_data = dict(name='Rudi', age='99')
194        converter = IObjectConverter(IContact) # a converter to IContact
195        err, inv_err, data = converter.fromStringDict(
196            input_data, contact)
197        assert data['name'] == 'Rudi'
198        assert data['age'] == 99
199        return
200
201    def test_bool(self):
202        contact1 = Contact()
203        contact2 = Contact()
204        input_data1 = dict(vip='on')
205        input_data2 = dict(vip='')
206        converter = IObjectConverter(IContact) # a converter to IContact
207        err1, inv_err1, data1 = converter.fromStringDict(
208            input_data1, contact1)
209        err2, inv_err2, data2 = converter.fromStringDict(
210            input_data2, contact2)
211        assert data1['vip'] is True
212        assert data2['vip'] is False
213
214    def test_bool_nonstandard_values1(self):
215        # We accept 'true', 'True', 'tRuE', 'faLSE' and similar.
216        contact1 = Contact()
217        contact2 = Contact()
218        input_data1 = dict(vip='True')
219        input_data2 = dict(vip='false')
220        converter = IObjectConverter(IContact) # a converter to IContact
221        err1, inv_err1, data1 = converter.fromStringDict(
222            input_data1, contact1)
223        err2, inv_err2, data2 = converter.fromStringDict(
224            input_data2, contact2)
225        assert data1['vip'] is True
226        assert data2['vip'] is False
227
228    def test_bool_nonstandard_values2(self):
229        # We accept '1' and '0' as bool values.
230        contact1 = Contact()
231        contact2 = Contact()
232        input_data1 = dict(vip='1')
233        input_data2 = dict(vip='0')
234        converter = IObjectConverter(IContact) # a converter to IContact
235        err1, inv_err1, data1 = converter.fromStringDict(
236            input_data1, contact1)
237        err2, inv_err2, data2 = converter.fromStringDict(
238            input_data2, contact2)
239        assert data1['vip'] is True
240        assert data2['vip'] is False
241
242    def test_bool_nonstandard_values3(self):
243        # We accept 'yEs', 'no' and similar as bool values.
244        contact1 = Contact()
245        contact2 = Contact()
246        input_data1 = dict(vip='Yes')
247        input_data2 = dict(vip='no')
248        converter = IObjectConverter(IContact) # a converter to IContact
249        err1, inv_err1, data1 = converter.fromStringDict(
250            input_data1, contact1)
251        err2, inv_err2, data2 = converter.fromStringDict(
252            input_data2, contact2)
253        assert data1['vip'] is True
254        assert data2['vip'] is False
255
256    def test_int(self):
257        contact = Contact()
258        input_data = dict(age='99')
259        converter = IObjectConverter(IContact) # a converter to IContact
260        err, inv_err, data = converter.fromStringDict(
261            input_data, contact)
262        assert data['age'] == 99
263        return
264
265    def test_int_invalid(self):
266        contact = Contact()
267        input_data = dict(age='sweet sixteen')
268        converter = IObjectConverter(IContact) # a converter to IContact
269        err, inv_err, new_contact = converter.fromStringDict(
270            input_data, contact)
271        self.assertEqual(err, [('age', u'Invalid integer data')])
272        return
273
274    def test_textline(self):
275        contact = Contact()
276        input_data = dict(name='Rudi')
277        converter = IObjectConverter(IContact) # a converter to IContact
278        err, inv_err, data = converter.fromStringDict(
279            input_data, contact)
280        self.assertEqual(data['name'], u'Rudi')
281        assert isinstance(data['name'], unicode)
282        return
283
284    def test_invariant(self):
285        contact = Contact()
286        input_data = dict(name='Kevin', age='22')
287        converter = IObjectConverter(IContact) # a converter to IContact
288        err, inv_err, new_contact = converter.fromStringDict(
289            input_data, contact)
290        self.assertEqual(inv_err, ['Kevins are age 16 or below.'])
291        return
292
293    def test_date(self):
294        contact = Contact()
295        converter = IObjectConverter(IContact) # a converter to IContact
296
297        # The input format for dates: YYYY-MM-DD
298        err, inv_err, data = converter.fromStringDict(
299            dict(birthday='1945-12-23'), contact)
300        assert data['birthday'] == datetime.date(1945, 12, 23)
301        assert isinstance(data['birthday'], datetime.date)
302
303        err, inv_err, data = converter.fromStringDict(
304            dict(birthday='1945-23-12'), contact)
305        #assert data['birthday'] == datetime.date(1945, 12, 23)
306        assert err[0][1] =='Invalid datetime data'
307
308        # '08' is not interpreted as octal number
309        err, inv_err, data = converter.fromStringDict(
310            dict(birthday='1945-12-08'), contact)
311        assert data['birthday'] == datetime.date(1945, 12, 8)
312        return
313
314    def test_date_invalid(self):
315        contact = Contact()
316        converter = IObjectConverter(IContact) # a converter to IContact
317        err, inv_err, data = converter.fromStringDict(
318            dict(birthday='not-a-date'), contact)
319        self.assertEqual(err, [('birthday', u'Invalid datetime data')])
320
321    def test_inject_formfields_select(self):
322        # We can use our own formfields and select only a subset of fields
323        contact = Contact()
324        converter = IObjectConverter(IContact) # a converter to IContact
325        input_data = dict(name='Bruno', age='99', vip='on')
326        err, inv_err, data = converter.fromStringDict(
327            input_data, contact, form_fields=form_fields_select)
328        self.assertEqual(data['name'], 'Bruno')
329        assert 'age' not in data.keys()
330        assert data['vip'] is True
331        return
332
333    def test_inject_formfields_omit(self):
334        # We can use our own formfields and omit some fields
335        contact = Contact()
336        converter = IObjectConverter(IContact) # a converter to IContact
337        input_data = dict(name='Bruno', age='99', vip='on')
338        err, inv_err, data = converter.fromStringDict(
339            input_data, contact, form_fields=form_fields_omit)
340        self.assertEqual(data['age'], 99)
341        assert 'name' not in data.keys()
342        assert 'vip' not in data.keys()
343        return
344
345    def test_factory(self):
346        # We can use factories to convert values
347        converter = IObjectConverter(IContact) # a converter to IContact
348        # pass string ``contact`` instead of a real object
349        err, inv_err, data = converter.fromStringDict(
350            dict(name='Gabi', age='23'), 'contact')
351        self.assertEqual(data['age'], 23)
352        self.assertEqual(data['name'], u'Gabi')
353        return
354
355    def test_choice_vocab(self):
356        # We can handle vocabularies
357        converter = IObjectConverter(IContact) # a converter to IContact
358        err, inv_err, data = converter.fromStringDict(
359            dict(fav_color='blue'), 'contact')
360        assert data['fav_color'] == u'blue'
361        assert isinstance(data['fav_color'], unicode)
362        return
363
364    def test_choice_vocab_invalid_value(self):
365        # We can handle vocabularies
366        converter = IObjectConverter(IContact) # a converter to IContact
367        err, inv_err, data = converter.fromStringDict(
368            dict(fav_color='magenta'), 'contact')
369        self.assertEqual(err, [('fav_color', u'Invalid value')])
370        assert 'fav_color' not in data.keys()
371        return
372
373    def test_non_string_choice(self):
374        # We can handle vocabs with non-string values
375        converter = IObjectConverter(IContact) # a converter to IContact
376        err, inv_err, data = converter.fromStringDict(
377            dict(num_cars='1'), 'contact')
378        assert data['num_cars'] == 1
379        return
380
381    def test_list_of_textlines(self):
382        # We can convert lists of text lines
383        converter = IObjectConverter(IContact)
384        err, inv_err, data = converter.fromStringDict(
385            {"friends": "['Fred', 'Wilma']"}, 'contact')
386        self.assertEqual(
387            data, {'friends': ['Fred', 'Wilma']})
388        return
389
390    def test_list_of_optionentries(self):
391        # We can handle lists of result entries
392        converter = IObjectConverter(IContact)
393        # get currently valid values
394        req_string = u"[(u'T1', u'55.6', u'USD'), (u'T2', u'66.6', u'EUR')]"
395        err, inv_err, data = converter.fromStringDict(
396            {"options": req_string,}, 'contact')
397        result_options = data['options']
398        self.assertTrue(isinstance(result_options, list))
399        self.assertEqual(len(result_options), 2)
400        self.assertEqual(result_options[0].title, 'T1')
401        self.assertEqual(result_options[0].fee, Decimal('55.6'))
402        self.assertEqual(result_options[0].currency, 'USD')
403        self.assertEqual(result_options[1].title, 'T2')
404        self.assertEqual(result_options[1].fee, Decimal('66.6'))
405        self.assertEqual(result_options[1].currency, 'EUR')
406        return
407
408    def test_ignore_values(self):
409        # in update mode we ignore marked values
410        converter = IObjectConverter(IContact)
411        err, inv_err, data = converter.fromStringDict(
412            {"friends": IGNORE_MARKER},
413            Contact(),
414            mode='update')
415        # the ignored key/value are not part of the result
416        self.assertEqual(data, {})
417        return
418
419    def test_delete_values(self):
420        # in update mode we delete values marked accordingly
421        # 'deleting' means setting to missing_value or to default if required.
422        converter = IObjectConverter(IContact)
423        err, inv_err, data = converter.fromStringDict(
424            {"options": DELETION_MARKER,
425             "friends": DELETION_MARKER},
426            'contact', mode='update')
427        # grades are about to be set to default, friends to None
428        self.assertEqual(data, {'options': [], 'friends': None})
429        return
Note: See TracBrowser for help on using the repository browser.