1# -*- coding: utf-8 -*-
2#    Copyright 2013 Red Hat, Inc.
3#
4#    Licensed under the Apache License, Version 2.0 (the "License"); you may
5#    not use this file except in compliance with the License. You may obtain
6#    a copy of the License at
7#
8#         http://www.apache.org/licenses/LICENSE-2.0
9#
10#    Unless required by applicable law or agreed to in writing, software
11#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13#    License for the specific language governing permissions and limitations
14#    under the License.
15
16import datetime
17from unittest import mock
18import warnings
19
20import iso8601
21import netaddr
22import testtools
23
24from oslo_versionedobjects import _utils
25from oslo_versionedobjects import base as obj_base
26from oslo_versionedobjects import exception
27from oslo_versionedobjects import fields
28from oslo_versionedobjects import test
29
30
31class FakeFieldType(fields.FieldType):
32    def coerce(self, obj, attr, value):
33        return '*%s*' % value
34
35    def to_primitive(self, obj, attr, value):
36        return '!%s!' % value
37
38    def from_primitive(self, obj, attr, value):
39        return value[1:-1]
40
41    def get_schema(self):
42        return {'type': ['foo']}
43
44
45class FakeEnum(fields.Enum):
46    FROG = "frog"
47    PLATYPUS = "platypus"
48    ALLIGATOR = "alligator"
49
50    ALL = (FROG, PLATYPUS, ALLIGATOR)
51
52    def __init__(self, **kwargs):
53        super(FakeEnum, self).__init__(valid_values=FakeEnum.ALL,
54                                       **kwargs)
55
56
57class FakeEnumAlt(fields.Enum):
58    FROG = "frog"
59    PLATYPUS = "platypus"
60    AARDVARK = "aardvark"
61
62    ALL = set([FROG, PLATYPUS, AARDVARK])
63
64    def __init__(self, **kwargs):
65        super(FakeEnumAlt, self).__init__(valid_values=FakeEnumAlt.ALL,
66                                          **kwargs)
67
68
69class FakeEnumField(fields.BaseEnumField):
70    AUTO_TYPE = FakeEnum()
71
72
73class FakeStateMachineField(fields.StateMachine):
74
75    ACTIVE = 'ACTIVE'
76    PENDING = 'PENDING'
77    ERROR = 'ERROR'
78
79    ALLOWED_TRANSITIONS = {
80        ACTIVE: {
81            PENDING,
82            ERROR
83        },
84        PENDING: {
85            ACTIVE,
86            ERROR
87        },
88        ERROR: {
89            PENDING,
90        },
91    }
92
93    _TYPES = (ACTIVE, PENDING, ERROR)
94
95    def __init__(self, **kwargs):
96        super(FakeStateMachineField, self).__init__(self._TYPES, **kwargs)
97
98
99class FakeEnumAltField(fields.BaseEnumField):
100    AUTO_TYPE = FakeEnumAlt()
101
102
103class TestFieldType(test.TestCase):
104    def test_get_schema(self):
105        self.assertRaises(NotImplementedError, fields.FieldType().get_schema)
106
107
108class TestField(test.TestCase):
109    def setUp(self):
110        super(TestField, self).setUp()
111        self.field = fields.Field(FakeFieldType())
112        self.coerce_good_values = [('foo', '*foo*')]
113        self.coerce_bad_values = []
114        self.to_primitive_values = [('foo', '!foo!')]
115        self.from_primitive_values = [('!foo!', 'foo')]
116
117    def test_coerce_good_values(self):
118        for in_val, out_val in self.coerce_good_values:
119            self.assertEqual(out_val, self.field.coerce('obj', 'attr', in_val))
120
121    def test_coerce_bad_values(self):
122        for in_val in self.coerce_bad_values:
123            self.assertRaises((TypeError, ValueError),
124                              self.field.coerce, 'obj', 'attr', in_val)
125
126    def test_to_primitive(self):
127        for in_val, prim_val in self.to_primitive_values:
128            self.assertEqual(prim_val, self.field.to_primitive('obj', 'attr',
129                                                               in_val))
130
131    def test_from_primitive(self):
132        class ObjectLikeThing(object):
133            _context = 'context'
134
135        for prim_val, out_val in self.from_primitive_values:
136            self.assertEqual(out_val, self.field.from_primitive(
137                ObjectLikeThing, 'attr', prim_val))
138
139    def test_stringify(self):
140        self.assertEqual('123', self.field.stringify(123))
141
142
143class TestSchema(test.TestCase):
144    def setUp(self):
145        super(TestSchema, self).setUp()
146        self.field = fields.Field(FakeFieldType(), nullable=True,
147                                  default='', read_only=False)
148
149    def test_get_schema(self):
150        self.assertEqual({'type': ['foo', 'null'], 'default': '',
151                          'readonly': False},
152                         self.field.get_schema())
153
154
155class TestString(TestField):
156    def setUp(self):
157        super(TestString, self).setUp()
158        self.field = fields.StringField()
159        self.coerce_good_values = [
160            ('foo', 'foo'), (1, '1'), (1.0, '1.0'), (True, 'True')]
161        self.coerce_bad_values = [None]
162        self.to_primitive_values = self.coerce_good_values[0:1]
163        self.from_primitive_values = self.coerce_good_values[0:1]
164
165    def test_stringify(self):
166        self.assertEqual("'123'", self.field.stringify(123))
167
168    def test_fieldtype_get_schema(self):
169        self.assertEqual({'type': ['string']}, self.field._type.get_schema())
170
171    def test_get_schema(self):
172        self.assertEqual({'type': ['string'], 'readonly': False},
173                         self.field.get_schema())
174
175
176class TestSensitiveString(TestString):
177    def setUp(self):
178        super(TestSensitiveString, self).setUp()
179        self.field = fields.SensitiveStringField()
180
181    def test_stringify(self):
182        payload = """{'admin_password':'mypassword'}"""
183        expected = """'{'admin_password':'***'}'"""
184        self.assertEqual(expected, self.field.stringify(payload))
185
186
187class TestVersionPredicate(TestString):
188    def setUp(self):
189        super(TestVersionPredicate, self).setUp()
190        self.field = fields.VersionPredicateField()
191        self.coerce_good_values = [('>=1.0', '>=1.0'),
192                                   ('==1.1', '==1.1'),
193                                   ('<1.1.0', '<1.1.0')]
194        self.coerce_bad_values = ['1', 'foo', '>1', 1.0, '1.0', '=1.0']
195        self.to_primitive_values = self.coerce_good_values[0:1]
196        self.from_primitive_values = self.coerce_good_values[0:1]
197
198
199class TestMACAddress(TestField):
200    def setUp(self):
201        super(TestMACAddress, self).setUp()
202        self.field = fields.MACAddressField()
203        self.coerce_good_values = [
204            ('c6:df:11:a5:c8:5d', 'c6:df:11:a5:c8:5d'),
205            ('C6:DF:11:A5:C8:5D', 'c6:df:11:a5:c8:5d'),
206            ('c6:df:11:a5:c8:5d', 'c6:df:11:a5:c8:5d'),
207            ('C6:DF:11:A5:C8:5D', 'c6:df:11:a5:c8:5d'),
208        ]
209        self.coerce_bad_values = [
210            'C6:DF:11:A5:C8',  # Too short
211            'C6:DF:11:A5:C8:5D:D7',  # Too long
212            'C6:DF:11:A5:C8:KD',  # Bad octal
213            1123123,  # Number
214            {},  # dict
215        ]
216        self.to_primitive_values = self.coerce_good_values[0:1]
217        self.from_primitive_values = self.coerce_good_values[0:1]
218
219    def test_get_schema(self):
220        schema = self.field.get_schema()
221        self.assertEqual(['string'], schema['type'])
222        self.assertEqual(False, schema['readonly'])
223        pattern = schema['pattern']
224        for _, valid_val in self.coerce_good_values:
225            self.assertRegex(valid_val, pattern)
226        invalid_vals = [x for x in self.coerce_bad_values if type(x) == 'str']
227        for invalid_val in invalid_vals:
228            self.assertNotRegex(invalid_val, pattern)
229
230
231class TestPCIAddress(TestField):
232    def setUp(self):
233        super(TestPCIAddress, self).setUp()
234        self.field = fields.PCIAddressField()
235        self.coerce_good_values = [
236            ('0000:02:00.0', '0000:02:00.0'),
237            ('FFFF:FF:1F.7', 'ffff:ff:1f.7'),
238            ('fFfF:fF:1F.7', 'ffff:ff:1f.7'),
239        ]
240        self.coerce_bad_values = [
241            '000:02:00.0',  # Too short
242            '00000:02:00.0',  # Too long
243            'FFFF:FF:2F.7',  # Bad slot
244            'FFFF:GF:1F.7',  # Bad octal
245            1123123,  # Number
246            {},  # dict
247        ]
248        self.to_primitive_values = self.coerce_good_values[0:1]
249        self.from_primitive_values = self.coerce_good_values[0:1]
250
251    def test_get_schema(self):
252        schema = self.field.get_schema()
253        self.assertEqual(['string'], schema['type'])
254        self.assertEqual(False, schema['readonly'])
255        pattern = schema['pattern']
256        for _, valid_val in self.coerce_good_values:
257            self.assertRegex(valid_val, pattern)
258        invalid_vals = [x for x in self.coerce_bad_values if type(x) == 'str']
259        for invalid_val in invalid_vals:
260            self.assertNotRegex(invalid_val, pattern)
261
262
263class TestUUID(TestField):
264    def setUp(self):
265        super(TestUUID, self).setUp()
266        self.field = fields.UUIDField()
267        self.coerce_good_values = [
268            ('da66a411-af0e-4829-9b67-475017ddd152',
269                'da66a411-af0e-4829-9b67-475017ddd152'),
270            ('da66a411af0e48299b67475017ddd152',
271                'da66a411af0e48299b67475017ddd152'),
272            ('DA66A411-AF0E-4829-9B67-475017DDD152',
273                'DA66A411-AF0E-4829-9B67-475017DDD152'),
274            ('DA66A411AF0E48299b67475017DDD152',
275                'DA66A411AF0E48299b67475017DDD152'),
276            # These values are included to ensure there is not change in
277            # behaviour - only when we can remove the old UUID behaviour can
278            #  we add these to the "self.coerce_bad_values" list
279            ('da66a411-af0e-4829-9b67',
280                'da66a411-af0e-4829-9b67'),
281            ('da66a411-af0e-4829-9b67-475017ddd152548999',
282                'da66a411-af0e-4829-9b67-475017ddd152548999'),
283            ('da66a411-af0e-4829-9b67-475017ddz152',
284                'da66a411-af0e-4829-9b67-475017ddz152'),
285            ('fake_uuid', 'fake_uuid'),
286            (u'fake_uāid', u'fake_uāid'),
287            (b'fake_u\xe1id'.decode('latin_1'),
288                b'fake_u\xe1id'.decode('latin_1')),
289            ('1', '1'),
290            (1, '1')
291        ]
292        self.to_primitive_values = self.coerce_good_values[0:1]
293        self.from_primitive_values = self.coerce_good_values[0:1]
294
295    @mock.patch('warnings.warn')
296    def test_coerce_good_values(self, mock_warn):
297        super().test_coerce_good_values()
298        mock_warn.assert_has_calls(
299            [mock.call(mock.ANY, FutureWarning)] * 8,
300        )
301
302    def test_validation_warning_can_be_escalated_to_exception(self):
303        warnings.filterwarnings(action='error')
304        self.assertRaises(FutureWarning, self.field.coerce, 'obj', 'attr',
305                          'not a uuid')
306
307    def test_get_schema(self):
308        field = fields.UUIDField()
309        schema = field.get_schema()
310        self.assertEqual(['string'], schema['type'])
311        self.assertEqual(False, schema['readonly'])
312        pattern = schema['pattern']
313        for _, valid_val in self.coerce_good_values[:4]:
314            self.assertRegex(valid_val, pattern)
315        invalid_vals = [x for x in self.coerce_bad_values if type(x) == 'str']
316        for invalid_val in invalid_vals:
317            self.assertNotRegex(invalid_val, pattern)
318
319
320class TestBaseEnum(TestField):
321    def setUp(self):
322        super(TestBaseEnum, self).setUp()
323        self.field = FakeEnumField()
324        self.coerce_good_values = [('frog', 'frog'),
325                                   ('platypus', 'platypus'),
326                                   ('alligator', 'alligator')]
327        self.coerce_bad_values = ['aardvark', 'wookie']
328        self.to_primitive_values = self.coerce_good_values[0:1]
329        self.from_primitive_values = self.coerce_good_values[0:1]
330
331    def test_stringify(self):
332        self.assertEqual("'platypus'", self.field.stringify('platypus'))
333
334    def test_stringify_invalid(self):
335        self.assertRaises(ValueError, self.field.stringify, 'aardvark')
336
337    def test_fingerprint(self):
338        # Notes(yjiang5): make sure changing valid_value will be detected
339        # in test_objects.test_versions
340        field1 = FakeEnumField()
341        field2 = FakeEnumAltField()
342        self.assertNotEqual(str(field1), str(field2))
343
344    def test_valid_values(self):
345        self.assertEqual(self.field.valid_values,
346                         FakeEnum.ALL)
347
348    def test_valid_values_keeps_type(self):
349        self.assertIsInstance(self.field.valid_values, tuple)
350        self.assertIsInstance(FakeEnumAltField().valid_values, set)
351
352
353class TestEnum(TestField):
354    def setUp(self):
355        super(TestEnum, self).setUp()
356        self.field = fields.EnumField(
357            valid_values=['foo', 'bar', 1, True])
358        self.coerce_good_values = [('foo', 'foo'), (1, '1'), (True, 'True')]
359        self.coerce_bad_values = ['boo', 2, False]
360        self.to_primitive_values = self.coerce_good_values[0:1]
361        self.from_primitive_values = self.coerce_good_values[0:1]
362
363    def test_stringify(self):
364        self.assertEqual("'foo'", self.field.stringify('foo'))
365
366    def test_stringify_invalid(self):
367        self.assertRaises(ValueError, self.field.stringify, '123')
368
369    def test_fieldtype_get_schema(self):
370        self.assertEqual({'type': ['string'], 'enum': ["foo", "bar", 1, True]},
371                         self.field._type.get_schema())
372
373    def test_get_schema(self):
374        self.assertEqual({'type': ['string'], 'enum': ["foo", "bar", 1, True],
375                          'readonly': False}, self.field.get_schema())
376
377    def test_fingerprint(self):
378        # Notes(yjiang5): make sure changing valid_value will be detected
379        # in test_objects.test_versions
380        field1 = fields.EnumField(valid_values=['foo', 'bar'])
381        field2 = fields.EnumField(valid_values=['foo', 'bar1'])
382        self.assertNotEqual(str(field1), str(field2))
383
384    def test_missing_valid_values(self):
385        self.assertRaises(exception.EnumRequiresValidValuesError,
386                          fields.EnumField, None)
387
388    def test_empty_valid_values(self):
389        self.assertRaises(exception.EnumRequiresValidValuesError,
390                          fields.EnumField, [])
391
392    def test_non_iterable_valid_values(self):
393        self.assertRaises(exception.EnumValidValuesInvalidError,
394                          fields.EnumField, True)
395
396    def test_enum_subclass_check(self):
397        def _test():
398            class BrokenEnumField(fields.BaseEnumField):
399                AUTO_TYPE = int
400
401            BrokenEnumField()
402
403        self.assertRaises(exception.EnumFieldInvalid, _test)
404
405
406class TestStateMachine(TestField):
407
408    def test_good_transitions(self):
409        @obj_base.VersionedObjectRegistry.register
410        class AnObject(obj_base.VersionedObject):
411            fields = {
412                'status': FakeStateMachineField(),
413            }
414
415        obj = AnObject()
416
417        obj.status = FakeStateMachineField.ACTIVE
418        obj.status = FakeStateMachineField.PENDING
419        obj.status = FakeStateMachineField.ERROR
420        obj.status = FakeStateMachineField.PENDING
421        obj.status = FakeStateMachineField.ACTIVE
422
423    def test_bad_transitions(self):
424        @obj_base.VersionedObjectRegistry.register
425        class AnObject(obj_base.VersionedObject):
426            fields = {
427                'status': FakeStateMachineField(),
428            }
429
430        obj = AnObject(status='ERROR')
431
432        try:
433            obj.status = FakeStateMachineField.ACTIVE
434        except ValueError as e:
435            ex = e
436        else:
437            ex = None
438
439        self.assertIsNotNone(ex, 'Invalid transition failed to raise error')
440        self.assertEqual('AnObject.status is not allowed to transition out '
441                         'of \'ERROR\' state to \'ACTIVE\' state, choose from '
442                         '[\'PENDING\']',
443                         str(ex))
444
445    def test_bad_initial_value(self):
446        @obj_base.VersionedObjectRegistry.register
447        class AnObject(obj_base.VersionedObject):
448            fields = {
449                'status': FakeStateMachineField(),
450            }
451
452        obj = AnObject()
453
454        with testtools.ExpectedException(ValueError):
455            obj.status = "FOO"
456
457    def test_bad_updated_value(self):
458        @obj_base.VersionedObjectRegistry.register
459        class AnObject(obj_base.VersionedObject):
460            fields = {
461                'status': FakeStateMachineField(),
462            }
463
464        obj = AnObject()
465
466        with testtools.ExpectedException(ValueError):
467            obj.status = FakeStateMachineField.ACTIVE
468            obj.status = "FOO"
469
470
471class TestInteger(TestField):
472    def setUp(self):
473        super(TestField, self).setUp()
474        self.field = fields.IntegerField()
475        self.coerce_good_values = [(1, 1), ('1', 1)]
476        self.coerce_bad_values = ['foo', None]
477        self.to_primitive_values = self.coerce_good_values[0:1]
478        self.from_primitive_values = self.coerce_good_values[0:1]
479
480    def test_fieldtype_get_schema(self):
481        self.assertEqual({'type': ['integer']}, self.field._type.get_schema())
482
483    def test_get_schema(self):
484        self.assertEqual({'type': ['integer'], 'readonly': False},
485                         self.field.get_schema())
486
487
488class TestNonNegativeInteger(TestField):
489    def setUp(self):
490        super(TestNonNegativeInteger, self).setUp()
491        self.field = fields.NonNegativeIntegerField()
492        self.coerce_good_values = [(1, 1), ('1', 1)]
493        self.coerce_bad_values = ['-2', '4.2', 'foo', None]
494        self.to_primitive_values = self.coerce_good_values[0:1]
495        self.from_primitive_values = self.coerce_good_values[0:1]
496
497    def test_get_schema(self):
498        self.assertEqual({'type': ['integer'], 'readonly': False,
499                          'minimum': 0}, self.field.get_schema())
500
501
502class TestFloat(TestField):
503    def setUp(self):
504        super(TestFloat, self).setUp()
505        self.field = fields.FloatField()
506        self.coerce_good_values = [(1.1, 1.1), ('1.1', 1.1)]
507        self.coerce_bad_values = ['foo', None]
508        self.to_primitive_values = self.coerce_good_values[0:1]
509        self.from_primitive_values = self.coerce_good_values[0:1]
510
511    def test_fieldtype_get_schema(self):
512        self.assertEqual({'type': ['number']}, self.field._type.get_schema())
513
514    def test_get_schema(self):
515        self.assertEqual({'type': ['number'], 'readonly': False},
516                         self.field.get_schema())
517
518
519class TestNonNegativeFloat(TestField):
520    def setUp(self):
521        super(TestNonNegativeFloat, self).setUp()
522        self.field = fields.NonNegativeFloatField()
523        self.coerce_good_values = [(1.1, 1.1), ('1.1', 1.1)]
524        self.coerce_bad_values = ['-4.2', 'foo', None]
525        self.to_primitive_values = self.coerce_good_values[0:1]
526        self.from_primitive_values = self.coerce_good_values[0:1]
527
528    def test_get_schema(self):
529        self.assertEqual({'type': ['number'], 'readonly': False,
530                          'minimum': 0}, self.field.get_schema())
531
532
533class TestBoolean(TestField):
534    def setUp(self):
535        super(TestField, self).setUp()
536        self.field = fields.BooleanField()
537        self.coerce_good_values = [(True, True), (False, False), (1, True),
538                                   ('foo', True), (0, False), ('', False)]
539        self.coerce_bad_values = []
540        self.to_primitive_values = self.coerce_good_values[0:2]
541        self.from_primitive_values = self.coerce_good_values[0:2]
542
543    def test_fieldtype_get_schema(self):
544        self.assertEqual({'type': ['boolean']}, self.field._type.get_schema())
545
546    def test_get_schema(self):
547        self.assertEqual({'type': ['boolean'], 'readonly': False},
548                         self.field.get_schema())
549
550
551class TestFlexibleBoolean(TestField):
552    def setUp(self):
553        super(TestFlexibleBoolean, self).setUp()
554        self.field = fields.FlexibleBooleanField()
555        self.coerce_good_values = [(True, True), (False, False),
556                                   ("true", True), ("false", False),
557                                   ("t", True), ("f", False),
558                                   ("yes", True), ("no", False),
559                                   ("y", True), ("n", False),
560                                   ("on", True), ("off", False),
561                                   (1, True), (0, False),
562                                   ('frog', False), ('', False)]
563        self.coerce_bad_values = []
564        self.to_primitive_values = self.coerce_good_values[0:2]
565        self.from_primitive_values = self.coerce_good_values[0:2]
566
567
568class TestDateTime(TestField):
569    def setUp(self):
570        super(TestDateTime, self).setUp()
571        self.dt = datetime.datetime(1955, 11, 5, tzinfo=iso8601.iso8601.UTC)
572        self.field = fields.DateTimeField()
573        self.coerce_good_values = [(self.dt, self.dt),
574                                   (_utils.isotime(self.dt), self.dt)]
575        self.coerce_bad_values = [1, 'foo']
576        self.to_primitive_values = [(self.dt, _utils.isotime(self.dt))]
577        self.from_primitive_values = [(_utils.isotime(self.dt), self.dt)]
578
579    def test_stringify(self):
580        self.assertEqual(
581            '1955-11-05T18:00:00Z',
582            self.field.stringify(
583                datetime.datetime(1955, 11, 5, 18, 0, 0,
584                                  tzinfo=iso8601.iso8601.UTC)))
585
586    def test_get_schema(self):
587        self.assertEqual({'type': ['string'], 'format': 'date-time',
588                          'readonly': False},
589                         self.field.get_schema())
590
591
592class TestDateTimeNoTzinfo(TestField):
593    def setUp(self):
594        super(TestDateTimeNoTzinfo, self).setUp()
595        self.dt = datetime.datetime(1955, 11, 5)
596        self.field = fields.DateTimeField(tzinfo_aware=False)
597        self.coerce_good_values = [(self.dt, self.dt),
598                                   (_utils.isotime(self.dt), self.dt)]
599        self.coerce_bad_values = [1, 'foo']
600        self.to_primitive_values = [(self.dt, _utils.isotime(self.dt))]
601        self.from_primitive_values = [
602            (
603                _utils.isotime(self.dt),
604                self.dt,
605            )
606        ]
607
608    def test_stringify(self):
609        self.assertEqual(
610            '1955-11-05T18:00:00Z',
611            self.field.stringify(
612                datetime.datetime(1955, 11, 5, 18, 0, 0)))
613
614
615class TestDict(TestField):
616    def setUp(self):
617        super(TestDict, self).setUp()
618        self.field = fields.Field(fields.Dict(FakeFieldType()))
619        self.coerce_good_values = [({'foo': 'bar'}, {'foo': '*bar*'}),
620                                   ({'foo': 1}, {'foo': '*1*'})]
621        self.coerce_bad_values = [{1: 'bar'}, 'foo']
622        self.to_primitive_values = [({'foo': 'bar'}, {'foo': '!bar!'})]
623        self.from_primitive_values = [({'foo': '!bar!'}, {'foo': 'bar'})]
624
625    def test_stringify(self):
626        self.assertEqual("{key=val}", self.field.stringify({'key': 'val'}))
627
628    def test_get_schema(self):
629        self.assertEqual({'type': ['object'],
630                          'additionalProperties': {'readonly': False,
631                                                   'type': ['foo']},
632                          'readonly': False},
633                         self.field.get_schema())
634
635
636class TestDictOfStrings(TestField):
637    def setUp(self):
638        super(TestDictOfStrings, self).setUp()
639        self.field = fields.DictOfStringsField()
640        self.coerce_good_values = [({'foo': 'bar'}, {'foo': 'bar'}),
641                                   ({'foo': 1}, {'foo': '1'})]
642        self.coerce_bad_values = [{1: 'bar'}, {'foo': None}, 'foo']
643        self.to_primitive_values = [({'foo': 'bar'}, {'foo': 'bar'})]
644        self.from_primitive_values = [({'foo': 'bar'}, {'foo': 'bar'})]
645
646    def test_stringify(self):
647        self.assertEqual("{key='val'}", self.field.stringify({'key': 'val'}))
648
649
650class TestDictOfIntegers(TestField):
651    def setUp(self):
652        super(TestDictOfIntegers, self).setUp()
653        self.field = fields.DictOfIntegersField()
654        self.coerce_good_values = [({'foo': '42'}, {'foo': 42}),
655                                   ({'foo': 4.2}, {'foo': 4})]
656        self.coerce_bad_values = [{1: 'bar'}, {'foo': 'boo'},
657                                  'foo', {'foo': None}]
658        self.to_primitive_values = [({'foo': 42}, {'foo': 42})]
659        self.from_primitive_values = [({'foo': 42}, {'foo': 42})]
660
661    def test_stringify(self):
662        self.assertEqual("{key=42}", self.field.stringify({'key': 42}))
663
664
665class TestDictOfStringsNone(TestField):
666    def setUp(self):
667        super(TestDictOfStringsNone, self).setUp()
668        self.field = fields.DictOfNullableStringsField()
669        self.coerce_good_values = [({'foo': 'bar'}, {'foo': 'bar'}),
670                                   ({'foo': 1}, {'foo': '1'}),
671                                   ({'foo': None}, {'foo': None})]
672        self.coerce_bad_values = [{1: 'bar'}, 'foo']
673        self.to_primitive_values = [({'foo': 'bar'}, {'foo': 'bar'})]
674        self.from_primitive_values = [({'foo': 'bar'}, {'foo': 'bar'})]
675
676    def test_stringify(self):
677        self.assertEqual("{k2=None,key='val'}",
678                         self.field.stringify({'k2': None,
679                                               'key': 'val'}))
680
681
682class TestListOfDictOfNullableStringsField(TestField):
683    def setUp(self):
684        super(TestListOfDictOfNullableStringsField, self).setUp()
685        self.field = fields.ListOfDictOfNullableStringsField()
686        self.coerce_good_values = [([{'f': 'b', 'f1': 'b1'}, {'f2': 'b2'}],
687                                    [{'f': 'b', 'f1': 'b1'}, {'f2': 'b2'}]),
688                                   ([{'f': 1}, {'f1': 'b1'}],
689                                    [{'f': '1'}, {'f1': 'b1'}]),
690                                   ([{'foo': None}], [{'foo': None}])]
691        self.coerce_bad_values = [[{1: 'a'}], ['ham', 1], ['eggs']]
692        self.to_primitive_values = [([{'f': 'b'}, {'f1': 'b1'}, {'f2': None}],
693                                     [{'f': 'b'}, {'f1': 'b1'}, {'f2': None}])]
694        self.from_primitive_values = [([{'f': 'b'}, {'f1': 'b1'},
695                                        {'f2': None}],
696                                       [{'f': 'b'}, {'f1': 'b1'},
697                                        {'f2': None}])]
698
699    def test_stringify(self):
700        self.assertEqual("[{f=None,f1='b1'},{f2='b2'}]",
701                         self.field.stringify(
702                             [{'f': None, 'f1': 'b1'}, {'f2': 'b2'}]))
703
704
705class TestList(TestField):
706    def setUp(self):
707        super(TestList, self).setUp()
708        self.field = fields.Field(fields.List(FakeFieldType()))
709        self.coerce_good_values = [(['foo', 'bar'], ['*foo*', '*bar*'])]
710        self.coerce_bad_values = ['foo']
711        self.to_primitive_values = [(['foo'], ['!foo!'])]
712        self.from_primitive_values = [(['!foo!'], ['foo'])]
713
714    def test_stringify(self):
715        self.assertEqual('[123]', self.field.stringify([123]))
716
717    def test_fieldtype_get_schema(self):
718        self.assertEqual({'type': ['array'],
719                          'items': {'type': ['foo'], 'readonly': False}},
720                         self.field._type.get_schema())
721
722    def test_get_schema(self):
723        self.assertEqual({'type': ['array'],
724                          'items': {'type': ['foo'], 'readonly': False},
725                          'readonly': False},
726                         self.field.get_schema())
727
728
729class TestListOfStrings(TestField):
730    def setUp(self):
731        super(TestListOfStrings, self).setUp()
732        self.field = fields.ListOfStringsField()
733        self.coerce_good_values = [(['foo', 'bar'], ['foo', 'bar'])]
734        self.coerce_bad_values = ['foo']
735        self.to_primitive_values = [(['foo'], ['foo'])]
736        self.from_primitive_values = [(['foo'], ['foo'])]
737
738    def test_stringify(self):
739        self.assertEqual("['abc']", self.field.stringify(['abc']))
740
741
742class TestDictOfListOfStrings(TestField):
743    def setUp(self):
744        super(TestDictOfListOfStrings, self).setUp()
745        self.field = fields.DictOfListOfStringsField()
746        self.coerce_good_values = [({'foo': ['1', '2']}, {'foo': ['1', '2']}),
747                                   ({'foo': [1]}, {'foo': ['1']})]
748        self.coerce_bad_values = [{'foo': [None, None]}, 'foo']
749        self.to_primitive_values = [({'foo': ['1', '2']}, {'foo': ['1', '2']})]
750        self.from_primitive_values = [({'foo': ['1', '2']},
751                                       {'foo': ['1', '2']})]
752
753    def test_stringify(self):
754        self.assertEqual("{foo=['1','2']}",
755                         self.field.stringify({'foo': ['1', '2']}))
756
757
758class TestListOfEnum(TestField):
759    def setUp(self):
760        super(TestListOfEnum, self).setUp()
761        self.field = fields.ListOfEnumField(valid_values=['foo', 'bar'])
762        self.coerce_good_values = [(['foo', 'bar'], ['foo', 'bar'])]
763        self.coerce_bad_values = ['foo', ['foo', 'bar1']]
764        self.to_primitive_values = [(['foo'], ['foo'])]
765        self.from_primitive_values = [(['foo'], ['foo'])]
766
767    def test_stringify(self):
768        self.assertEqual("['foo']", self.field.stringify(['foo']))
769
770    def test_stringify_invalid(self):
771        self.assertRaises(ValueError, self.field.stringify, '[abc]')
772
773    def test_fingerprint(self):
774        # Notes(yjiang5): make sure changing valid_value will be detected
775        # in test_objects.test_versions
776        field1 = fields.ListOfEnumField(valid_values=['foo', 'bar'])
777        field2 = fields.ListOfEnumField(valid_values=['foo', 'bar1'])
778        self.assertNotEqual(str(field1), str(field2))
779
780
781class TestSet(TestField):
782    def setUp(self):
783        super(TestSet, self).setUp()
784        self.field = fields.Field(fields.Set(FakeFieldType()))
785        self.coerce_good_values = [(set(['foo', 'bar']),
786                                    set(['*foo*', '*bar*']))]
787        self.coerce_bad_values = [['foo'], {'foo': 'bar'}]
788        self.to_primitive_values = [(set(['foo']), tuple(['!foo!']))]
789        self.from_primitive_values = [(tuple(['!foo!']), set(['foo']))]
790
791    def test_stringify(self):
792        self.assertEqual('set([123])', self.field.stringify(set([123])))
793
794    def test_get_schema(self):
795        self.assertEqual({'type': ['array'], 'uniqueItems': True,
796                          'items': {'type': ['foo'], 'readonly': False},
797                          'readonly': False},
798                         self.field.get_schema())
799
800
801class TestSetOfIntegers(TestField):
802    def setUp(self):
803        super(TestSetOfIntegers, self).setUp()
804        self.field = fields.SetOfIntegersField()
805        self.coerce_good_values = [(set(['1', 2]),
806                                    set([1, 2]))]
807        self.coerce_bad_values = [set(['foo'])]
808        self.to_primitive_values = [(set([1]), tuple([1]))]
809        self.from_primitive_values = [(tuple([1]), set([1]))]
810
811    def test_stringify(self):
812        self.assertEqual('set([1,2])', self.field.stringify(set([1, 2])))
813
814    def test_repr(self):
815        self.assertEqual("Set(default=<class 'oslo_versionedobjects.fields."
816                         "UnspecifiedDefault'>,nullable=False)",
817                         repr(self.field))
818        self.assertEqual("Set(default=set([]),nullable=False)",
819                         repr(fields.SetOfIntegersField(default=set())))
820        self.assertEqual("Set(default=set([1,a]),nullable=False)",
821                         repr(fields.SetOfIntegersField(default={1, 'a'})))
822
823
824class TestListOfSetsOfIntegers(TestField):
825    def setUp(self):
826        super(TestListOfSetsOfIntegers, self).setUp()
827        self.field = fields.ListOfSetsOfIntegersField()
828        self.coerce_good_values = [([set(['1', 2]), set([3, '4'])],
829                                    [set([1, 2]), set([3, 4])])]
830        self.coerce_bad_values = [[set(['foo'])]]
831        self.to_primitive_values = [([set([1])], [tuple([1])])]
832        self.from_primitive_values = [([tuple([1])], [set([1])])]
833
834    def test_stringify(self):
835        self.assertEqual('[set([1,2])]', self.field.stringify([set([1, 2])]))
836
837
838class TestListOfIntegers(TestField):
839    def setUp(self):
840        super(TestListOfIntegers, self).setUp()
841        self.field = fields.ListOfIntegersField()
842        self.coerce_good_values = [(['1', 2], [1, 2]),
843                                   ([1, 2], [1, 2])]
844        self.coerce_bad_values = [['foo']]
845        self.to_primitive_values = [([1], [1])]
846        self.from_primitive_values = [([1], [1])]
847
848    def test_stringify(self):
849        self.assertEqual('[[1, 2]]', self.field.stringify([[1, 2]]))
850
851
852class TestListOfUUIDField(TestField):
853    def setUp(self):
854        super(TestListOfUUIDField, self).setUp()
855        self.field = fields.ListOfUUIDField()
856        self.uuid1 = '6b2097ea-d0e3-44dd-b131-95472b3ea8fd'
857        self.uuid2 = '478c193d-2533-4e71-ab2b-c7683f67d7f9'
858        self.coerce_good_values = [([self.uuid1, self.uuid2],
859                                    [self.uuid1, self.uuid2])]
860        # coerce_bad_values is intentionally ignored since the UUID field
861        # allows non-UUID values for now. See TestUUIDField for examples.
862        self.to_primitive_values = [([self.uuid1], [self.uuid1])]
863        self.from_primitive_values = [([self.uuid1], [self.uuid1])]
864
865    def test_stringify(self):
866        self.assertEqual('[%s,%s]' % (self.uuid1, self.uuid2),
867                         self.field.stringify([self.uuid1, self.uuid2]))
868
869
870class TestLocalMethods(test.TestCase):
871    @mock.patch.object(obj_base.LOG, 'exception')
872    def test__make_class_properties_setter_value_error(self, mock_log):
873        @obj_base.VersionedObjectRegistry.register
874        class AnObject(obj_base.VersionedObject):
875            fields = {
876                'intfield': fields.IntegerField(),
877            }
878
879        self.assertRaises(ValueError, AnObject, intfield='badvalue')
880        self.assertFalse(mock_log.called)
881
882    @mock.patch.object(obj_base.LOG, 'exception')
883    def test__make_class_properties_setter_setattr_fails(self, mock_log):
884        @obj_base.VersionedObjectRegistry.register
885        class AnObject(obj_base.VersionedObject):
886            fields = {
887                'intfield': fields.IntegerField(),
888            }
889
890        # We want the setattr() call in _make_class_properties.setter() to
891        # raise an exception
892        with mock.patch.object(obj_base, '_get_attrname') as mock_attr:
893            mock_attr.return_value = '__class__'
894            self.assertRaises(TypeError, AnObject, intfield=2)
895            mock_attr.assert_called_once_with('intfield')
896            mock_log.assert_called_once_with(mock.ANY,
897                                             {'attr': 'AnObject.intfield'})
898
899
900class TestObject(TestField):
901    def setUp(self):
902        super(TestObject, self).setUp()
903
904        @obj_base.VersionedObjectRegistry.register
905        class TestableObject(obj_base.VersionedObject):
906            fields = {
907                'uuid': fields.StringField(),
908                }
909
910            def __eq__(self, value):
911                # NOTE(danms): Be rather lax about this equality thing to
912                # satisfy the assertEqual() in test_from_primitive(). We
913                # just want to make sure the right type of object is re-created
914                return value.__class__.__name__ == TestableObject.__name__
915
916        class OtherTestableObject(obj_base.VersionedObject):
917            pass
918
919        test_inst = TestableObject()
920        self._test_cls = TestableObject
921        self.field = fields.Field(fields.Object('TestableObject'))
922        self.coerce_good_values = [(test_inst, test_inst)]
923        self.coerce_bad_values = [OtherTestableObject(), 1, 'foo']
924        self.to_primitive_values = [(test_inst, test_inst.obj_to_primitive())]
925        self.from_primitive_values = [(test_inst.obj_to_primitive(),
926                                       test_inst),
927                                      (test_inst, test_inst)]
928
929    def test_stringify(self):
930        obj = self._test_cls(uuid='fake-uuid')
931        self.assertEqual('TestableObject(fake-uuid)',
932                         self.field.stringify(obj))
933
934    def test_from_primitive(self):
935        @obj_base.VersionedObjectRegistry.register
936        class TestFakeObject(obj_base.VersionedObject):
937            OBJ_PROJECT_NAMESPACE = 'fake-project'
938
939        @obj_base.VersionedObjectRegistry.register
940        class TestBar(TestFakeObject, obj_base.ComparableVersionedObject):
941            fields = {
942                'name': fields.StringField(),
943            }
944
945        @obj_base.VersionedObjectRegistry.register
946        class TestFoo(TestFakeObject, obj_base.ComparableVersionedObject):
947            fields = {
948                'name': fields.StringField(),
949                'bar': fields.ObjectField('TestBar', nullable=True)
950            }
951
952        bar = TestBar(name='bar')
953        foo = TestFoo(name='foo', bar=bar)
954        from_primitive_values = [(foo.obj_to_primitive(), foo), (foo, foo)]
955
956        for prim_val, out_val in from_primitive_values:
957            self.assertEqual(out_val, self.field.from_primitive(
958                foo, 'attr', prim_val))
959
960    def test_inheritance(self):
961        # We need a whole lot of classes in a hierarchy to
962        # test subclass recognition for the Object field
963        class TestAnimal(obj_base.VersionedObject):
964            pass
965
966        class TestMammal(TestAnimal):
967            pass
968
969        class TestReptile(TestAnimal):
970            pass
971
972        # We'll use this to create a diamond in the
973        # class hierarchy
974        class TestPet(TestAnimal):
975            pass
976
977        # Non-versioned object mixin
978        class TestScary(object):
979            pass
980
981        class TestCrocodile(TestReptile, TestPet, TestScary):
982            pass
983
984        class TestPig(TestMammal):
985            pass
986
987        class TestDog(TestMammal, TestPet):
988            pass
989
990        # Some fictional animals
991        wolfy = TestDog()  # Terminator-2
992        ticktock = TestCrocodile()  # Peter Pan
993        babe = TestPig()  # Babe
994
995        # The various classes
996        animals = fields.Object('TestAnimal', subclasses=True)
997        mammals = fields.Object('TestMammal', subclasses=True)
998        reptiles = fields.Object('TestReptile', subclasses=True)
999        pets = fields.Object('TestPet', subclasses=True)
1000        pigs = fields.Object('TestPig', subclasses=True)
1001        dogs = fields.Object('TestDog', subclasses=True)
1002        crocs = fields.Object('TestCrocodile', subclasses=True)
1003
1004        self.assertEqual(["TestDog", "TestMammal", "TestPet",
1005                          "TestAnimal", "VersionedObject"],
1006                         fields.Object._get_all_obj_names(wolfy))
1007
1008        self.assertEqual(["TestCrocodile", "TestReptile", "TestPet",
1009                          "TestAnimal", "VersionedObject"],
1010                         fields.Object._get_all_obj_names(ticktock))
1011
1012        self.assertEqual(["TestPig", "TestMammal",
1013                          "TestAnimal", "VersionedObject"],
1014                         fields.Object._get_all_obj_names(babe))
1015
1016        # When stringifying we should see the subclass object name
1017        # not the base class object name
1018        self.assertEqual("TestDog", animals.stringify(wolfy))
1019        self.assertEqual("TestCrocodile", animals.stringify(ticktock))
1020        self.assertEqual("TestPig", animals.stringify(babe))
1021
1022        # Everything is an animal
1023        self.assertEqual(wolfy, animals.coerce(None, "animal", wolfy))
1024        self.assertEqual(ticktock, animals.coerce(None, "animal", ticktock))
1025        self.assertEqual(babe, animals.coerce(None, "animal", babe))
1026
1027        # crocodiles are not mammals
1028        self.assertEqual(wolfy, mammals.coerce(None, "animal", wolfy))
1029        self.assertRaises(ValueError, mammals.coerce, None, "animal", ticktock)
1030        self.assertEqual(babe, mammals.coerce(None, "animal", babe))
1031
1032        # dogs and pigs are not reptiles
1033        self.assertRaises(ValueError, reptiles.coerce, None, "animal", wolfy)
1034        self.assertEqual(ticktock, reptiles.coerce(None, "animal", ticktock))
1035        self.assertRaises(ValueError, reptiles.coerce, None, "animal", babe)
1036
1037        # pigs are not pets, but crocodiles (!) & dogs are
1038        self.assertEqual(wolfy, pets.coerce(None, "animal", wolfy))
1039        self.assertEqual(ticktock, pets.coerce(None, "animal", ticktock))
1040        self.assertRaises(ValueError, pets.coerce, None, "animal", babe)
1041
1042        # Only dogs are dogs
1043        self.assertEqual(wolfy, dogs.coerce(None, "animal", wolfy))
1044        self.assertRaises(ValueError, dogs.coerce, None, "animal", ticktock)
1045        self.assertRaises(ValueError, dogs.coerce, None, "animal", babe)
1046
1047        # Only crocs are crocs
1048        self.assertRaises(ValueError, crocs.coerce, None, "animal", wolfy)
1049        self.assertEqual(ticktock, crocs.coerce(None, "animal", ticktock))
1050        self.assertRaises(ValueError, crocs.coerce, None, "animal", babe)
1051
1052        # Only pigs are pigs
1053        self.assertRaises(ValueError, pigs.coerce, None, "animal", ticktock)
1054        self.assertRaises(ValueError, pigs.coerce, None, "animal", wolfy)
1055        self.assertEqual(babe, pigs.coerce(None, "animal", babe))
1056
1057    def test_coerce_bad_value_primitive_type(self):
1058        # Tests that the ValueError has the primitive type in it's message.
1059        ex = self.assertRaises(ValueError, self.field.coerce,
1060                               'obj', 'attr', [{}])
1061        self.assertEqual('An object of type TestableObject is required '
1062                         'in field attr, not a list', str(ex))
1063
1064    def test_get_schema(self):
1065        self.assertEqual(
1066            {
1067                'properties': {
1068                    'versioned_object.changes':
1069                        {'items': {'type': 'string'}, 'type': 'array'},
1070                    'versioned_object.data': {
1071                        'description': 'fields of TestableObject',
1072                        'properties':
1073                            {'uuid': {'readonly': False, 'type': ['string']}},
1074                        'required': ['uuid'],
1075                        'type': 'object'},
1076                    'versioned_object.name': {'type': 'string'},
1077                    'versioned_object.namespace': {'type': 'string'},
1078                    'versioned_object.version': {'type': 'string'}
1079                },
1080                'readonly': False,
1081                'required': ['versioned_object.namespace',
1082                             'versioned_object.name',
1083                             'versioned_object.version',
1084                             'versioned_object.data'],
1085                'type': ['object']
1086            },
1087            self.field.get_schema())
1088
1089
1090class TestIPAddress(TestField):
1091    def setUp(self):
1092        super(TestIPAddress, self).setUp()
1093        self.field = fields.IPAddressField()
1094        self.coerce_good_values = [('1.2.3.4', netaddr.IPAddress('1.2.3.4')),
1095                                   ('::1', netaddr.IPAddress('::1')),
1096                                   (netaddr.IPAddress('::1'),
1097                                    netaddr.IPAddress('::1'))]
1098        self.coerce_bad_values = ['1-2', 'foo']
1099        self.to_primitive_values = [(netaddr.IPAddress('1.2.3.4'), '1.2.3.4'),
1100                                    (netaddr.IPAddress('::1'), '::1')]
1101        self.from_primitive_values = [('1.2.3.4',
1102                                       netaddr.IPAddress('1.2.3.4')),
1103                                      ('::1',
1104                                       netaddr.IPAddress('::1'))]
1105
1106
1107class TestIPAddressV4(TestField):
1108    def setUp(self):
1109        super(TestIPAddressV4, self).setUp()
1110        self.field = fields.IPV4AddressField()
1111        self.coerce_good_values = [('1.2.3.4', netaddr.IPAddress('1.2.3.4')),
1112                                   (netaddr.IPAddress('1.2.3.4'),
1113                                    netaddr.IPAddress('1.2.3.4'))]
1114        self.coerce_bad_values = ['1-2', 'foo', '::1']
1115        self.to_primitive_values = [(netaddr.IPAddress('1.2.3.4'), '1.2.3.4')]
1116        self.from_primitive_values = [('1.2.3.4',
1117                                       netaddr.IPAddress('1.2.3.4'))]
1118
1119    def test_get_schema(self):
1120        self.assertEqual({'type': ['string'], 'readonly': False,
1121                          'format': 'ipv4'},
1122                         self.field.get_schema())
1123
1124
1125class TestIPAddressV6(TestField):
1126    def setUp(self):
1127        super(TestIPAddressV6, self).setUp()
1128        self.field = fields.IPV6AddressField()
1129        self.coerce_good_values = [('::1', netaddr.IPAddress('::1')),
1130                                   (netaddr.IPAddress('::1'),
1131                                    netaddr.IPAddress('::1'))]
1132        self.coerce_bad_values = ['1.2', 'foo', '1.2.3.4']
1133        self.to_primitive_values = [(netaddr.IPAddress('::1'), '::1')]
1134        self.from_primitive_values = [('::1', netaddr.IPAddress('::1'))]
1135
1136    def test_get_schema(self):
1137        self.assertEqual({'type': ['string'], 'readonly': False,
1138                          'format': 'ipv6'},
1139                         self.field.get_schema())
1140
1141
1142class TestIPV4AndV6Address(TestField):
1143    def setUp(self):
1144        super(TestIPV4AndV6Address, self).setUp()
1145        self.field = fields.IPV4AndV6Address()
1146        self.coerce_good_values = [('::1', netaddr.IPAddress('::1')),
1147                                   (netaddr.IPAddress('::1'),
1148                                    netaddr.IPAddress('::1')),
1149                                   ('1.2.3.4',
1150                                    netaddr.IPAddress('1.2.3.4')),
1151                                   (netaddr.IPAddress('1.2.3.4'),
1152                                    netaddr.IPAddress('1.2.3.4'))]
1153        self.coerce_bad_values = ['1-2', 'foo']
1154        self.to_primitive_values = [(netaddr.IPAddress('::1'),
1155                                     '::1'),
1156                                    (netaddr.IPAddress('1.2.3.4'),
1157                                     '1.2.3.4')]
1158        self.from_primitive_values = [('::1',
1159                                       netaddr.IPAddress('::1')),
1160                                      ('1.2.3.4',
1161                                       netaddr.IPAddress('1.2.3.4'))]
1162
1163    def test_get_schema(self):
1164        self.assertEqual({'oneOf': [{'format': 'ipv4', 'type': ['string']},
1165                                    {'format': 'ipv6', 'type': ['string']}]},
1166                         self.field.get_schema())
1167
1168
1169class TestIPNetwork(TestField):
1170    def setUp(self):
1171        super(TestIPNetwork, self).setUp()
1172        self.field = fields.IPNetworkField()
1173        self.coerce_good_values = [('::1/0', netaddr.IPNetwork('::1/0')),
1174                                   ('1.2.3.4/24',
1175                                    netaddr.IPNetwork('1.2.3.4/24')),
1176                                   (netaddr.IPNetwork('::1/32'),
1177                                    netaddr.IPNetwork('::1/32'))]
1178        self.coerce_bad_values = ['foo']
1179        self.to_primitive_values = [(netaddr.IPNetwork('::1/0'), '::1/0')]
1180        self.from_primitive_values = [('::1/0',
1181                                       netaddr.IPNetwork('::1/0'))]
1182
1183
1184class TestIPV4Network(TestField):
1185    def setUp(self):
1186        super(TestIPV4Network, self).setUp()
1187        self.field = fields.IPV4NetworkField()
1188        self.coerce_good_values = [('1.2.3.4/24',
1189                                    netaddr.IPNetwork('1.2.3.4/24'))]
1190        self.coerce_bad_values = ['foo', '::1/32']
1191        self.to_primitive_values = [(netaddr.IPNetwork('1.2.3.4/24'),
1192                                     '1.2.3.4/24')]
1193        self.from_primitive_values = [('1.2.3.4/24',
1194                                       netaddr.IPNetwork('1.2.3.4/24'))]
1195
1196    def test_get_schema(self):
1197        schema = self.field.get_schema()
1198        self.assertEqual(['string'], schema['type'])
1199        self.assertEqual(False, schema['readonly'])
1200        pattern = schema['pattern']
1201        for _, valid_val in self.coerce_good_values:
1202            self.assertRegex(str(valid_val), pattern)
1203        invalid_vals = [x for x in self.coerce_bad_values]
1204        for invalid_val in invalid_vals:
1205            self.assertNotRegex(str(invalid_val), pattern)
1206
1207
1208class TestIPV6Network(TestField):
1209    def setUp(self):
1210        super(TestIPV6Network, self).setUp()
1211        self.field = fields.IPV6NetworkField()
1212        self.coerce_good_values = [('::1/0', netaddr.IPNetwork('::1/0')),
1213                                   (netaddr.IPNetwork('::1/32'),
1214                                    netaddr.IPNetwork('::1/32'))]
1215        self.coerce_bad_values = ['foo', '1.2.3.4/24']
1216        self.to_primitive_values = [(netaddr.IPNetwork('::1/0'), '::1/0')]
1217        self.from_primitive_values = [('::1/0',
1218                                       netaddr.IPNetwork('::1/0'))]
1219
1220    def test_get_schema(self):
1221        schema = self.field.get_schema()
1222        self.assertEqual(['string'], schema['type'])
1223        self.assertEqual(False, schema['readonly'])
1224        pattern = schema['pattern']
1225        for _, valid_val in self.coerce_good_values:
1226            self.assertRegex(str(valid_val), pattern)
1227        invalid_vals = [x for x in self.coerce_bad_values]
1228        for invalid_val in invalid_vals:
1229            self.assertNotRegex(str(invalid_val), pattern)
1230
1231
1232class FakeCounter(object):
1233    def __init__(self):
1234        self.n = 0
1235
1236    def __iter__(self):
1237        return self
1238
1239    def __next__(self):
1240        if self.n <= 4:
1241            self.n += 1
1242            return self.n
1243        else:
1244            raise StopIteration
1245
1246
1247class TestListTypes(test.TestCase):
1248
1249    def test_regular_list(self):
1250        fields.List(fields.Integer).coerce(None, None, [1, 2])
1251
1252    def test_non_iterable(self):
1253        self.assertRaises(ValueError,
1254                          fields.List(fields.Integer).coerce, None, None, 2)
1255
1256    def test_string_iterable(self):
1257        self.assertRaises(ValueError,
1258                          fields.List(fields.Integer).coerce, None, None,
1259                          'hello')
1260
1261    def test_mapping_iterable(self):
1262        self.assertRaises(ValueError,
1263                          fields.List(fields.Integer).coerce, None, None,
1264                          {'a': 1, 'b': 2})
1265
1266    def test_iter_class(self):
1267        fields.List(fields.Integer).coerce(None, None, FakeCounter())
1268