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