Skip to content

Commit fd04203

Browse files
author
testgen-ci-bot
committed
Merge remote-tracking branch 'origin/enterprise' into fix/mssql-schema-case-sensitive
2 parents c40bc57 + 3f22175 commit fd04203

6 files changed

Lines changed: 135 additions & 5 deletions

File tree

testgen/mcp/tools/table_groups.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from testgen.common.models import with_database_session
1919
from testgen.common.models.connection import Connection
2020
from testgen.common.models.table_group import TableGroup
21-
from testgen.mcp.exceptions import MCPResourceNotAccessible, MCPUserError
21+
from testgen.mcp.exceptions import MCPPermissionDenied, MCPResourceNotAccessible, MCPUserError
2222
from testgen.mcp.permissions import get_project_permissions, mcp_permission
2323
from testgen.mcp.tools.common import (
2424
format_flavor_label,
@@ -33,6 +33,10 @@
3333
from testgen.utils import friendly_score
3434

3535
_DUPLICATE_NAME_MESSAGE = "A Table Group with the same name already exists."
36+
_PII_FLAG_DENIED_MESSAGE = (
37+
"Changing PII detection requires permission to view PII. "
38+
"Leave this setting unchanged or contact your administrator."
39+
)
3640
_SCHEMA_LOCKED_MESSAGE = (
3741
"Schema cannot be changed once the table group has been used. "
3842
"Delete and recreate the table group to use a different schema."
@@ -402,6 +406,13 @@ def update_table_group(
402406
):
403407
raise MCPUserError(_SCHEMA_LOCKED_MESSAGE)
404408

409+
if (
410+
profile_flag_pii is not None
411+
and profile_flag_pii != table_group.profile_flag_pii
412+
and not get_project_permissions().has_permission("view_pii", table_group.project_code)
413+
):
414+
raise MCPPermissionDenied(_PII_FLAG_DENIED_MESSAGE)
415+
405416
before = _snapshot(table_group)
406417
_apply_args_to_table_group(table_group, **supplied)
407418

testgen/ui/components/frontend/js/pages/table_group_list.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ const TableGroupList = (props) => {
120120
connections: van.derive(() => getValue(props.edit_dialog)?.connections),
121121
table_group: van.derive(() => getValue(props.edit_dialog)?.table_group),
122122
is_in_use: van.derive(() => getValue(props.edit_dialog)?.is_in_use),
123+
can_view_pii: van.derive(() => getValue(props.permissions)?.can_view_pii),
123124
table_group_preview: van.derive(() => getValue(props.edit_dialog)?.table_group_preview),
124125
result: van.derive(() => getValue(props.edit_dialog)?.result),
125126
}));

testgen/ui/static/js/components/table_group_edit_dialog.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* @property {Connection[]} connections
1515
* @property {TableGroup} table_group
1616
* @property {boolean} is_in_use
17+
* @property {boolean?} can_view_pii
1718
* @property {TableGroupPreview?} table_group_preview
1819
* @property {EditResult?} result
1920
*/
@@ -61,6 +62,7 @@ const TableGroupEditDialog = (props) => {
6162
showConnectionSelector: connections.length > 1,
6263
disableConnectionSelector: false,
6364
disableSchemaField: getValue(props.is_in_use) ?? false,
65+
disablePiiFlag: !(getValue(props.can_view_pii) ?? false),
6466
onChange: (updatedTableGroup, state) => {
6567
tableGroupState.val = updatedTableGroup;
6668
formValid.val = state.valid;

testgen/ui/views/table_groups.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,10 +539,14 @@ def on_close_edit(_params: dict) -> None:
539539
is_in_use = TableGroup.is_in_use([table_group_id])
540540

541541
edit_tg_data = get_edit_tg()
542+
can_view_pii = session.auth.user_has_permission("view_pii")
542543
add_scorecard_definition = False
543544
for key, value in edit_tg_data.items():
544545
if key == "add_scorecard_definition":
545546
add_scorecard_definition = value
547+
elif key == "profile_flag_pii" and not can_view_pii:
548+
# Users without view_pii cannot change the PII flag — keep the stored value.
549+
continue
546550
else:
547551
setattr(table_group, key, value)
548552

tests/unit/mcp/conftest.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@
55

66
from testgen.mcp.permissions import set_mcp_token, set_mcp_username
77

8-
# Fictional role matrix for tests. role_a has full access, role_c is restricted.
8+
# Fictional role matrix for tests. role_a has full access (but NOT view_pii — several
9+
# tests rely on that to exercise the no-view_pii path), role_c is restricted, and
10+
# role_d holds edit + view_pii so deny/allow pairs can be distinguished against a real
11+
# ProjectPermissions without role_a accidentally granting view_pii.
912
TEST_PERM_MATRIX = {
1013
"view": ["role_a", "role_b"],
1114
"catalog": ["role_a", "role_b", "role_c"],
12-
"edit": ["role_a"],
15+
"edit": ["role_a", "role_d"],
1316
"administer": ["role_a"],
17+
"view_pii": ["role_d"],
1418
}
1519

1620

tests/unit/mcp/test_tools_table_groups.py

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@
1919
# ---------------------------------------------------------------------------
2020

2121

22-
def _patch_perms(allowed=("demo",), memberships=None, permission="edit"):
23-
memberships = memberships or dict.fromkeys(allowed, "role_a")
22+
def _patch_perms(allowed=("demo",), memberships=None, permission="edit", role="role_a"):
23+
# role_a has edit but NOT view_pii; role_d has edit + view_pii (see conftest matrix).
24+
memberships = memberships or dict.fromkeys(allowed, role)
2425
return patch(
2526
"testgen.mcp.permissions._compute_project_permissions",
2627
return_value=ProjectPermissions(
@@ -363,6 +364,34 @@ def test_create_table_group_requires_edit(db_session_mock):
363364
)
364365

365366

367+
# ---------------------------------------------------------------------------
368+
# create_table_group — PII flag is not gated on create
369+
# ---------------------------------------------------------------------------
370+
371+
372+
@patch(f"{MODULE}.TableGroup")
373+
@patch(f"{MODULE}.resolve_connection")
374+
def test_create_table_group_pii_on_allowed_without_view_pii(mock_resolve, mock_tg_cls, db_session_mock):
375+
"""A new table group has no manually-marked PII to overwrite, so creating with the
376+
flag on is allowed even without view_pii — the gate applies only to editing it."""
377+
mock_resolve.return_value = _mock_connection()
378+
instance = _mock_table_group(profile_flag_pii=False)
379+
mock_tg_cls.return_value = instance
380+
381+
from testgen.mcp.tools.table_groups import create_table_group
382+
383+
with _patch_perms(): # role_a: edit, no view_pii
384+
create_table_group(
385+
connection_id=42,
386+
table_group_name="Sample TG",
387+
schema="public",
388+
profile_flag_pii=True,
389+
)
390+
391+
instance.save.assert_called_once()
392+
assert instance.profile_flag_pii is True
393+
394+
366395
# ---------------------------------------------------------------------------
367396
# update_table_group
368397
# ---------------------------------------------------------------------------
@@ -525,6 +554,85 @@ def test_update_table_group_delay_days_int_cast_to_str(mock_resolve, mock_tg_cls
525554
assert tg.profiling_delay_days == "3"
526555

527556

557+
# ---------------------------------------------------------------------------
558+
# update_table_group — PII flag gating (view_pii permission)
559+
# ---------------------------------------------------------------------------
560+
561+
562+
@patch(f"{MODULE}.TableGroup")
563+
@patch(f"{MODULE}.resolve_table_group")
564+
def test_update_table_group_enable_pii_denied_without_view_pii(mock_resolve, mock_tg_cls, db_session_mock):
565+
"""role_a has edit but not view_pii (real ProjectPermissions) — enabling PII is denied.
566+
567+
role_a *does* hold administer, so this also proves the gate checks view_pii
568+
specifically, not some broader permission.
569+
"""
570+
tg = _mock_table_group(profile_flag_pii=False)
571+
mock_resolve.return_value = tg
572+
mock_tg_cls.is_in_use.return_value = False
573+
574+
from testgen.mcp.tools.table_groups import update_table_group
575+
576+
with _patch_perms(role="role_a"), pytest.raises(MCPPermissionDenied):
577+
update_table_group(table_group_id=str(tg.id), profile_flag_pii=True)
578+
tg.save.assert_not_called()
579+
580+
581+
@patch(f"{MODULE}.TableGroup")
582+
@patch(f"{MODULE}.resolve_table_group")
583+
def test_update_table_group_disable_pii_denied_without_view_pii(mock_resolve, mock_tg_cls, db_session_mock):
584+
"""Change-detection mirrors the disabled checkbox — the value can't be touched either way."""
585+
tg = _mock_table_group(profile_flag_pii=True)
586+
mock_resolve.return_value = tg
587+
mock_tg_cls.is_in_use.return_value = False
588+
589+
from testgen.mcp.tools.table_groups import update_table_group
590+
591+
with _patch_perms(role="role_a"), pytest.raises(MCPPermissionDenied):
592+
update_table_group(table_group_id=str(tg.id), profile_flag_pii=False)
593+
tg.save.assert_not_called()
594+
595+
596+
@patch(f"{MODULE}.TableGroup")
597+
@patch(f"{MODULE}.resolve_table_group")
598+
def test_update_table_group_unchanged_pii_allowed_without_view_pii(mock_resolve, mock_tg_cls, db_session_mock):
599+
"""Re-sending the current PII value (as the disabled UI checkbox does) is not a change."""
600+
tg = _mock_table_group(profile_flag_pii=True, description=None)
601+
mock_resolve.return_value = tg
602+
mock_tg_cls.is_in_use.return_value = False
603+
604+
from testgen.mcp.tools.table_groups import update_table_group
605+
606+
with _patch_perms(role="role_a"):
607+
out = update_table_group(
608+
table_group_id=str(tg.id),
609+
profile_flag_pii=True,
610+
description="Edited elsewhere",
611+
)
612+
613+
tg.save.assert_called_once()
614+
assert tg.profile_flag_pii is True
615+
assert "Description" in out
616+
617+
618+
@patch(f"{MODULE}.TableGroup")
619+
@patch(f"{MODULE}.resolve_table_group")
620+
def test_update_table_group_enable_pii_allowed_with_view_pii(mock_resolve, mock_tg_cls, db_session_mock):
621+
"""role_d holds edit + view_pii (real ProjectPermissions) — enabling PII is allowed."""
622+
tg = _mock_table_group(profile_flag_pii=False)
623+
mock_resolve.return_value = tg
624+
mock_tg_cls.is_in_use.return_value = False
625+
626+
from testgen.mcp.tools.table_groups import update_table_group
627+
628+
with _patch_perms(role="role_d"):
629+
out = update_table_group(table_group_id=str(tg.id), profile_flag_pii=True)
630+
631+
tg.save.assert_called_once()
632+
assert tg.profile_flag_pii is True
633+
assert "Flag PII" in out
634+
635+
528636
# ---------------------------------------------------------------------------
529637
# preview_table_group
530638
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)