|
19 | 19 | # --------------------------------------------------------------------------- |
20 | 20 |
|
21 | 21 |
|
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) |
24 | 25 | return patch( |
25 | 26 | "testgen.mcp.permissions._compute_project_permissions", |
26 | 27 | return_value=ProjectPermissions( |
@@ -363,6 +364,34 @@ def test_create_table_group_requires_edit(db_session_mock): |
363 | 364 | ) |
364 | 365 |
|
365 | 366 |
|
| 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 | + |
366 | 395 | # --------------------------------------------------------------------------- |
367 | 396 | # update_table_group |
368 | 397 | # --------------------------------------------------------------------------- |
@@ -525,6 +554,85 @@ def test_update_table_group_delay_days_int_cast_to_str(mock_resolve, mock_tg_cls |
525 | 554 | assert tg.profiling_delay_days == "3" |
526 | 555 |
|
527 | 556 |
|
| 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 | + |
528 | 636 | # --------------------------------------------------------------------------- |
529 | 637 | # preview_table_group |
530 | 638 | # --------------------------------------------------------------------------- |
|
0 commit comments