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