|
8 | 8 | from django.contrib.auth.models import User |
9 | 9 | from django.core.management import call_command |
10 | 10 | from django.core.management.base import CommandError |
| 11 | +from google.api_core.exceptions import NotFound |
11 | 12 |
|
12 | 13 | from lando.main.models.uplift import ( |
13 | 14 | RevisionUpliftJob, |
|
17 | 18 | UpliftSubmission, |
18 | 19 | ) |
19 | 20 | from lando.utils.management.commands.etl import ( |
| 21 | + BigQueryLoader, |
20 | 22 | Command, |
21 | 23 | JsonLinesLoader, |
22 | 24 | RepoTransformer, |
@@ -545,3 +547,59 @@ def test_extract_filters_out_records_before_cutoff(): |
545 | 547 | assert ( |
546 | 548 | old_assessment.id not in result_ids |
547 | 549 | ), "Records before the cutoff should be excluded." |
| 550 | + |
| 551 | + |
| 552 | +def test_wait_for_incoming_tables_succeeds_immediately(): |
| 553 | + """Should return immediately when all tables are visible.""" |
| 554 | + mock_client = MagicMock() |
| 555 | + loader = BigQueryLoader(mock_client) |
| 556 | + |
| 557 | + incoming_table = MagicMock() |
| 558 | + incoming_table.project = "proj" |
| 559 | + incoming_table.dataset_id = "ds" |
| 560 | + incoming_table.table_id = "tbl_incoming" |
| 561 | + loader.incoming_tables["key"] = incoming_table |
| 562 | + |
| 563 | + loader.wait_for_incoming_tables(retry_base_delay_s=0) |
| 564 | + |
| 565 | + assert ( |
| 566 | + mock_client.get_table.call_count == 1 |
| 567 | + ), "Should call `get_table` once per incoming table." |
| 568 | + |
| 569 | + |
| 570 | +def test_wait_for_incoming_tables_retries_on_not_found(): |
| 571 | + """Should retry when `get_table` raises `NotFound`, then succeed.""" |
| 572 | + mock_client = MagicMock() |
| 573 | + mock_client.get_table.side_effect = [ |
| 574 | + NotFound("Table not found."), |
| 575 | + MagicMock(), |
| 576 | + ] |
| 577 | + loader = BigQueryLoader(mock_client) |
| 578 | + |
| 579 | + incoming_table = MagicMock() |
| 580 | + incoming_table.project = "proj" |
| 581 | + incoming_table.dataset_id = "ds" |
| 582 | + incoming_table.table_id = "tbl_incoming" |
| 583 | + loader.incoming_tables["key"] = incoming_table |
| 584 | + |
| 585 | + loader.wait_for_incoming_tables(retry_base_delay_s=0) |
| 586 | + |
| 587 | + assert ( |
| 588 | + mock_client.get_table.call_count == 2 |
| 589 | + ), "Should have retried after `NotFound`." |
| 590 | + |
| 591 | + |
| 592 | +def test_wait_for_incoming_tables_raises_after_max_retries(): |
| 593 | + """Should raise `CommandError` when tables never become visible.""" |
| 594 | + mock_client = MagicMock() |
| 595 | + mock_client.get_table.side_effect = NotFound("Table not found.") |
| 596 | + loader = BigQueryLoader(mock_client) |
| 597 | + |
| 598 | + incoming_table = MagicMock() |
| 599 | + incoming_table.project = "proj" |
| 600 | + incoming_table.dataset_id = "ds" |
| 601 | + incoming_table.table_id = "tbl_incoming" |
| 602 | + loader.incoming_tables["key"] = incoming_table |
| 603 | + |
| 604 | + with pytest.raises(CommandError, match="Incoming tables not visible"): |
| 605 | + loader.wait_for_incoming_tables(max_retries=2, retry_base_delay_s=0) |
0 commit comments