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