• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2017, Google LLC
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15
16import mock
17import pytest
18
19try:
20    import grpc  # noqa: F401
21except ImportError:
22    pytest.skip("No GRPC", allow_module_level=True)
23
24from google.api_core import exceptions
25from google.api_core import operation
26from google.api_core import operations_v1
27from google.api_core import retry
28from google.longrunning import operations_pb2
29from google.protobuf import struct_pb2
30from google.rpc import code_pb2
31from google.rpc import status_pb2
32
33TEST_OPERATION_NAME = "test/operation"
34
35
36def make_operation_proto(
37    name=TEST_OPERATION_NAME, metadata=None, response=None, error=None, **kwargs
38):
39    operation_proto = operations_pb2.Operation(name=name, **kwargs)
40
41    if metadata is not None:
42        operation_proto.metadata.Pack(metadata)
43
44    if response is not None:
45        operation_proto.response.Pack(response)
46
47    if error is not None:
48        operation_proto.error.CopyFrom(error)
49
50    return operation_proto
51
52
53def make_operation_future(client_operations_responses=None):
54    if client_operations_responses is None:
55        client_operations_responses = [make_operation_proto()]
56
57    refresh = mock.Mock(spec=["__call__"], side_effect=client_operations_responses)
58    refresh.responses = client_operations_responses
59    cancel = mock.Mock(spec=["__call__"])
60    operation_future = operation.Operation(
61        client_operations_responses[0],
62        refresh,
63        cancel,
64        result_type=struct_pb2.Struct,
65        metadata_type=struct_pb2.Struct,
66    )
67
68    return operation_future, refresh, cancel
69
70
71def test_constructor():
72    future, refresh, _ = make_operation_future()
73
74    assert future.operation == refresh.responses[0]
75    assert future.operation.done is False
76    assert future.operation.name == TEST_OPERATION_NAME
77    assert future.metadata is None
78    assert future.running()
79
80
81def test_metadata():
82    expected_metadata = struct_pb2.Struct()
83    future, _, _ = make_operation_future(
84        [make_operation_proto(metadata=expected_metadata)]
85    )
86
87    assert future.metadata == expected_metadata
88
89
90def test_cancellation():
91    responses = [
92        make_operation_proto(),
93        # Second response indicates that the operation was cancelled.
94        make_operation_proto(
95            done=True, error=status_pb2.Status(code=code_pb2.CANCELLED)
96        ),
97    ]
98    future, _, cancel = make_operation_future(responses)
99
100    assert future.cancel()
101    assert future.cancelled()
102    cancel.assert_called_once_with()
103
104    # Cancelling twice should have no effect.
105    assert not future.cancel()
106    cancel.assert_called_once_with()
107
108
109def test_result():
110    expected_result = struct_pb2.Struct()
111    responses = [
112        make_operation_proto(),
113        # Second operation response includes the result.
114        make_operation_proto(done=True, response=expected_result),
115    ]
116    future, _, _ = make_operation_future(responses)
117
118    result = future.result()
119
120    assert result == expected_result
121    assert future.done()
122
123
124def test_done_w_retry():
125    RETRY_PREDICATE = retry.if_exception_type(exceptions.TooManyRequests)
126    test_retry = retry.Retry(predicate=RETRY_PREDICATE)
127
128    expected_result = struct_pb2.Struct()
129    responses = [
130        make_operation_proto(),
131        # Second operation response includes the result.
132        make_operation_proto(done=True, response=expected_result),
133    ]
134    future, _, _ = make_operation_future(responses)
135    future._refresh = mock.Mock()
136
137    future.done(retry=test_retry)
138    future._refresh.assert_called_once_with(retry=test_retry)
139
140
141def test_exception():
142    expected_exception = status_pb2.Status(message="meep")
143    responses = [
144        make_operation_proto(),
145        # Second operation response includes the error.
146        make_operation_proto(done=True, error=expected_exception),
147    ]
148    future, _, _ = make_operation_future(responses)
149
150    exception = future.exception()
151
152    assert expected_exception.message in "{!r}".format(exception)
153
154
155def test_exception_with_error_code():
156    expected_exception = status_pb2.Status(message="meep", code=5)
157    responses = [
158        make_operation_proto(),
159        # Second operation response includes the error.
160        make_operation_proto(done=True, error=expected_exception),
161    ]
162    future, _, _ = make_operation_future(responses)
163
164    exception = future.exception()
165
166    assert expected_exception.message in "{!r}".format(exception)
167    # Status Code 5 maps to Not Found
168    # https://developers.google.com/maps-booking/reference/grpc-api/status_codes
169    assert isinstance(exception, exceptions.NotFound)
170
171
172def test_unexpected_result():
173    responses = [
174        make_operation_proto(),
175        # Second operation response is done, but has not error or response.
176        make_operation_proto(done=True),
177    ]
178    future, _, _ = make_operation_future(responses)
179
180    exception = future.exception()
181
182    assert "Unexpected state" in "{!r}".format(exception)
183
184
185def test__refresh_http():
186    json_response = {"name": TEST_OPERATION_NAME, "done": True}
187    api_request = mock.Mock(return_value=json_response)
188
189    result = operation._refresh_http(api_request, TEST_OPERATION_NAME)
190
191    assert isinstance(result, operations_pb2.Operation)
192    assert result.name == TEST_OPERATION_NAME
193    assert result.done is True
194
195    api_request.assert_called_once_with(
196        method="GET", path="operations/{}".format(TEST_OPERATION_NAME)
197    )
198
199
200def test__refresh_http_w_retry():
201    json_response = {"name": TEST_OPERATION_NAME, "done": True}
202    api_request = mock.Mock()
203    retry = mock.Mock()
204    retry.return_value.return_value = json_response
205
206    result = operation._refresh_http(api_request, TEST_OPERATION_NAME, retry=retry)
207
208    assert isinstance(result, operations_pb2.Operation)
209    assert result.name == TEST_OPERATION_NAME
210    assert result.done is True
211
212    api_request.assert_not_called()
213    retry.assert_called_once_with(api_request)
214    retry.return_value.assert_called_once_with(
215        method="GET", path="operations/{}".format(TEST_OPERATION_NAME)
216    )
217
218
219def test__cancel_http():
220    api_request = mock.Mock()
221
222    operation._cancel_http(api_request, TEST_OPERATION_NAME)
223
224    api_request.assert_called_once_with(
225        method="POST", path="operations/{}:cancel".format(TEST_OPERATION_NAME)
226    )
227
228
229def test_from_http_json():
230    operation_json = {"name": TEST_OPERATION_NAME, "done": True}
231    api_request = mock.sentinel.api_request
232
233    future = operation.from_http_json(
234        operation_json, api_request, struct_pb2.Struct, metadata_type=struct_pb2.Struct
235    )
236
237    assert future._result_type == struct_pb2.Struct
238    assert future._metadata_type == struct_pb2.Struct
239    assert future.operation.name == TEST_OPERATION_NAME
240    assert future.done
241
242
243def test__refresh_grpc():
244    operations_stub = mock.Mock(spec=["GetOperation"])
245    expected_result = make_operation_proto(done=True)
246    operations_stub.GetOperation.return_value = expected_result
247
248    result = operation._refresh_grpc(operations_stub, TEST_OPERATION_NAME)
249
250    assert result == expected_result
251    expected_request = operations_pb2.GetOperationRequest(name=TEST_OPERATION_NAME)
252    operations_stub.GetOperation.assert_called_once_with(expected_request)
253
254
255def test__refresh_grpc_w_retry():
256    operations_stub = mock.Mock(spec=["GetOperation"])
257    expected_result = make_operation_proto(done=True)
258    retry = mock.Mock()
259    retry.return_value.return_value = expected_result
260
261    result = operation._refresh_grpc(operations_stub, TEST_OPERATION_NAME, retry=retry)
262
263    assert result == expected_result
264    expected_request = operations_pb2.GetOperationRequest(name=TEST_OPERATION_NAME)
265    operations_stub.GetOperation.assert_not_called()
266    retry.assert_called_once_with(operations_stub.GetOperation)
267    retry.return_value.assert_called_once_with(expected_request)
268
269
270def test__cancel_grpc():
271    operations_stub = mock.Mock(spec=["CancelOperation"])
272
273    operation._cancel_grpc(operations_stub, TEST_OPERATION_NAME)
274
275    expected_request = operations_pb2.CancelOperationRequest(name=TEST_OPERATION_NAME)
276    operations_stub.CancelOperation.assert_called_once_with(expected_request)
277
278
279def test_from_grpc():
280    operation_proto = make_operation_proto(done=True)
281    operations_stub = mock.sentinel.operations_stub
282
283    future = operation.from_grpc(
284        operation_proto,
285        operations_stub,
286        struct_pb2.Struct,
287        metadata_type=struct_pb2.Struct,
288        grpc_metadata=[("x-goog-request-params", "foo")],
289    )
290
291    assert future._result_type == struct_pb2.Struct
292    assert future._metadata_type == struct_pb2.Struct
293    assert future.operation.name == TEST_OPERATION_NAME
294    assert future.done
295    assert future._refresh.keywords["metadata"] == [("x-goog-request-params", "foo")]
296    assert future._cancel.keywords["metadata"] == [("x-goog-request-params", "foo")]
297
298
299def test_from_gapic():
300    operation_proto = make_operation_proto(done=True)
301    operations_client = mock.create_autospec(
302        operations_v1.OperationsClient, instance=True
303    )
304
305    future = operation.from_gapic(
306        operation_proto,
307        operations_client,
308        struct_pb2.Struct,
309        metadata_type=struct_pb2.Struct,
310        grpc_metadata=[("x-goog-request-params", "foo")],
311    )
312
313    assert future._result_type == struct_pb2.Struct
314    assert future._metadata_type == struct_pb2.Struct
315    assert future.operation.name == TEST_OPERATION_NAME
316    assert future.done
317    assert future._refresh.keywords["metadata"] == [("x-goog-request-params", "foo")]
318    assert future._cancel.keywords["metadata"] == [("x-goog-request-params", "foo")]
319
320
321def test_deserialize():
322    op = make_operation_proto(name="foobarbaz")
323    serialized = op.SerializeToString()
324    deserialized_op = operation.Operation.deserialize(serialized)
325    assert op.name == deserialized_op.name
326    assert type(op) is type(deserialized_op)
327