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