source: main/waeup.kofa/trunk/src/waeup/kofa/utils/tests/test_converters.py

Last change on this file was 16818, checked in by Henrik Bettermann, 3 years ago

Don't complain but remove leading and trailing whitespaces while converting values during import.

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