Skip to content

Commit 3d731ee

Browse files
authored
Merge pull request #823 from Benjamin-Knight/fix/denies-test-principal-isolation
test(denies): give each xdist worker its own deny principal
2 parents 427fd05 + bea57c7 commit 3d731ee

1 file changed

Lines changed: 44 additions & 11 deletions

File tree

tests/functional/adapter/mssql/test_denies.py

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,35 @@
1010
principal that holds a schema-level GRANT.
1111
"""
1212

13+
import os
14+
1315
import pytest
1416

1517
from dbt.tests.util import get_connection, run_dbt, run_dbt_and_capture
1618

1719
# A login-less database user used as the deny target across the suite.
18-
DENY_PRINCIPAL = "dbt_deny_reader"
20+
#
21+
# The name is per-xdist-worker, and that is load-bearing rather than cosmetic. A
22+
# database principal is database-scoped: unlike `project.test_schema`, it is NOT
23+
# isolated per test. The suite runs under `pytest -n auto` (see the Makefile),
24+
# which spreads these classes across worker processes sharing one TestDB, so a
25+
# single shared name lets the first class to finish drop the user out from under
26+
# every class still running — taking its sys.database_permissions rows with it.
27+
# apply_denies is deliberately warn-and-skip on a missing principal, so the
28+
# rebuild does not fail; the DENY just silently never lands and the assertions
29+
# see an empty set. Classes on the same worker run sequentially, so a per-worker
30+
# name is enough to make setup and teardown non-overlapping.
31+
DENY_PRINCIPAL = "dbt_deny_reader_{}".format(os.environ.get("PYTEST_XDIST_WORKER", "main"))
32+
33+
# Placeholder the model templates below carry in their `denies` config, swapped
34+
# for DENY_PRINCIPAL at import. str.format/f-strings are unusable on these: they
35+
# are Jinja, and `{{ config(...) }}` would be eaten as an escaped brace.
36+
PRINCIPAL_PLACEHOLDER = "__DENY_PRINCIPAL__"
37+
38+
39+
def with_principal(sql):
40+
"""Substitute the per-worker principal name into a model template."""
41+
return sql.replace(PRINCIPAL_PLACEHOLDER, DENY_PRINCIPAL)
1942

2043

2144
# ---------------------------------------------------------------------------
@@ -65,10 +88,12 @@ def deny_principal(project):
6588
# table materialization
6689
# ---------------------------------------------------------------------------
6790

68-
table_deny_sql = """
69-
{{ config(materialized="table", denies={"select": ["dbt_deny_reader"]}) }}
91+
table_deny_sql = with_principal(
92+
"""
93+
{{ config(materialized="table", denies={"select": ["__DENY_PRINCIPAL__"]}) }}
7094
select 1 as id, cast('secret' as varchar(50)) as ssn
7195
"""
96+
)
7297

7398

7499
class TestTableDenies:
@@ -115,10 +140,12 @@ def test_deny_is_enforced_against_a_schema_grant(self, project):
115140
# every run, so an object-level DENY is lost most often here.
116141
# ---------------------------------------------------------------------------
117142

118-
view_deny_sql = """
119-
{{ config(materialized="view", denies={"select": ["dbt_deny_reader"]}) }}
143+
view_deny_sql = with_principal(
144+
"""
145+
{{ config(materialized="view", denies={"select": ["__DENY_PRINCIPAL__"]}) }}
120146
select 1 as id, cast('secret' as varchar(50)) as ssn
121147
"""
148+
)
122149

123150

124151
class TestViewDenies:
@@ -140,11 +167,13 @@ def test_deny_survives_ordinary_view_rebuild(self, project):
140167
# incremental materialization — append and full-refresh paths
141168
# ---------------------------------------------------------------------------
142169

143-
incremental_deny_sql = """
144-
{{ config(materialized="incremental", denies={"select": ["dbt_deny_reader"]}) }}
170+
incremental_deny_sql = with_principal(
171+
"""
172+
{{ config(materialized="incremental", denies={"select": ["__DENY_PRINCIPAL__"]}) }}
145173
select 1 as id
146174
{% if is_incremental() %}where 1 = 0{% endif %}
147175
"""
176+
)
148177

149178

150179
class TestIncrementalDenies:
@@ -178,17 +207,19 @@ def test_deny_survives_append_and_full_refresh(self, project):
178207
select 1 as id, cast('Smith' as varchar(50)) as surname
179208
"""
180209

181-
denied_snapshot_sql = """
210+
denied_snapshot_sql = with_principal(
211+
"""
182212
{% snapshot denied_snapshot %}
183213
{{ config(
184214
unique_key='id',
185215
strategy='check',
186216
check_cols=['surname'],
187-
denies={'select': ['dbt_deny_reader']}
217+
denies={'select': ['__DENY_PRINCIPAL__']}
188218
) }}
189219
select * from {{ ref('snap_source') }}
190220
{% endsnapshot %}
191221
"""
222+
)
192223

193224

194225
class TestSnapshotDenies:
@@ -264,13 +295,15 @@ def test_absent_principal_warns_and_run_succeeds(self, project):
264295
# unsupported privilege: warn and skip rather than taking down the run
265296
# ---------------------------------------------------------------------------
266297

267-
unsupported_privilege_sql = """
298+
unsupported_privilege_sql = with_principal(
299+
"""
268300
{{ config(
269301
materialized="table",
270-
denies={"execute": ["dbt_deny_reader"], "select": ["dbt_deny_reader"]}
302+
denies={"execute": ["__DENY_PRINCIPAL__"], "select": ["__DENY_PRINCIPAL__"]}
271303
) }}
272304
select 1 as id
273305
"""
306+
)
274307

275308

276309
class TestUnsupportedPrivilege:

0 commit comments

Comments
 (0)