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