source: main/waeup.kofa/branches/0.2/src/waeup/kofa/utils/tests/test_converters.py @ 17808

Last change on this file since 17808 was 8216, checked in by uli, 13 years ago

Test DELETION_MARKER and IGNORE_MARKER functionality of converters.

  • Property svn:keywords set to Id
File size: 15.2 KB
Line 
1## $Id: test_converters.py 8216 2012-04-19 13:05:07Z uli $
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 zope import schema
26from zope.component import provideUtility
27from zope.component.factory import Factory
28from zope.component.hooks import clearSite
29from zope.component.interfaces import IFactory
30from zope.formlib import form
31from zope.interface import (
32    Interface, implements, invariant, Invalid, implementedBy, verify)
33
34from waeup.kofa.app import University
35from waeup.kofa.testing import FunctionalLayer, FunctionalTestCase
36from waeup.kofa.interfaces import (
37    SimpleKofaVocabulary, SubjectSource, GradeSource, IFieldConverter,
38    DELETION_MARKER, IGNORE_MARKER)
39from waeup.kofa.schoolgrades import ResultEntryField
40from waeup.kofa.university import Faculty
41from waeup.kofa.utils.converters import (
42    IObjectConverter, IFieldConverter, DefaultFieldConverter,
43    ListFieldConverter, PhoneNumberFieldConverter, ResultEntryConverter,
44    DefaultObjectConverter)
45from waeup.kofa.utils.helpers import attrs_to_fields
46
47colors = SimpleKofaVocabulary(
48    ('Red', u'red'),
49    ('Green', u'green'),
50    ('Blue', u'blue'),
51    )
52car_nums = SimpleKofaVocabulary(
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    grades = schema.List(
95        title = u'School Grades',
96        value_type = ResultEntryField(),
97        required = True,
98        default = [],
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 ContactFactory(object):
122    """A factory for faculty containers.
123    """
124    implements(IContact)
125
126    def __call__(self, *args, **kw):
127        return Faculty()
128
129    def getInterfaces(self):
130        return implementedBy(Faculty)
131
132class FieldConverterTests(unittest.TestCase):
133
134    def test_iface(self):
135        # make sure we fullfill interface contracts
136        obj1 = DefaultFieldConverter(None)
137        obj2 = ListFieldConverter(None)
138        obj3 = PhoneNumberFieldConverter(None)
139        obj4 = ResultEntryConverter(None)
140        verify.verifyObject(IFieldConverter, obj1)
141        verify.verifyObject(IFieldConverter, obj2)
142        verify.verifyObject(IFieldConverter, obj3)
143        verify.verifyObject(IFieldConverter, obj4)
144        verify.verifyClass(IFieldConverter, DefaultFieldConverter)
145        verify.verifyClass(IFieldConverter, ListFieldConverter)
146        verify.verifyClass(IFieldConverter, PhoneNumberFieldConverter)
147        verify.verifyClass(IFieldConverter, ResultEntryConverter)
148        return
149
150class ConverterTests(FunctionalTestCase):
151
152    layer = FunctionalLayer
153
154    def setUp(self):
155        super(ConverterTests, self).setUp()
156
157        # Setup a sample site for each test
158        app = University()
159        self.dc_root = tempfile.mkdtemp()
160        app['datacenter'].setStoragePath(self.dc_root)
161
162        # Prepopulate the ZODB...
163        self.getRootFolder()['app'] = app
164        self.app = self.getRootFolder()['app']
165
166        self.workdir = tempfile.mkdtemp()
167
168        # Create a factory for contacts and register it as global utility
169        factory = Factory(Contact)
170        provideUtility(factory, IFactory, 'contact')
171        return
172
173    def tearDown(self):
174        super(ConverterTests, self).tearDown()
175        shutil.rmtree(self.workdir)
176        shutil.rmtree(self.dc_root)
177        clearSite()
178        return
179
180    def test_iface(self):
181        # make sure we fullfill interface contracts
182        obj = DefaultObjectConverter(IContact)
183        verify.verifyObject(IObjectConverter, obj)
184        verify.verifyClass(IObjectConverter, DefaultObjectConverter)
185        return
186
187    def test_valid_data(self):
188        contact = Contact()
189        contact.age = 33
190        input_data = dict(name='Rudi', age='99')
191        converter = IObjectConverter(IContact) # a converter to IContact
192        err, inv_err, data = converter.fromStringDict(
193            input_data, contact)
194        assert data['name'] == 'Rudi'
195        assert data['age'] == 99
196        return
197
198    def test_bool(self):
199        contact1 = Contact()
200        contact2 = Contact()
201        input_data1 = dict(vip='on')
202        input_data2 = dict(vip='')
203        converter = IObjectConverter(IContact) # a converter to IContact
204        err1, inv_err1, data1 = converter.fromStringDict(
205            input_data1, contact1)
206        err2, inv_err2, data2 = converter.fromStringDict(
207            input_data2, contact2)
208        assert data1['vip'] is True
209        assert data2['vip'] is False
210
211    def test_bool_nonstandard_values1(self):
212        # We accept 'true', 'True', 'tRuE', 'faLSE' and similar.
213        contact1 = Contact()
214        contact2 = Contact()
215        input_data1 = dict(vip='True')
216        input_data2 = dict(vip='false')
217        converter = IObjectConverter(IContact) # a converter to IContact
218        err1, inv_err1, data1 = converter.fromStringDict(
219            input_data1, contact1)
220        err2, inv_err2, data2 = converter.fromStringDict(
221            input_data2, contact2)
222        assert data1['vip'] is True
223        assert data2['vip'] is False
224
225    def test_bool_nonstandard_values2(self):
226        # We accept '1' and '0' as bool values.
227        contact1 = Contact()
228        contact2 = Contact()
229        input_data1 = dict(vip='1')
230        input_data2 = dict(vip='0')
231        converter = IObjectConverter(IContact) # a converter to IContact
232        err1, inv_err1, data1 = converter.fromStringDict(
233            input_data1, contact1)
234        err2, inv_err2, data2 = converter.fromStringDict(
235            input_data2, contact2)
236        assert data1['vip'] is True
237        assert data2['vip'] is False
238
239    def test_bool_nonstandard_values3(self):
240        # We accept 'yEs', 'no' and similar as bool values.
241        contact1 = Contact()
242        contact2 = Contact()
243        input_data1 = dict(vip='Yes')
244        input_data2 = dict(vip='no')
245        converter = IObjectConverter(IContact) # a converter to IContact
246        err1, inv_err1, data1 = converter.fromStringDict(
247            input_data1, contact1)
248        err2, inv_err2, data2 = converter.fromStringDict(
249            input_data2, contact2)
250        assert data1['vip'] is True
251        assert data2['vip'] is False
252
253    def test_int(self):
254        contact = Contact()
255        input_data = dict(age='99')
256        converter = IObjectConverter(IContact) # a converter to IContact
257        err, inv_err, data = converter.fromStringDict(
258            input_data, contact)
259        assert data['age'] == 99
260        return
261
262    def test_int_invalid(self):
263        contact = Contact()
264        input_data = dict(age='sweet sixteen')
265        converter = IObjectConverter(IContact) # a converter to IContact
266        err, inv_err, new_contact = converter.fromStringDict(
267            input_data, contact)
268        self.assertEqual(err, [('age', u'Invalid integer data')])
269        return
270
271    def test_textline(self):
272        contact = Contact()
273        input_data = dict(name='Rudi')
274        converter = IObjectConverter(IContact) # a converter to IContact
275        err, inv_err, data = converter.fromStringDict(
276            input_data, contact)
277        self.assertEqual(data['name'], u'Rudi')
278        assert isinstance(data['name'], unicode)
279        return
280
281    def test_invariant(self):
282        contact = Contact()
283        input_data = dict(name='Kevin', age='22')
284        converter = IObjectConverter(IContact) # a converter to IContact
285        err, inv_err, new_contact = converter.fromStringDict(
286            input_data, contact)
287        self.assertEqual(inv_err, ['Kevins are age 16 or below.'])
288        return
289
290    def test_date(self):
291        contact = Contact()
292        converter = IObjectConverter(IContact) # a converter to IContact
293
294        # The input format for dates: YYYY-MM-DD
295        err, inv_err, data = converter.fromStringDict(
296            dict(birthday='1945-12-23'), contact)
297        assert data['birthday'] == datetime.date(1945, 12, 23)
298        assert isinstance(data['birthday'], datetime.date)
299
300        err, inv_err, data = converter.fromStringDict(
301            dict(birthday='1945-23-12'), contact)
302        #assert data['birthday'] == datetime.date(1945, 12, 23)
303        assert err[0][1] =='Invalid datetime data'
304
305        # '08' is not interpreted as octal number
306        err, inv_err, data = converter.fromStringDict(
307            dict(birthday='1945-12-08'), contact)
308        assert data['birthday'] == datetime.date(1945, 12, 8)
309        return
310
311    def test_date_invalid(self):
312        contact = Contact()
313        converter = IObjectConverter(IContact) # a converter to IContact
314        err, inv_err, data = converter.fromStringDict(
315            dict(birthday='not-a-date'), contact)
316        self.assertEqual(err, [('birthday', u'Invalid datetime data')])
317
318    def test_inject_formfields_select(self):
319        # We can use our own formfields and select only a subset of fields
320        contact = Contact()
321        converter = IObjectConverter(IContact) # a converter to IContact
322        input_data = dict(name='Bruno', age='99', vip='on')
323        err, inv_err, data = converter.fromStringDict(
324            input_data, contact, form_fields=form_fields_select)
325        self.assertEqual(data['name'], 'Bruno')
326        assert 'age' not in data.keys()
327        assert data['vip'] is True
328        return
329
330    def test_inject_formfields_omit(self):
331        # We can use our own formfields and omit some fields
332        contact = Contact()
333        converter = IObjectConverter(IContact) # a converter to IContact
334        input_data = dict(name='Bruno', age='99', vip='on')
335        err, inv_err, data = converter.fromStringDict(
336            input_data, contact, form_fields=form_fields_omit)
337        self.assertEqual(data['age'], 99)
338        assert 'name' not in data.keys()
339        assert 'vip' not in data.keys()
340        return
341
342    def test_factory(self):
343        # We can use factories to convert values
344        converter = IObjectConverter(IContact) # a converter to IContact
345        # pass string ``contact`` instead of a real object
346        err, inv_err, data = converter.fromStringDict(
347            dict(name='Gabi', age='23'), 'contact')
348        self.assertEqual(data['age'], 23)
349        self.assertEqual(data['name'], u'Gabi')
350        return
351
352    def test_choice_vocab(self):
353        # We can handle vocabularies
354        converter = IObjectConverter(IContact) # a converter to IContact
355        err, inv_err, data = converter.fromStringDict(
356            dict(fav_color='blue'), 'contact')
357        assert data['fav_color'] == u'blue'
358        assert isinstance(data['fav_color'], unicode)
359        return
360
361    def test_choice_vocab_invalid_value(self):
362        # We can handle vocabularies
363        converter = IObjectConverter(IContact) # a converter to IContact
364        err, inv_err, data = converter.fromStringDict(
365            dict(fav_color='magenta'), 'contact')
366        self.assertEqual(err, [('fav_color', u'Invalid value')])
367        assert 'fav_color' not in data.keys()
368        return
369
370    def test_non_string_choice(self):
371        # We can handle vocabs with non-string values
372        converter = IObjectConverter(IContact) # a converter to IContact
373        err, inv_err, data = converter.fromStringDict(
374            dict(num_cars='1'), 'contact')
375        assert data['num_cars'] == 1
376        return
377
378    def test_list_of_textlines(self):
379        # We can convert lists of text lines
380        converter = IObjectConverter(IContact)
381        err, inv_err, data = converter.fromStringDict(
382            {"friends": "['Fred', 'Wilma']"}, 'contact')
383        self.assertEqual(
384            data, {'friends': ['Fred', 'Wilma']})
385        return
386
387    def test_list_of_resultentries(self):
388        # We can handle lists of result entries
389        converter = IObjectConverter(IContact)
390        # get currently valid values
391        s_src, g_src = SubjectSource(), GradeSource()
392        s_val1, s_val2 = list(s_src.factory.getValues())[0:2]
393        g_val1, g_val2 = list(g_src.factory.getValues())[0:2]
394        req_string = u"[('%s', '%s'), ('%s', '%s')]" % (
395                s_val1, g_val1, s_val2, g_val2)
396        err, inv_err, data = converter.fromStringDict(
397            {"grades": req_string,
398             },
399            'contact')
400        result_grades = data['grades']
401        self.assertTrue(isinstance(result_grades, list))
402        self.assertEqual(len(result_grades), 2)
403        self.assertEqual(result_grades[0].subject, s_val1)
404        self.assertEqual(result_grades[0].grade, g_val1)
405        return
406
407    def test_ignore_values(self):
408        # in update mode we ignore marked values
409        converter = IObjectConverter(IContact)
410        err, inv_err, data = converter.fromStringDict(
411            {"friends": IGNORE_MARKER},
412            Contact(),
413            mode='update')
414        # the ignored key/value are not part of the result
415        self.assertEqual(data, {})
416        return
417
418    def test_delete_values(self):
419        # in update mode we delete values marked accordingly
420        # 'deleting' means setting to missing_value or to default if required.
421        converter = IObjectConverter(IContact)
422        err, inv_err, data = converter.fromStringDict(
423            {"grades": DELETION_MARKER,
424             "friends": DELETION_MARKER},
425            'contact', mode='update')
426        # grades are about to be set to default, friends to None
427        self.assertEqual(data, {'grades': [], 'friends': None})
428        return
Note: See TracBrowser for help on using the repository browser.