-
Notifications
You must be signed in to change notification settings - Fork 23
[Bugfix] _upload_to_gcs nunca pode apagar a tabela de produção #1862
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
7356fba
1fa15c6
c877949
f26cb02
f6e736f
efc4dbf
3f7d5b5
e715d7d
2ea5add
c5be8b7
37f3a6b
7f8a2f3
c8ea9ef
877616a
748576e
3f27823
c291c98
845e615
0fa3757
be93815
36070e7
fb9eeba
d8e0b3c
b460409
43da183
043a5d5
b276105
a1ed847
7875d57
5feb3ca
953a5a5
272e51d
8364992
a278957
f37d414
45333c3
c3530dc
3061a1a
86f430f
cda9077
91c431d
d6697c3
a1bfa45
b3eafa3
52fbf0b
8c4ac16
cf43f1d
65aa4ca
8ad4c99
b47d865
3558164
cbe4794
f1c2cc2
6b6c6d6
ac692c8
2d90e38
599c6f2
4149206
435eaa4
159aabc
191201f
29c79cc
0f00e25
aae051e
6dd75b7
f658078
14258f6
a3bf90e
1ba281c
829b1af
2c4afa3
ff267ff
7e83748
22c1a0e
d109772
3470d21
e6ad150
77fea5a
7854c0a
3548707
ad0134e
19e9287
b56b137
0d38ab2
612a908
dddc28d
3633d62
a5dacc3
9ba56d0
359cb72
71260e8
624328a
31f896f
c081a40
0bfaaf9
ba62500
1e158b6
2d85e94
908dfd2
f1c6c44
972bab8
50b9da0
a37d6f1
30759f2
4608db1
2ff0bb4
27d8466
f158320
804bd8c
c5d946a
8226b64
562dd09
30e0cc6
f5592ff
a1e9e56
e2fba66
435fd7e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| """Garantias de que `_upload_to_gcs` nunca toca a tabela de produção. | ||
|
|
||
| Três estragos silenciosos já aconteceram por causa do que estes testes fixam: | ||
|
|
||
| 1. `dump_mode="overwrite"` chamava `tb.delete(mode="all")`, e `all` percorre | ||
| staging E prod — apagando a tabela materializada de produção, inclusive a | ||
| partir da iteração dev do laço de ambientes. | ||
| 2. A tabela externa de staging guardava o bucket usado na criação. Como o ramo | ||
| `append` só cria a tabela quando ela não existe, um bucket errado gravado uma | ||
| vez ficava gravado para sempre, e o dbt de produção passava a ler blobs de | ||
| dev sem nenhum erro. | ||
| 3. As duas funções de sync falavam com a staging por um `bigquery.Client()` | ||
| construído à mão, que cai no ADC do pod e leva 403 — enquanto a `bd.Table` | ||
| ao lado, com as credenciais de staging da lib, lia a mesma tabela sem | ||
| problema. | ||
| """ | ||
|
|
||
| from unittest.mock import MagicMock, patch | ||
|
|
||
| from pipelines.utils.tasks import ( | ||
| _staging_client, | ||
| _sync_staging_source_uris, | ||
| _upload_to_gcs, | ||
| ) | ||
|
|
||
| STAGING = "basedosdados-staging.us_sec_edgar_staging.dicionario" | ||
|
|
||
|
|
||
| def _bd_table(uris=None): | ||
| """`bd.Table` de mentira cujo cliente de staging devolve `uris`.""" | ||
| tb = MagicMock() | ||
| tb.table_full_name = {"staging": STAGING} | ||
| table = MagicMock() | ||
| table.external_data_configuration.source_uris = uris | ||
| tb.client = {"bigquery_staging": MagicMock()} | ||
| tb.client["bigquery_staging"].get_table.return_value = table | ||
| return tb | ||
|
|
||
|
|
||
| def test_staging_client_is_the_libs_not_a_fresh_one(): | ||
| """O ponto do 403: o cliente tem de vir da `bd.Table`, não do ADC.""" | ||
| tb = _bd_table() | ||
| assert _staging_client(tb) is tb.client["bigquery_staging"] | ||
|
|
||
|
|
||
| def test_repoints_when_only_the_bucket_differs(): | ||
| tb = _bd_table(["gs://basedosdados-dev/staging/us_sec_edgar/dicionario/*"]) | ||
|
|
||
| _sync_staging_source_uris( | ||
| tb=tb, | ||
| bucket_name="basedosdados", | ||
| dataset_id="us_sec_edgar", | ||
| table_id="dicionario", | ||
| ) | ||
|
|
||
| client = tb.client["bigquery_staging"] | ||
| client.update_table.assert_called_once() | ||
| updated, fields = client.update_table.call_args[0] | ||
| assert fields == ["external_data_configuration"] | ||
| assert updated.external_data_configuration.source_uris == [ | ||
| "gs://basedosdados/staging/us_sec_edgar/dicionario/*" | ||
| ] | ||
|
|
||
|
|
||
| def test_noop_when_already_correct(): | ||
| tb = _bd_table(["gs://basedosdados/staging/us_sec_edgar/dicionario/*"]) | ||
|
|
||
| _sync_staging_source_uris( | ||
| tb=tb, | ||
| bucket_name="basedosdados", | ||
| dataset_id="us_sec_edgar", | ||
| table_id="dicionario", | ||
| ) | ||
|
|
||
| tb.client["bigquery_staging"].update_table.assert_not_called() | ||
|
|
||
|
|
||
| def test_leaves_non_conventional_uris_alone(): | ||
| """Várias URIs, ou caminho fora da convenção: avisa e não mexe.""" | ||
| tb = _bd_table( | ||
| [ | ||
| "gs://outro/caminho/custom/*", | ||
| "gs://outro/caminho/extra/*", | ||
| ] | ||
| ) | ||
|
Comment on lines
+78
to
+85
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Test the single non-conventional URI case separately. This fixture combines two rejection conditions: multiple URIs and a non-conventional path. A regression that repoints exactly one non-conventional URI could still pass. Add a case with one custom URI, and keep a separate case for multiple URIs. 🤖 Prompt for AI Agents |
||
|
|
||
| _sync_staging_source_uris( | ||
| tb=tb, | ||
| bucket_name="basedosdados", | ||
| dataset_id="us_sec_edgar", | ||
| table_id="dicionario", | ||
| ) | ||
|
|
||
| tb.client["bigquery_staging"].update_table.assert_not_called() | ||
|
|
||
|
|
||
| @patch("pipelines.utils.tasks.dump_header") | ||
| @patch("pipelines.utils.tasks.bd") | ||
| def test_overwrite_never_deletes_prod(bd_mod, dump_header_mock): | ||
| """O ponto central: `overwrite` só pode apagar staging.""" | ||
| dump_header_mock.return_value = "/tmp/header.parquet" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Remove the hardcoded Ruff reports S108 for both literals. These values only feed mocks in this test. Replace them with neutral fixture values such as Proposed fix- dump_header_mock.return_value = "/tmp/header.parquet"
+ dump_header_mock.return_value = "header.parquet"
...
- data_path="/tmp/data",
+ data_path="data",Also applies to: 107-107 🧰 Tools🪛 Ruff (0.16.1)[error] 101-101: Probable insecure usage of temporary file or directory: "/tmp/header.parquet" (S108) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
| tb = bd_mod.Table.return_value | ||
| tb.table_full_name = {"staging": STAGING} | ||
| tb.table_exists.return_value = True | ||
|
|
||
| _upload_to_gcs( | ||
| data_path="/tmp/data", | ||
| dataset_id="us_sec_edgar", | ||
| table_id="dicionario", | ||
| bucket_name="basedosdados-dev", | ||
| dump_mode="overwrite", | ||
| source_format="parquet", | ||
| ) | ||
|
|
||
| tb.delete.assert_called_once_with(mode="staging") | ||
| for call in tb.delete.call_args_list: | ||
| assert call.kwargs.get("mode") != "all" | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add the required type hints and Google-style docstrings to the new test functions.
pipelines/utils/tests/test_upload_to_gcs_safety.py#L29-L37: Annotateurisand the return value. Document the parameter and return value.pipelines/utils/tests/test_upload_to_gcs_safety.py#L40-L43: Add the-> Nonereturn annotation.pipelines/utils/tests/test_upload_to_gcs_safety.py#L46-L62: Add a docstring and the-> Nonereturn annotation.pipelines/utils/tests/test_upload_to_gcs_safety.py#L65-L75: Add a docstring and the-> Nonereturn annotation.pipelines/utils/tests/test_upload_to_gcs_safety.py#L78-L94: Add the-> Nonereturn annotation.pipelines/utils/tests/test_upload_to_gcs_safety.py#L99-L117: Annotate the mock parameters and return value. Document the mock parameters.As per coding guidelines,
**/*.pymust “add Google-Style type hints and docstrings to functions.”📍 Affects 1 file
pipelines/utils/tests/test_upload_to_gcs_safety.py#L29-L37(this comment)pipelines/utils/tests/test_upload_to_gcs_safety.py#L40-L43pipelines/utils/tests/test_upload_to_gcs_safety.py#L46-L62pipelines/utils/tests/test_upload_to_gcs_safety.py#L65-L75pipelines/utils/tests/test_upload_to_gcs_safety.py#L78-L94pipelines/utils/tests/test_upload_to_gcs_safety.py#L99-L117🤖 Prompt for AI Agents
Source: Coding guidelines