Skip to content

Commit ea30878

Browse files
committed
Add metadata-only grants for different model types
1 parent 2590a5c commit ea30878

File tree

1 file changed

+81
-79
lines changed

1 file changed

+81
-79
lines changed

tests/core/engine_adapter/integration/test_integration_postgres.py

Lines changed: 81 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -504,29 +504,24 @@ def test_grants_plan(engine_adapter: PostgresEngineAdapter, ctx: TestContext, tm
504504
assert virtual_grants == {"SELECT": [roles["analyst"]["username"]]}
505505

506506
# Update model with query change and new grants
507-
updated_model_def = """
508-
MODEL (
509-
name test_schema.grant_model,
510-
kind FULL,
511-
grants (
512-
'select' = ['test_analyst', 'test_etl_user'],
513-
'insert' = ['test_etl_user']
514-
),
515-
grants_target_layer 'all'
516-
);
517-
SELECT 1 as id, CURRENT_DATE as created_date, 'v2' as version
518-
"""
519-
520-
(tmp_path / "models" / "grant_model.sql").write_text(updated_model_def)
521-
522-
context = ctx.create_context(path=tmp_path)
507+
existing_model = context.get_model("test_schema.grant_model")
508+
from sqlglot import parse_one
509+
510+
updated_query = parse_one("SELECT 1 as id, CURRENT_DATE as created_date, 'v2' as version")
511+
context.upsert_model(
512+
existing_model,
513+
query=updated_query,
514+
grants={
515+
"select": [roles["analyst"]["username"], roles["etl_user"]["username"]],
516+
"insert": [roles["etl_user"]["username"]],
517+
},
518+
)
523519
plan_result = context.plan(auto_apply=True, no_prompts=True)
524-
assert len(plan_result.directly_modified) == 1
525520

526-
modified_snapshot_id = next(iter(plan_result.directly_modified))
527-
new_snapshot = context.get_snapshot(modified_snapshot_id.name)
528-
assert new_snapshot is not None
521+
assert len(plan_result.new_snapshots) == 1
522+
new_snapshot = plan_result.new_snapshots[0]
529523

524+
assert new_snapshot is not None
530525
new_table_name = new_snapshot.table_name()
531526
final_grants = engine_adapter._get_current_grants_config(
532527
exp.to_table(new_table_name, dialect=engine_adapter.dialect)
@@ -697,7 +692,6 @@ def test_grants_plan_incremental_model(
697692

698693
context = ctx.create_context(path=tmp_path)
699694

700-
# First plan
701695
plan_result = context.plan(
702696
"dev", start="2020-01-01", end="2020-01-01", auto_apply=True, no_prompts=True
703697
)
@@ -777,72 +771,109 @@ def test_grants_plan_clone_environment(
777771
assert dev_view_grants == prod_grants
778772

779773

774+
@pytest.mark.parametrize(
775+
"model_name,kind_config,query,extra_config,needs_seed",
776+
[
777+
(
778+
"grants_full",
779+
"FULL",
780+
"SELECT 1 as id, 'unchanged_query' as data",
781+
"",
782+
False,
783+
),
784+
(
785+
"grants_view",
786+
"VIEW",
787+
"SELECT 1 as id, 'unchanged_query' as data",
788+
"",
789+
False,
790+
),
791+
(
792+
"grants_incr_time",
793+
"INCREMENTAL_BY_TIME_RANGE (time_column event_date)",
794+
"SELECT '2025-09-01'::date as event_date, 1 as id, 'unchanged_query' as data",
795+
"start '2025-09-01',",
796+
False,
797+
),
798+
(
799+
"grants_seed",
800+
"SEED (path '../seeds/grants_seed.csv')",
801+
"",
802+
"",
803+
True,
804+
),
805+
],
806+
)
780807
def test_grants_metadata_only_changes(
781-
engine_adapter: PostgresEngineAdapter, ctx: TestContext, tmp_path: Path
808+
engine_adapter: PostgresEngineAdapter,
809+
ctx: TestContext,
810+
tmp_path: Path,
811+
model_name: str,
812+
kind_config: str,
813+
query: str,
814+
extra_config: str,
815+
needs_seed: bool,
782816
):
783817
with create_users(engine_adapter, "reader", "writer", "admin") as roles:
784818
(tmp_path / "models").mkdir(exist_ok=True)
785819

820+
if needs_seed:
821+
(tmp_path / "seeds").mkdir(exist_ok=True)
822+
csv_content = "id,data\\n1,unchanged_query"
823+
(tmp_path / "seeds" / f"{model_name}.csv").write_text(csv_content)
824+
786825
initial_model_def = f"""
787826
MODEL (
788-
name test_schema.metadata_grants_model,
789-
kind FULL,
827+
name test_schema.{model_name},
828+
kind {kind_config},
829+
{extra_config}
790830
grants (
791831
'select' = ['{roles["reader"]["username"]}']
792832
),
793833
grants_target_layer 'all'
794834
);
795-
SELECT 1 as id, 'unchanged_query' as data
835+
{query}
796836
"""
837+
(tmp_path / "models" / f"{model_name}.sql").write_text(initial_model_def)
797838

798-
(tmp_path / "models" / "metadata_grants_model.sql").write_text(initial_model_def)
799-
800-
# Create initial model with grants
801839
context = ctx.create_context(path=tmp_path)
802840
initial_plan_result = context.plan(auto_apply=True, no_prompts=True)
803841

804842
assert len(initial_plan_result.new_snapshots) == 1
805843
initial_snapshot = initial_plan_result.new_snapshots[0]
806844

807845
physical_table_name = initial_snapshot.table_name()
846+
virtual_view_name = f"test_schema.{model_name}"
847+
808848
initial_physical_grants = engine_adapter._get_current_grants_config(
809849
exp.to_table(physical_table_name, dialect=engine_adapter.dialect)
810850
)
811851
assert initial_physical_grants == {"SELECT": [roles["reader"]["username"]]}
812852

813-
virtual_view_name = f"test_schema.metadata_grants_model"
814853
initial_virtual_grants = engine_adapter._get_current_grants_config(
815854
exp.to_table(virtual_view_name, dialect=engine_adapter.dialect)
816855
)
817856
assert initial_virtual_grants == {"SELECT": [roles["reader"]["username"]]}
818857

819-
# Update grants ONLY (same SQL query, replace SELECT with writer and admin, add admin to INSERT)
820-
updated_model_def = f"""
821-
MODEL (
822-
name test_schema.metadata_grants_model,
823-
kind FULL,
824-
grants (
825-
'select' = ['{roles["writer"]["username"]}', '{roles["admin"]["username"]}'],
826-
'insert' = ['{roles["admin"]["username"]}']
827-
),
828-
grants_target_layer 'all'
829-
);
830-
SELECT 1 as id, 'unchanged_query' as data
831-
"""
832-
833-
(tmp_path / "models" / "metadata_grants_model.sql").write_text(updated_model_def)
834-
835-
context = ctx.create_context(path=tmp_path)
858+
# Metadata-only change: update grants only using upsert_model
859+
existing_model = context.get_model(f"test_schema.{model_name}")
860+
context.upsert_model(
861+
existing_model,
862+
grants={
863+
"select": [roles["writer"]["username"], roles["admin"]["username"]],
864+
"insert": [roles["admin"]["username"]],
865+
},
866+
)
836867
context.plan(auto_apply=True, no_prompts=True)
837868

838-
# Grants should be updated regardless of how the change is categorized
839-
updated_physical_grants = engine_adapter._get_current_grants_config(
840-
exp.to_table(physical_table_name, dialect=engine_adapter.dialect)
841-
)
842869
expected_grants = {
843870
"SELECT": [roles["writer"]["username"], roles["admin"]["username"]],
844871
"INSERT": [roles["admin"]["username"]],
845872
}
873+
874+
updated_physical_grants = engine_adapter._get_current_grants_config(
875+
exp.to_table(physical_table_name, dialect=engine_adapter.dialect)
876+
)
846877
assert set(updated_physical_grants.get("SELECT", [])) == set(expected_grants["SELECT"])
847878
assert updated_physical_grants.get("INSERT", []) == expected_grants["INSERT"]
848879

@@ -852,34 +883,6 @@ def test_grants_metadata_only_changes(
852883
assert set(updated_virtual_grants.get("SELECT", [])) == set(expected_grants["SELECT"])
853884
assert updated_virtual_grants.get("INSERT", []) == expected_grants["INSERT"]
854885

855-
# Test removing grants (remove INSERT, replace SELECT with reader)
856-
minimal_grants_model_def = f"""
857-
MODEL (
858-
name test_schema.metadata_grants_model,
859-
kind FULL,
860-
grants (
861-
'select' = ['{roles["reader"]["username"]}']
862-
),
863-
grants_target_layer 'all'
864-
);
865-
SELECT 1 as id, 'unchanged_query' as data
866-
"""
867-
868-
(tmp_path / "models" / "metadata_grants_model.sql").write_text(minimal_grants_model_def)
869-
870-
context = ctx.create_context(path=tmp_path)
871-
context.plan(auto_apply=True, no_prompts=True)
872-
873-
final_physical_grants = engine_adapter._get_current_grants_config(
874-
exp.to_table(physical_table_name, dialect=engine_adapter.dialect)
875-
)
876-
assert final_physical_grants == {"SELECT": [roles["reader"]["username"]]}
877-
878-
final_virtual_grants = engine_adapter._get_current_grants_config(
879-
exp.to_table(virtual_view_name, dialect=engine_adapter.dialect)
880-
)
881-
assert final_virtual_grants == {"SELECT": [roles["reader"]["username"]]}
882-
883886

884887
def _vde_dev_only_config(gateway: str, config: Config) -> None:
885888
config.virtual_environment_mode = VirtualEnvironmentMode.DEV_ONLY
@@ -974,7 +977,6 @@ def test_grants_incremental_model_with_vde_dev_only(
974977
(tmp_path / "models" / "vde_incremental_model.sql").write_text(model_def)
975978

976979
context = ctx.create_context(path=tmp_path, config_mutator=_vde_dev_only_config)
977-
978980
context.plan("prod", auto_apply=True, no_prompts=True)
979981

980982
prod_table = "test_schema.vde_incremental_model"

0 commit comments

Comments
 (0)