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