1# 2# Copyright 2015 Google Inc. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16import datetime 17import json 18import math 19 20import unittest2 21 22from apitools.base.protorpclite import messages 23from apitools.base.py import encoding 24from apitools.base.py import exceptions 25from apitools.base.py import extra_types 26 27 28class ExtraTypesTest(unittest2.TestCase): 29 30 def assertRoundTrip(self, value): 31 if isinstance(value, extra_types._JSON_PROTO_TYPES): 32 self.assertEqual( 33 value, 34 extra_types._PythonValueToJsonProto( 35 extra_types._JsonProtoToPythonValue(value))) 36 else: 37 self.assertEqual( 38 value, 39 extra_types._JsonProtoToPythonValue( 40 extra_types._PythonValueToJsonProto(value))) 41 42 def assertTranslations(self, py_value, json_proto): 43 self.assertEqual( 44 py_value, extra_types._JsonProtoToPythonValue(json_proto)) 45 self.assertEqual( 46 json_proto, extra_types._PythonValueToJsonProto(py_value)) 47 48 def testInvalidProtos(self): 49 with self.assertRaises(exceptions.InvalidDataError): 50 extra_types._ValidateJsonValue(extra_types.JsonValue()) 51 with self.assertRaises(exceptions.InvalidDataError): 52 extra_types._ValidateJsonValue( 53 extra_types.JsonValue(is_null=True, string_value='a')) 54 with self.assertRaises(exceptions.InvalidDataError): 55 extra_types._ValidateJsonValue( 56 extra_types.JsonValue(integer_value=3, string_value='a')) 57 58 def testNullEncoding(self): 59 self.assertTranslations(None, extra_types.JsonValue(is_null=True)) 60 61 def testJsonNumberEncoding(self): 62 seventeen = extra_types.JsonValue(integer_value=17) 63 self.assertRoundTrip(17) 64 self.assertRoundTrip(seventeen) 65 self.assertTranslations(17, seventeen) 66 67 json_pi = extra_types.JsonValue(double_value=math.pi) 68 self.assertRoundTrip(math.pi) 69 self.assertRoundTrip(json_pi) 70 self.assertTranslations(math.pi, json_pi) 71 72 def testArrayEncoding(self): 73 array = [3, 'four', False] 74 json_array = extra_types.JsonArray(entries=[ 75 extra_types.JsonValue(integer_value=3), 76 extra_types.JsonValue(string_value='four'), 77 extra_types.JsonValue(boolean_value=False), 78 ]) 79 self.assertRoundTrip(array) 80 self.assertRoundTrip(json_array) 81 self.assertTranslations(array, json_array) 82 83 def testArrayAsValue(self): 84 array_json = '[3, "four", false]' 85 array = [3, 'four', False] 86 value = encoding.JsonToMessage(extra_types.JsonValue, array_json) 87 self.assertTrue(isinstance(value, extra_types.JsonValue)) 88 self.assertEqual(array, encoding.MessageToPyValue(value)) 89 90 def testObjectAsValue(self): 91 obj_json = '{"works": true}' 92 obj = {'works': True} 93 value = encoding.JsonToMessage(extra_types.JsonValue, obj_json) 94 self.assertTrue(isinstance(value, extra_types.JsonValue)) 95 self.assertEqual(obj, encoding.MessageToPyValue(value)) 96 97 def testDictEncoding(self): 98 d = {'a': 6, 'b': 'eleventeen'} 99 json_d = extra_types.JsonObject(properties=[ 100 extra_types.JsonObject.Property( 101 key='a', value=extra_types.JsonValue(integer_value=6)), 102 extra_types.JsonObject.Property( 103 key='b', 104 value=extra_types.JsonValue(string_value='eleventeen')), 105 ]) 106 self.assertRoundTrip(d) 107 # We don't know json_d will round-trip, because of randomness in 108 # python dictionary iteration ordering. We also need to force 109 # comparison as lists, since hashing protos isn't helpful. 110 translated_properties = extra_types._PythonValueToJsonProto( 111 d).properties 112 for p in json_d.properties: 113 self.assertIn(p, translated_properties) 114 for p in translated_properties: 115 self.assertIn(p, json_d.properties) 116 117 def testJsonObjectPropertyTranslation(self): 118 value = extra_types.JsonValue(string_value='abc') 119 obj = extra_types.JsonObject(properties=[ 120 extra_types.JsonObject.Property(key='attr_name', value=value)]) 121 json_value = '"abc"' 122 json_obj = '{"attr_name": "abc"}' 123 124 self.assertRoundTrip(value) 125 self.assertRoundTrip(obj) 126 self.assertRoundTrip(json_value) 127 self.assertRoundTrip(json_obj) 128 129 self.assertEqual(json_value, encoding.MessageToJson(value)) 130 self.assertEqual(json_obj, encoding.MessageToJson(obj)) 131 132 def testJsonValueAsFieldTranslation(self): 133 class HasJsonValueMsg(messages.Message): 134 some_value = messages.MessageField(extra_types.JsonValue, 1) 135 136 msg_json = '{"some_value": [1, 2, 3]}' 137 msg = HasJsonValueMsg( 138 some_value=encoding.PyValueToMessage( 139 extra_types.JsonValue, [1, 2, 3])) 140 self.assertEqual(msg, 141 encoding.JsonToMessage(HasJsonValueMsg, msg_json)) 142 self.assertEqual(msg_json, encoding.MessageToJson(msg)) 143 144 def testDateField(self): 145 146 class DateMsg(messages.Message): 147 start_date = extra_types.DateField(1) 148 all_dates = extra_types.DateField(2, repeated=True) 149 150 msg = DateMsg( 151 start_date=datetime.date(1752, 9, 9), all_dates=[ 152 datetime.date(1979, 5, 6), 153 datetime.date(1980, 10, 24), 154 datetime.date(1981, 1, 19), 155 ]) 156 msg_dict = { 157 'start_date': '1752-09-09', 158 'all_dates': ['1979-05-06', '1980-10-24', '1981-01-19'], 159 } 160 self.assertEqual(msg_dict, json.loads(encoding.MessageToJson(msg))) 161 self.assertEqual( 162 msg, encoding.JsonToMessage(DateMsg, json.dumps(msg_dict))) 163 164 def testInt64(self): 165 # Testing roundtrip of type 'long' 166 167 class DogeMsg(messages.Message): 168 such_string = messages.StringField(1) 169 wow = messages.IntegerField(2, variant=messages.Variant.INT64) 170 very_unsigned = messages.IntegerField( 171 3, variant=messages.Variant.UINT64) 172 much_repeated = messages.IntegerField( 173 4, variant=messages.Variant.INT64, repeated=True) 174 175 def MtoJ(msg): 176 return encoding.MessageToJson(msg) 177 178 def JtoM(class_type, json_str): 179 return encoding.JsonToMessage(class_type, json_str) 180 181 def DoRoundtrip(class_type, json_msg=None, message=None, times=4): 182 if json_msg: 183 json_msg = MtoJ(JtoM(class_type, json_msg)) 184 if message: 185 message = JtoM(class_type, MtoJ(message)) 186 if times == 0: 187 result = json_msg if json_msg else message 188 return result 189 return DoRoundtrip(class_type=class_type, json_msg=json_msg, 190 message=message, times=times - 1) 191 192 # Single 193 json_msg = ('{"such_string": "poot", "wow": "-1234", ' 194 '"very_unsigned": "999", "much_repeated": ["123", "456"]}') 195 out_json = MtoJ(JtoM(DogeMsg, json_msg)) 196 self.assertEqual(json.loads(out_json)['wow'], '-1234') 197 198 # Repeated test case 199 msg = DogeMsg(such_string='wow', wow=-1234, 200 very_unsigned=800, much_repeated=[123, 456]) 201 self.assertEqual(msg, DoRoundtrip(DogeMsg, message=msg)) 202