|
12 | 12 | # See the License for the specific language governing permissions and
|
13 | 13 | # limitations under the License.
|
14 | 14 |
|
15 |
| -from google.cloud.firestore_v1.types.document import Document |
16 |
| -from google.cloud.firestore_v1.types.firestore import RunQueryResponse |
17 | 15 | import types
|
18 | 16 | import unittest
|
19 | 17 |
|
20 | 18 | import mock
|
21 | 19 | import pytest
|
22 | 20 |
|
| 21 | +from google.api_core import gapic_v1 |
| 22 | +from google.cloud.firestore_v1.types.document import Document |
| 23 | +from google.cloud.firestore_v1.types.firestore import RunQueryResponse |
23 | 24 | from tests.unit.v1.test_base_query import _make_credentials
|
24 | 25 | from tests.unit.v1.test_base_query import _make_cursor_pb
|
25 | 26 | from tests.unit.v1.test_base_query import _make_query_response
|
@@ -456,6 +457,124 @@ def test_stream_w_collection_group(self):
|
456 | 457 | metadata=client._rpc_metadata,
|
457 | 458 | )
|
458 | 459 |
|
| 460 | + def _stream_w_retriable_exc_helper( |
| 461 | + self, |
| 462 | + retry=gapic_v1.method.DEFAULT, |
| 463 | + timeout=None, |
| 464 | + transaction=None, |
| 465 | + expect_retry=True, |
| 466 | + ): |
| 467 | + from google.api_core import exceptions |
| 468 | + from google.cloud.firestore_v1 import _helpers |
| 469 | + |
| 470 | + if transaction is not None: |
| 471 | + expect_retry = False |
| 472 | + |
| 473 | + # Create a minimal fake GAPIC. |
| 474 | + firestore_api = mock.Mock(spec=["run_query", "_transport"]) |
| 475 | + transport = firestore_api._transport = mock.Mock(spec=["run_query"]) |
| 476 | + stub = transport.run_query = mock.create_autospec( |
| 477 | + gapic_v1.method._GapicCallable |
| 478 | + ) |
| 479 | + stub._retry = mock.Mock(spec=["_predicate"]) |
| 480 | + stub._predicate = lambda exc: True # pragma: NO COVER |
| 481 | + |
| 482 | + # Attach the fake GAPIC to a real client. |
| 483 | + client = _make_client() |
| 484 | + client._firestore_api_internal = firestore_api |
| 485 | + |
| 486 | + # Make a **real** collection reference as parent. |
| 487 | + parent = client.collection("dee") |
| 488 | + |
| 489 | + # Add a dummy response to the minimal fake GAPIC. |
| 490 | + _, expected_prefix = parent._parent_info() |
| 491 | + name = "{}/sleep".format(expected_prefix) |
| 492 | + data = {"snooze": 10} |
| 493 | + response_pb = _make_query_response(name=name, data=data) |
| 494 | + retriable_exc = exceptions.ServiceUnavailable("testing") |
| 495 | + |
| 496 | + def _stream_w_exception(*_args, **_kw): |
| 497 | + yield response_pb |
| 498 | + raise retriable_exc |
| 499 | + |
| 500 | + firestore_api.run_query.side_effect = [_stream_w_exception(), iter([])] |
| 501 | + kwargs = _helpers.make_retry_timeout_kwargs(retry, timeout) |
| 502 | + |
| 503 | + # Execute the query and check the response. |
| 504 | + query = self._make_one(parent) |
| 505 | + |
| 506 | + get_response = query.stream(transaction=transaction, **kwargs) |
| 507 | + |
| 508 | + self.assertIsInstance(get_response, types.GeneratorType) |
| 509 | + if expect_retry: |
| 510 | + returned = list(get_response) |
| 511 | + else: |
| 512 | + returned = [next(get_response)] |
| 513 | + with self.assertRaises(exceptions.ServiceUnavailable): |
| 514 | + next(get_response) |
| 515 | + |
| 516 | + self.assertEqual(len(returned), 1) |
| 517 | + snapshot = returned[0] |
| 518 | + self.assertEqual(snapshot.reference._path, ("dee", "sleep")) |
| 519 | + self.assertEqual(snapshot.to_dict(), data) |
| 520 | + |
| 521 | + # Verify the mock call. |
| 522 | + parent_path, _ = parent._parent_info() |
| 523 | + calls = firestore_api.run_query.call_args_list |
| 524 | + |
| 525 | + if expect_retry: |
| 526 | + self.assertEqual(len(calls), 2) |
| 527 | + else: |
| 528 | + self.assertEqual(len(calls), 1) |
| 529 | + |
| 530 | + if transaction is not None: |
| 531 | + expected_transaction_id = transaction.id |
| 532 | + else: |
| 533 | + expected_transaction_id = None |
| 534 | + |
| 535 | + self.assertEqual( |
| 536 | + calls[0], |
| 537 | + mock.call( |
| 538 | + request={ |
| 539 | + "parent": parent_path, |
| 540 | + "structured_query": query._to_protobuf(), |
| 541 | + "transaction": expected_transaction_id, |
| 542 | + }, |
| 543 | + metadata=client._rpc_metadata, |
| 544 | + **kwargs, |
| 545 | + ), |
| 546 | + ) |
| 547 | + |
| 548 | + if expect_retry: |
| 549 | + new_query = query.start_after(snapshot) |
| 550 | + self.assertEqual( |
| 551 | + calls[1], |
| 552 | + mock.call( |
| 553 | + request={ |
| 554 | + "parent": parent_path, |
| 555 | + "structured_query": new_query._to_protobuf(), |
| 556 | + "transaction": None, |
| 557 | + }, |
| 558 | + metadata=client._rpc_metadata, |
| 559 | + **kwargs, |
| 560 | + ), |
| 561 | + ) |
| 562 | + |
| 563 | + def test_stream_w_retriable_exc_w_defaults(self): |
| 564 | + self._stream_w_retriable_exc_helper() |
| 565 | + |
| 566 | + def test_stream_w_retriable_exc_w_retry(self): |
| 567 | + retry = mock.Mock(spec=["_predicate"]) |
| 568 | + retry._predicate = lambda exc: False |
| 569 | + self._stream_w_retriable_exc_helper(retry=retry, expect_retry=False) |
| 570 | + |
| 571 | + def test_stream_w_retriable_exc_w_transaction(self): |
| 572 | + from google.cloud.firestore_v1 import transaction |
| 573 | + |
| 574 | + txn = transaction.Transaction(client=mock.Mock(spec=[])) |
| 575 | + txn._id = b"DEADBEEF" |
| 576 | + self._stream_w_retriable_exc_helper(transaction=txn) |
| 577 | + |
459 | 578 | @mock.patch("google.cloud.firestore_v1.query.Watch", autospec=True)
|
460 | 579 | def test_on_snapshot(self, watch):
|
461 | 580 | query = self._make_one(mock.sentinel.parent)
|
|
0 commit comments