OLD | NEW |
(Empty) | |
| 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.""" |
| 18 import datetime |
| 19 import json |
| 20 import unittest |
| 21 |
| 22 from apitools.base.protorpclite import message_types |
| 23 from apitools.base.protorpclite import messages |
| 24 from apitools.base.protorpclite import protojson |
| 25 from apitools.base.protorpclite import test_util |
| 26 |
| 27 |
| 28 class 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 |
| 41 class 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 |
| 68 class 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. |
| 75 class 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 testDecodeBadBase64BytesField(self): |
| 405 """Test decoding improperly encoded base64 bytes value.""" |
| 406 self.assertRaisesWithRegexpMatch( |
| 407 messages.DecodeError, |
| 408 'Base64 decoding error: Incorrect padding', |
| 409 protojson.decode_message, |
| 410 test_util.OptionalMessage, |
| 411 '{"bytes_value": "abcdefghijklmnopq"}') |
| 412 |
| 413 |
| 414 class CustomProtoJson(protojson.ProtoJson): |
| 415 |
| 416 def encode_field(self, field, value): |
| 417 return '{encoded}' + value |
| 418 |
| 419 def decode_field(self, field, value): |
| 420 return '{decoded}' + value |
| 421 |
| 422 |
| 423 class CustomProtoJsonTest(test_util.TestCase): |
| 424 """Tests for serialization overriding functionality.""" |
| 425 |
| 426 def setUp(self): |
| 427 self.protojson = CustomProtoJson() |
| 428 |
| 429 def testEncode(self): |
| 430 self.assertEqual( |
| 431 '{"a_string": "{encoded}xyz"}', |
| 432 self.protojson.encode_message(MyMessage(a_string='xyz'))) |
| 433 |
| 434 def testDecode(self): |
| 435 self.assertEqual( |
| 436 MyMessage(a_string='{decoded}xyz'), |
| 437 self.protojson.decode_message(MyMessage, '{"a_string": "xyz"}')) |
| 438 |
| 439 def testDecodeEmptyMessage(self): |
| 440 self.assertEqual( |
| 441 MyMessage(a_string='{decoded}'), |
| 442 self.protojson.decode_message(MyMessage, '{"a_string": ""}')) |
| 443 |
| 444 def testDefault(self): |
| 445 self.assertTrue(protojson.ProtoJson.get_default(), |
| 446 protojson.ProtoJson.get_default()) |
| 447 |
| 448 instance = CustomProtoJson() |
| 449 protojson.ProtoJson.set_default(instance) |
| 450 self.assertTrue(instance is protojson.ProtoJson.get_default()) |
| 451 |
| 452 |
| 453 if __name__ == '__main__': |
| 454 unittest.main() |
OLD | NEW |