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 def testConvertIntegerToFloat(self): 157 """Test that integers passed in to float fields are converted. 158 159 This is necessary because JSON outputs integers for numbers 160 with 0 decimals. 161 162 """ 163 message = protojson.decode_message(MyMessage, '{"a_float": 10}') 164 165 self.assertTrue(isinstance(message.a_float, float)) 166 self.assertEquals(10.0, message.a_float) 167 168 def testConvertStringToNumbers(self): 169 """Test that strings passed to integer fields are converted.""" 170 message = protojson.decode_message(MyMessage, 171 """{"an_integer": "10", 172 "a_float": "3.5", 173 "a_repeated": ["1", "2"], 174 "a_repeated_float": ["1.5", "2", 10] 175 }""") 176 177 self.assertEquals(MyMessage(an_integer=10, 178 a_float=3.5, 179 a_repeated=[1, 2], 180 a_repeated_float=[1.5, 2.0, 10.0]), 181 message) 182 183 def testWrongTypeAssignment(self): 184 """Test when wrong type is assigned to a field.""" 185 self.assertRaises(messages.ValidationError, 186 protojson.decode_message, 187 MyMessage, '{"a_string": 10}') 188 self.assertRaises(messages.ValidationError, 189 protojson.decode_message, 190 MyMessage, '{"an_integer": 10.2}') 191 self.assertRaises(messages.ValidationError, 192 protojson.decode_message, 193 MyMessage, '{"an_integer": "10.2"}') 194 195 def testNumericEnumeration(self): 196 """Test that numbers work for enum values.""" 197 message = protojson.decode_message(MyMessage, '{"an_enum": 2}') 198 199 expected_message = MyMessage() 200 expected_message.an_enum = MyMessage.Color.GREEN 201 202 self.assertEquals(expected_message, message) 203 204 def testNumericEnumerationNegativeTest(self): 205 """Test with an invalid number for the enum value.""" 206 # The message should successfully decode. 207 message = protojson.decode_message(MyMessage, 208 '{"an_enum": 89}') 209 210 expected_message = MyMessage() 211 212 self.assertEquals(expected_message, message) 213 # The roundtrip should result in equivalent encoded 214 # message. 215 self.assertEquals('{"an_enum": 89}', protojson.encode_message(message)) 216 217 def testAlphaEnumeration(self): 218 """Test that alpha enum values work.""" 219 message = protojson.decode_message(MyMessage, '{"an_enum": "RED"}') 220 221 expected_message = MyMessage() 222 expected_message.an_enum = MyMessage.Color.RED 223 224 self.assertEquals(expected_message, message) 225 226 def testAlphaEnumerationNegativeTest(self): 227 """The alpha enum value is invalid.""" 228 # The message should successfully decode. 229 message = protojson.decode_message(MyMessage, 230 '{"an_enum": "IAMINVALID"}') 231 232 expected_message = MyMessage() 233 234 self.assertEquals(expected_message, message) 235 # The roundtrip should result in equivalent encoded message. 236 self.assertEquals('{"an_enum": "IAMINVALID"}', 237 protojson.encode_message(message)) 238 239 def testEnumerationNegativeTestWithEmptyString(self): 240 """The enum value is an empty string.""" 241 # The message should successfully decode. 242 message = protojson.decode_message(MyMessage, '{"an_enum": ""}') 243 244 expected_message = MyMessage() 245 246 self.assertEquals(expected_message, message) 247 # The roundtrip should result in equivalent encoded message. 248 self.assertEquals('{"an_enum": ""}', protojson.encode_message(message)) 249 250 def testNullValues(self): 251 """Test that null values overwrite existing values.""" 252 self.assertEquals(MyMessage(), 253 protojson.decode_message(MyMessage, 254 ('{"an_integer": null,' 255 ' "a_nested": null,' 256 ' "an_enum": null' 257 '}'))) 258 259 def testEmptyList(self): 260 """Test that empty lists are ignored.""" 261 self.assertEquals(MyMessage(), 262 protojson.decode_message(MyMessage, 263 '{"a_repeated": []}')) 264 265 def testNotJSON(self): 266 """Test error when string is not valid JSON.""" 267 self.assertRaises( 268 ValueError, 269 protojson.decode_message, MyMessage, 270 '{this is not json}') 271 272 def testDoNotEncodeStrangeObjects(self): 273 """Test trying to encode a strange object. 274 275 The main purpose of this test is to complete coverage. It 276 ensures that the default behavior of the JSON encoder is 277 preserved when someone tries to serialized an unexpected type. 278 279 """ 280 class BogusObject(object): 281 282 def check_initialized(self): 283 pass 284 285 self.assertRaises(TypeError, 286 protojson.encode_message, 287 BogusObject()) 288 289 def testMergeEmptyString(self): 290 """Test merging the empty or space only string.""" 291 message = protojson.decode_message(test_util.OptionalMessage, '') 292 self.assertEquals(test_util.OptionalMessage(), message) 293 294 message = protojson.decode_message(test_util.OptionalMessage, ' ') 295 self.assertEquals(test_util.OptionalMessage(), message) 296 297 def testProtojsonUnrecognizedFieldName(self): 298 """Test that unrecognized fields are saved and can be accessed.""" 299 decoded = protojson.decode_message( 300 MyMessage, 301 ('{"an_integer": 1, "unknown_val": 2}')) 302 self.assertEquals(decoded.an_integer, 1) 303 self.assertEquals(1, len(decoded.all_unrecognized_fields())) 304 self.assertEquals('unknown_val', decoded.all_unrecognized_fields()[0]) 305 self.assertEquals((2, messages.Variant.INT64), 306 decoded.get_unrecognized_field_info('unknown_val')) 307 308 def testProtojsonUnrecognizedFieldNumber(self): 309 """Test that unrecognized fields are saved and can be accessed.""" 310 decoded = protojson.decode_message( 311 MyMessage, 312 '{"an_integer": 1, "1001": "unknown", "-123": "negative", ' 313 '"456_mixed": 2}') 314 self.assertEquals(decoded.an_integer, 1) 315 self.assertEquals(3, len(decoded.all_unrecognized_fields())) 316 self.assertFalse(1001 in decoded.all_unrecognized_fields()) 317 self.assertTrue('1001' in decoded.all_unrecognized_fields()) 318 self.assertEquals(('unknown', messages.Variant.STRING), 319 decoded.get_unrecognized_field_info('1001')) 320 self.assertTrue('-123' in decoded.all_unrecognized_fields()) 321 self.assertEquals(('negative', messages.Variant.STRING), 322 decoded.get_unrecognized_field_info('-123')) 323 self.assertTrue('456_mixed' in decoded.all_unrecognized_fields()) 324 self.assertEquals((2, messages.Variant.INT64), 325 decoded.get_unrecognized_field_info('456_mixed')) 326 327 def testProtojsonUnrecognizedNull(self): 328 """Test that unrecognized fields that are None are skipped.""" 329 decoded = protojson.decode_message( 330 MyMessage, 331 '{"an_integer": 1, "unrecognized_null": null}') 332 self.assertEquals(decoded.an_integer, 1) 333 self.assertEquals(decoded.all_unrecognized_fields(), []) 334 335 def testUnrecognizedFieldVariants(self): 336 """Test that unrecognized fields are mapped to the right variants.""" 337 for encoded, expected_variant in ( 338 ('{"an_integer": 1, "unknown_val": 2}', 339 messages.Variant.INT64), 340 ('{"an_integer": 1, "unknown_val": 2.0}', 341 messages.Variant.DOUBLE), 342 ('{"an_integer": 1, "unknown_val": "string value"}', 343 messages.Variant.STRING), 344 ('{"an_integer": 1, "unknown_val": [1, 2, 3]}', 345 messages.Variant.INT64), 346 ('{"an_integer": 1, "unknown_val": [1, 2.0, 3]}', 347 messages.Variant.DOUBLE), 348 ('{"an_integer": 1, "unknown_val": [1, "foo", 3]}', 349 messages.Variant.STRING), 350 ('{"an_integer": 1, "unknown_val": true}', 351 messages.Variant.BOOL)): 352 decoded = protojson.decode_message(MyMessage, encoded) 353 self.assertEquals(decoded.an_integer, 1) 354 self.assertEquals(1, len(decoded.all_unrecognized_fields())) 355 self.assertEquals( 356 'unknown_val', decoded.all_unrecognized_fields()[0]) 357 _, decoded_variant = decoded.get_unrecognized_field_info( 358 'unknown_val') 359 self.assertEquals(expected_variant, decoded_variant) 360 361 def testDecodeDateTime(self): 362 for datetime_string, datetime_vals in ( 363 ('2012-09-30T15:31:50.262', (2012, 9, 30, 15, 31, 50, 262000)), 364 ('2012-09-30T15:31:50', (2012, 9, 30, 15, 31, 50, 0))): 365 message = protojson.decode_message( 366 MyMessage, '{"a_datetime": "%s"}' % datetime_string) 367 expected_message = MyMessage( 368 a_datetime=datetime.datetime(*datetime_vals)) 369 370 self.assertEquals(expected_message, message) 371 372 def testDecodeInvalidDateTime(self): 373 self.assertRaises(messages.DecodeError, protojson.decode_message, 374 MyMessage, '{"a_datetime": "invalid"}') 375 376 def testDecodeInvalidMessage(self): 377 encoded = """{ 378 "a_nested_datetime": { 379 "nested_dt_value": "invalid" 380 } 381 } 382 """ 383 self.assertRaises(messages.DecodeError, protojson.decode_message, 384 MyMessage, encoded) 385 386 def testEncodeDateTime(self): 387 for datetime_string, datetime_vals in ( 388 ('2012-09-30T15:31:50.262000', 389 (2012, 9, 30, 15, 31, 50, 262000)), 390 ('2012-09-30T15:31:50.262123', 391 (2012, 9, 30, 15, 31, 50, 262123)), 392 ('2012-09-30T15:31:50', 393 (2012, 9, 30, 15, 31, 50, 0))): 394 decoded_message = protojson.encode_message( 395 MyMessage(a_datetime=datetime.datetime(*datetime_vals))) 396 expected_decoding = '{"a_datetime": "%s"}' % datetime_string 397 self.CompareEncoded(expected_decoding, decoded_message) 398 399 def testDecodeRepeatedDateTime(self): 400 message = protojson.decode_message( 401 MyMessage, 402 '{"a_repeated_datetime": ["2012-09-30T15:31:50.262", ' 403 '"2010-01-21T09:52:00", "2000-01-01T01:00:59.999999"]}') 404 expected_message = MyMessage( 405 a_repeated_datetime=[ 406 datetime.datetime(2012, 9, 30, 15, 31, 50, 262000), 407 datetime.datetime(2010, 1, 21, 9, 52), 408 datetime.datetime(2000, 1, 1, 1, 0, 59, 999999)]) 409 410 self.assertEquals(expected_message, message) 411 412 def testDecodeCustom(self): 413 message = protojson.decode_message(MyMessage, '{"a_custom": 1}') 414 self.assertEquals(MyMessage(a_custom=1), message) 415 416 def testDecodeInvalidCustom(self): 417 self.assertRaises(messages.ValidationError, protojson.decode_message, 418 MyMessage, '{"a_custom": "invalid"}') 419 420 def testEncodeCustom(self): 421 decoded_message = protojson.encode_message(MyMessage(a_custom=1)) 422 self.CompareEncoded('{"a_custom": 1}', decoded_message) 423 424 def testDecodeRepeatedCustom(self): 425 message = protojson.decode_message( 426 MyMessage, '{"a_repeated_custom": [1, 2, 3]}') 427 self.assertEquals(MyMessage(a_repeated_custom=[1, 2, 3]), message) 428 429 def testDecodeRepeatedEmpty(self): 430 message = protojson.decode_message( 431 MyMessage, '{"a_repeated": []}') 432 self.assertEquals(MyMessage(a_repeated=[]), message) 433 434 def testDecodeNone(self): 435 message = protojson.decode_message( 436 MyMessage, '{"an_integer": []}') 437 self.assertEquals(MyMessage(an_integer=None), message) 438 439 def testDecodeBadBase64BytesField(self): 440 """Test decoding improperly encoded base64 bytes value.""" 441 self.assertRaisesWithRegexpMatch( 442 messages.DecodeError, 443 'Base64 decoding error: Incorrect padding', 444 protojson.decode_message, 445 test_util.OptionalMessage, 446 '{"bytes_value": "abcdefghijklmnopq"}') 447 448 449class CustomProtoJson(protojson.ProtoJson): 450 451 def encode_field(self, field, value): 452 return '{encoded}' + value 453 454 def decode_field(self, field, value): 455 return '{decoded}' + value 456 457 458class CustomProtoJsonTest(test_util.TestCase): 459 """Tests for serialization overriding functionality.""" 460 461 def setUp(self): 462 self.protojson = CustomProtoJson() 463 464 def testEncode(self): 465 self.assertEqual( 466 '{"a_string": "{encoded}xyz"}', 467 self.protojson.encode_message(MyMessage(a_string='xyz'))) 468 469 def testDecode(self): 470 self.assertEqual( 471 MyMessage(a_string='{decoded}xyz'), 472 self.protojson.decode_message(MyMessage, '{"a_string": "xyz"}')) 473 474 def testDecodeEmptyMessage(self): 475 self.assertEqual( 476 MyMessage(a_string='{decoded}'), 477 self.protojson.decode_message(MyMessage, '{"a_string": ""}')) 478 479 def testDefault(self): 480 self.assertTrue(protojson.ProtoJson.get_default(), 481 protojson.ProtoJson.get_default()) 482 483 instance = CustomProtoJson() 484 protojson.ProtoJson.set_default(instance) 485 self.assertTrue(instance is protojson.ProtoJson.get_default()) 486 487 488if __name__ == '__main__': 489 unittest.main() 490