• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2#
3# Copyright 2010 Google Inc.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17"""Tests for apitools.base.protorpclite.protojson."""
18import datetime
19import json
20import unittest
21
22from apitools.base.protorpclite import message_types
23from apitools.base.protorpclite import messages
24from apitools.base.protorpclite import protojson
25from apitools.base.protorpclite import test_util
26
27
28class CustomField(messages.MessageField):
29    """Custom MessageField class."""
30
31    type = int
32    message_type = message_types.VoidMessage
33
34    def __init__(self, number, **kwargs):
35        super(CustomField, self).__init__(self.message_type, number, **kwargs)
36
37    def value_to_message(self, value):
38        return self.message_type()  # pylint:disable=not-callable
39
40
41class MyMessage(messages.Message):
42    """Test message containing various types."""
43
44    class Color(messages.Enum):
45
46        RED = 1
47        GREEN = 2
48        BLUE = 3
49
50    class Nested(messages.Message):
51
52        nested_value = messages.StringField(1)
53
54    a_string = messages.StringField(2)
55    an_integer = messages.IntegerField(3)
56    a_float = messages.FloatField(4)
57    a_boolean = messages.BooleanField(5)
58    an_enum = messages.EnumField(Color, 6)
59    a_nested = messages.MessageField(Nested, 7)
60    a_repeated = messages.IntegerField(8, repeated=True)
61    a_repeated_float = messages.FloatField(9, repeated=True)
62    a_datetime = message_types.DateTimeField(10)
63    a_repeated_datetime = message_types.DateTimeField(11, repeated=True)
64    a_custom = CustomField(12)
65    a_repeated_custom = CustomField(13, repeated=True)
66
67
68class ModuleInterfaceTest(test_util.ModuleInterfaceTest,
69                          test_util.TestCase):
70
71    MODULE = protojson
72
73
74# TODO(rafek): Convert this test to the compliance test in test_util.
75class ProtojsonTest(test_util.TestCase,
76                    test_util.ProtoConformanceTestBase):
77    """Test JSON encoding and decoding."""
78
79    PROTOLIB = protojson
80
81    def CompareEncoded(self, expected_encoded, actual_encoded):
82        """JSON encoding will be laundered to remove string differences."""
83        self.assertEquals(json.loads(expected_encoded),
84                          json.loads(actual_encoded))
85
86    encoded_empty_message = '{}'
87
88    encoded_partial = """{
89    "double_value": 1.23,
90    "int64_value": -100000000000,
91    "int32_value": 1020,
92    "string_value": "a string",
93    "enum_value": "VAL2"
94    }
95    """
96
97    # pylint:disable=anomalous-unicode-escape-in-string
98    encoded_full = """{
99    "double_value": 1.23,
100    "float_value": -2.5,
101    "int64_value": -100000000000,
102    "uint64_value": 102020202020,
103    "int32_value": 1020,
104    "bool_value": true,
105    "string_value": "a string\u044f",
106    "bytes_value": "YSBieXRlc//+",
107    "enum_value": "VAL2"
108    }
109    """
110
111    encoded_repeated = """{
112    "double_value": [1.23, 2.3],
113    "float_value": [-2.5, 0.5],
114    "int64_value": [-100000000000, 20],
115    "uint64_value": [102020202020, 10],
116    "int32_value": [1020, 718],
117    "bool_value": [true, false],
118    "string_value": ["a string\u044f", "another string"],
119    "bytes_value": ["YSBieXRlc//+", "YW5vdGhlciBieXRlcw=="],
120    "enum_value": ["VAL2", "VAL1"]
121    }
122    """
123
124    encoded_nested = """{
125    "nested": {
126      "a_value": "a string"
127    }
128    }
129    """
130
131    encoded_repeated_nested = """{
132    "repeated_nested": [{"a_value": "a string"},
133                        {"a_value": "another string"}]
134    }
135    """
136
137    unexpected_tag_message = '{"unknown": "value"}'
138
139    encoded_default_assigned = '{"a_value": "a default"}'
140
141    encoded_nested_empty = '{"nested": {}}'
142
143    encoded_repeated_nested_empty = '{"repeated_nested": [{}, {}]}'
144
145    encoded_extend_message = '{"int64_value": [400, 50, 6000]}'
146
147    encoded_string_types = '{"string_value": "Latin"}'
148
149    encoded_invalid_enum = '{"enum_value": "undefined"}'
150
151    def testConvertIntegerToFloat(self):
152        """Test that integers passed in to float fields are converted.
153
154        This is necessary because JSON outputs integers for numbers
155        with 0 decimals.
156
157        """
158        message = protojson.decode_message(MyMessage, '{"a_float": 10}')
159
160        self.assertTrue(isinstance(message.a_float, float))
161        self.assertEquals(10.0, message.a_float)
162
163    def testConvertStringToNumbers(self):
164        """Test that strings passed to integer fields are converted."""
165        message = protojson.decode_message(MyMessage,
166                                           """{"an_integer": "10",
167                                           "a_float": "3.5",
168                                           "a_repeated": ["1", "2"],
169                                           "a_repeated_float": ["1.5", "2", 10]
170                                           }""")
171
172        self.assertEquals(MyMessage(an_integer=10,
173                                    a_float=3.5,
174                                    a_repeated=[1, 2],
175                                    a_repeated_float=[1.5, 2.0, 10.0]),
176                          message)
177
178    def testWrongTypeAssignment(self):
179        """Test when wrong type is assigned to a field."""
180        self.assertRaises(messages.ValidationError,
181                          protojson.decode_message,
182                          MyMessage, '{"a_string": 10}')
183        self.assertRaises(messages.ValidationError,
184                          protojson.decode_message,
185                          MyMessage, '{"an_integer": 10.2}')
186        self.assertRaises(messages.ValidationError,
187                          protojson.decode_message,
188                          MyMessage, '{"an_integer": "10.2"}')
189
190    def testNumericEnumeration(self):
191        """Test that numbers work for enum values."""
192        message = protojson.decode_message(MyMessage, '{"an_enum": 2}')
193
194        expected_message = MyMessage()
195        expected_message.an_enum = MyMessage.Color.GREEN
196
197        self.assertEquals(expected_message, message)
198
199    def testNumericEnumerationNegativeTest(self):
200        """Test with an invalid number for the enum value."""
201        self.assertRaisesRegexp(
202            messages.DecodeError,
203            'Invalid enum value "89"',
204            protojson.decode_message,
205            MyMessage,
206            '{"an_enum": 89}')
207
208    def testAlphaEnumeration(self):
209        """Test that alpha enum values work."""
210        message = protojson.decode_message(MyMessage, '{"an_enum": "RED"}')
211
212        expected_message = MyMessage()
213        expected_message.an_enum = MyMessage.Color.RED
214
215        self.assertEquals(expected_message, message)
216
217    def testAlphaEnumerationNegativeTest(self):
218        """The alpha enum value is invalid."""
219        self.assertRaisesRegexp(
220            messages.DecodeError,
221            'Invalid enum value "IAMINVALID"',
222            protojson.decode_message,
223            MyMessage,
224            '{"an_enum": "IAMINVALID"}')
225
226    def testEnumerationNegativeTestWithEmptyString(self):
227        """The enum value is an empty string."""
228        self.assertRaisesRegexp(
229            messages.DecodeError,
230            'Invalid enum value ""',
231            protojson.decode_message,
232            MyMessage,
233            '{"an_enum": ""}')
234
235    def testNullValues(self):
236        """Test that null values overwrite existing values."""
237        self.assertEquals(MyMessage(),
238                          protojson.decode_message(MyMessage,
239                                                   ('{"an_integer": null,'
240                                                    ' "a_nested": null,'
241                                                    ' "an_enum": null'
242                                                    '}')))
243
244    def testEmptyList(self):
245        """Test that empty lists are ignored."""
246        self.assertEquals(MyMessage(),
247                          protojson.decode_message(MyMessage,
248                                                   '{"a_repeated": []}'))
249
250    def testNotJSON(self):
251        """Test error when string is not valid JSON."""
252        self.assertRaises(
253            ValueError,
254            protojson.decode_message, MyMessage,
255            '{this is not json}')
256
257    def testDoNotEncodeStrangeObjects(self):
258        """Test trying to encode a strange object.
259
260        The main purpose of this test is to complete coverage. It
261        ensures that the default behavior of the JSON encoder is
262        preserved when someone tries to serialized an unexpected type.
263
264        """
265        class BogusObject(object):
266
267            def check_initialized(self):
268                pass
269
270        self.assertRaises(TypeError,
271                          protojson.encode_message,
272                          BogusObject())
273
274    def testMergeEmptyString(self):
275        """Test merging the empty or space only string."""
276        message = protojson.decode_message(test_util.OptionalMessage, '')
277        self.assertEquals(test_util.OptionalMessage(), message)
278
279        message = protojson.decode_message(test_util.OptionalMessage, ' ')
280        self.assertEquals(test_util.OptionalMessage(), message)
281
282    def testProtojsonUnrecognizedFieldName(self):
283        """Test that unrecognized fields are saved and can be accessed."""
284        decoded = protojson.decode_message(
285            MyMessage,
286            ('{"an_integer": 1, "unknown_val": 2}'))
287        self.assertEquals(decoded.an_integer, 1)
288        self.assertEquals(1, len(decoded.all_unrecognized_fields()))
289        self.assertEquals('unknown_val', decoded.all_unrecognized_fields()[0])
290        self.assertEquals((2, messages.Variant.INT64),
291                          decoded.get_unrecognized_field_info('unknown_val'))
292
293    def testProtojsonUnrecognizedFieldNumber(self):
294        """Test that unrecognized fields are saved and can be accessed."""
295        decoded = protojson.decode_message(
296            MyMessage,
297            '{"an_integer": 1, "1001": "unknown", "-123": "negative", '
298            '"456_mixed": 2}')
299        self.assertEquals(decoded.an_integer, 1)
300        self.assertEquals(3, len(decoded.all_unrecognized_fields()))
301        self.assertFalse(1001 in decoded.all_unrecognized_fields())
302        self.assertTrue('1001' in decoded.all_unrecognized_fields())
303        self.assertEquals(('unknown', messages.Variant.STRING),
304                          decoded.get_unrecognized_field_info('1001'))
305        self.assertTrue('-123' in decoded.all_unrecognized_fields())
306        self.assertEquals(('negative', messages.Variant.STRING),
307                          decoded.get_unrecognized_field_info('-123'))
308        self.assertTrue('456_mixed' in decoded.all_unrecognized_fields())
309        self.assertEquals((2, messages.Variant.INT64),
310                          decoded.get_unrecognized_field_info('456_mixed'))
311
312    def testProtojsonUnrecognizedNull(self):
313        """Test that unrecognized fields that are None are skipped."""
314        decoded = protojson.decode_message(
315            MyMessage,
316            '{"an_integer": 1, "unrecognized_null": null}')
317        self.assertEquals(decoded.an_integer, 1)
318        self.assertEquals(decoded.all_unrecognized_fields(), [])
319
320    def testUnrecognizedFieldVariants(self):
321        """Test that unrecognized fields are mapped to the right variants."""
322        for encoded, expected_variant in (
323                ('{"an_integer": 1, "unknown_val": 2}',
324                 messages.Variant.INT64),
325                ('{"an_integer": 1, "unknown_val": 2.0}',
326                 messages.Variant.DOUBLE),
327                ('{"an_integer": 1, "unknown_val": "string value"}',
328                 messages.Variant.STRING),
329                ('{"an_integer": 1, "unknown_val": [1, 2, 3]}',
330                 messages.Variant.INT64),
331                ('{"an_integer": 1, "unknown_val": [1, 2.0, 3]}',
332                 messages.Variant.DOUBLE),
333                ('{"an_integer": 1, "unknown_val": [1, "foo", 3]}',
334                 messages.Variant.STRING),
335                ('{"an_integer": 1, "unknown_val": true}',
336                 messages.Variant.BOOL)):
337            decoded = protojson.decode_message(MyMessage, encoded)
338            self.assertEquals(decoded.an_integer, 1)
339            self.assertEquals(1, len(decoded.all_unrecognized_fields()))
340            self.assertEquals(
341                'unknown_val', decoded.all_unrecognized_fields()[0])
342            _, decoded_variant = decoded.get_unrecognized_field_info(
343                'unknown_val')
344            self.assertEquals(expected_variant, decoded_variant)
345
346    def testDecodeDateTime(self):
347        for datetime_string, datetime_vals in (
348                ('2012-09-30T15:31:50.262', (2012, 9, 30, 15, 31, 50, 262000)),
349                ('2012-09-30T15:31:50', (2012, 9, 30, 15, 31, 50, 0))):
350            message = protojson.decode_message(
351                MyMessage, '{"a_datetime": "%s"}' % datetime_string)
352            expected_message = MyMessage(
353                a_datetime=datetime.datetime(*datetime_vals))
354
355            self.assertEquals(expected_message, message)
356
357    def testDecodeInvalidDateTime(self):
358        self.assertRaises(messages.DecodeError, protojson.decode_message,
359                          MyMessage, '{"a_datetime": "invalid"}')
360
361    def testEncodeDateTime(self):
362        for datetime_string, datetime_vals in (
363                ('2012-09-30T15:31:50.262000',
364                 (2012, 9, 30, 15, 31, 50, 262000)),
365                ('2012-09-30T15:31:50.262123',
366                 (2012, 9, 30, 15, 31, 50, 262123)),
367                ('2012-09-30T15:31:50',
368                 (2012, 9, 30, 15, 31, 50, 0))):
369            decoded_message = protojson.encode_message(
370                MyMessage(a_datetime=datetime.datetime(*datetime_vals)))
371            expected_decoding = '{"a_datetime": "%s"}' % datetime_string
372            self.CompareEncoded(expected_decoding, decoded_message)
373
374    def testDecodeRepeatedDateTime(self):
375        message = protojson.decode_message(
376            MyMessage,
377            '{"a_repeated_datetime": ["2012-09-30T15:31:50.262", '
378            '"2010-01-21T09:52:00", "2000-01-01T01:00:59.999999"]}')
379        expected_message = MyMessage(
380            a_repeated_datetime=[
381                datetime.datetime(2012, 9, 30, 15, 31, 50, 262000),
382                datetime.datetime(2010, 1, 21, 9, 52),
383                datetime.datetime(2000, 1, 1, 1, 0, 59, 999999)])
384
385        self.assertEquals(expected_message, message)
386
387    def testDecodeCustom(self):
388        message = protojson.decode_message(MyMessage, '{"a_custom": 1}')
389        self.assertEquals(MyMessage(a_custom=1), message)
390
391    def testDecodeInvalidCustom(self):
392        self.assertRaises(messages.ValidationError, protojson.decode_message,
393                          MyMessage, '{"a_custom": "invalid"}')
394
395    def testEncodeCustom(self):
396        decoded_message = protojson.encode_message(MyMessage(a_custom=1))
397        self.CompareEncoded('{"a_custom": 1}', decoded_message)
398
399    def testDecodeRepeatedCustom(self):
400        message = protojson.decode_message(
401            MyMessage, '{"a_repeated_custom": [1, 2, 3]}')
402        self.assertEquals(MyMessage(a_repeated_custom=[1, 2, 3]), message)
403
404    def testDecodeRepeatedEmpty(self):
405        message = protojson.decode_message(
406            MyMessage, '{"a_repeated": []}')
407        self.assertEquals(MyMessage(a_repeated=[]), message)
408
409    def testDecodeNone(self):
410        message = protojson.decode_message(
411            MyMessage, '{"an_integer": []}')
412        self.assertEquals(MyMessage(an_integer=None), message)
413
414    def testDecodeBadBase64BytesField(self):
415        """Test decoding improperly encoded base64 bytes value."""
416        self.assertRaisesWithRegexpMatch(
417            messages.DecodeError,
418            'Base64 decoding error: Incorrect padding',
419            protojson.decode_message,
420            test_util.OptionalMessage,
421            '{"bytes_value": "abcdefghijklmnopq"}')
422
423
424class CustomProtoJson(protojson.ProtoJson):
425
426    def encode_field(self, field, value):
427        return '{encoded}' + value
428
429    def decode_field(self, field, value):
430        return '{decoded}' + value
431
432
433class CustomProtoJsonTest(test_util.TestCase):
434    """Tests for serialization overriding functionality."""
435
436    def setUp(self):
437        self.protojson = CustomProtoJson()
438
439    def testEncode(self):
440        self.assertEqual(
441            '{"a_string": "{encoded}xyz"}',
442            self.protojson.encode_message(MyMessage(a_string='xyz')))
443
444    def testDecode(self):
445        self.assertEqual(
446            MyMessage(a_string='{decoded}xyz'),
447            self.protojson.decode_message(MyMessage, '{"a_string": "xyz"}'))
448
449    def testDecodeEmptyMessage(self):
450        self.assertEqual(
451            MyMessage(a_string='{decoded}'),
452            self.protojson.decode_message(MyMessage, '{"a_string": ""}'))
453
454    def testDefault(self):
455        self.assertTrue(protojson.ProtoJson.get_default(),
456                        protojson.ProtoJson.get_default())
457
458        instance = CustomProtoJson()
459        protojson.ProtoJson.set_default(instance)
460        self.assertTrue(instance is protojson.ProtoJson.get_default())
461
462
463if __name__ == '__main__':
464    unittest.main()
465