1010principal that holds a schema-level GRANT.
1111"""
1212
13+ import os
14+
1315import pytest
1416
1517from 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__"]}) }}
7094select 1 as id, cast('secret' as varchar(50)) as ssn
7195"""
96+ )
7297
7398
7499class 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__"]}) }}
120146select 1 as id, cast('secret' as varchar(50)) as ssn
121147"""
148+ )
122149
123150
124151class 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__"]}) }}
145173select 1 as id
146174{% if is_incremental() %}where 1 = 0{% endif %}
147175"""
176+ )
148177
149178
150179class TestIncrementalDenies :
@@ -178,17 +207,19 @@ def test_deny_survives_append_and_full_refresh(self, project):
178207select 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) }}
189219select * from {{ ref('snap_source') }}
190220{% endsnapshot %}
191221"""
222+ )
192223
193224
194225class 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) }}
272304select 1 as id
273305"""
306+ )
274307
275308
276309class TestUnsupportedPrivilege :
0 commit comments