Files
govoplan-campaign/tests/test_editor_state_security.py
T
zemion c51fc180fb
Module Package Release / publish-packages (push) Successful in 12s
Release govoplan-campaign v0.1.28: stabilize saving, review and delivery recovery
2026-09-08 01:32:26 +02:00

159 lines
5.3 KiB
Python

from __future__ import annotations
from datetime import UTC, datetime
import pytest
from pydantic import ValidationError
from govoplan_campaign.backend.campaign.mail_profile_boundary import (
CampaignMailProfileBoundaryError,
campaign_editor_state_for_edit,
campaign_editor_state_with_client_update,
)
from govoplan_campaign.backend.schemas import (
CampaignVersionResponse,
CampaignVersionUpdateRequest,
)
@pytest.mark.parametrize(
"editor_state",
[
{"smtp": {"host": "smtp.example.test", "password": "secret"}},
{"transport": {"imap_password": "secret"}},
{"approval_gate": {"request_id": "forged"}},
{
"review_send": {
"build_token": "forged",
"inspection_complete": True,
"reviewed_message_keys": [],
"updated_at": "2026-07-21T00:00:00+00:00",
"updated_by_user_id": "user-1",
}
},
],
)
def test_version_updates_reject_arbitrary_or_server_owned_editor_state(
editor_state: dict[str, object],
) -> None:
with pytest.raises(ValidationError, match="unsupported or transport-owned"):
CampaignVersionUpdateRequest(editor_state=editor_state)
def test_version_response_omits_legacy_secret_bearing_editor_state() -> None:
response = CampaignVersionResponse.model_validate(
{
"id": "version-1",
"campaign_id": "campaign-1",
"version_number": 1,
"schema_version": "1.0",
"editor_state": {
"opt_ins": {"inline_guidance": True},
"smtp": {"host": "smtp.internal.example", "password": "provider-secret"},
},
"created_at": datetime.now(UTC),
"updated_at": datetime.now(UTC),
}
)
assert response.editor_state == {"opt_ins": {"inline_guidance": True}}
assert "provider-secret" not in repr(response)
assert "internal.example" not in repr(response)
def test_review_build_token_is_visible_only_in_operator_diagnostics() -> None:
value = {
"id": "version-1",
"campaign_id": "campaign-1",
"version_number": 1,
"schema_version": "1.0",
"editor_state": {
"review_send": {
"build_token": "internal-build-token",
"inspection_complete": True,
"reviewed_message_keys": ["entry-1"],
"updated_at": "2026-07-21T00:00:00+00:00",
"updated_by_user_id": "reviewer-1",
}
},
"created_at": datetime.now(UTC),
"updated_at": datetime.now(UTC),
}
public = CampaignVersionResponse.model_validate(value)
operator = CampaignVersionResponse.model_validate(
value,
context={"include_diagnostics": True},
)
assert "build_token" not in public.editor_state["review_send"]
assert operator.editor_state["review_send"]["build_token"] == "internal-build-token"
def test_fork_copy_keeps_only_client_owned_bounded_metadata() -> None:
copied = campaign_editor_state_for_edit(
{
"created_from": "minimal_campaign",
"field_overrides": {"department": False},
"review_send": {
"build_token": "build-1",
"inspection_complete": True,
"reviewed_message_keys": ["entry-1"],
"updated_at": "2026-07-21T00:00:00+00:00",
"updated_by_user_id": "reviewer-1",
},
"credentials": {"password": "legacy-secret"},
}
)
assert copied == {
"created_from": "minimal_campaign",
"field_overrides": {"department": False},
}
assert "legacy-secret" not in repr(copied)
def test_client_metadata_update_preserves_only_trusted_server_evidence() -> None:
review = {
"build_token": "server-build-token",
"inspection_complete": True,
"reviewed_message_keys": ["entry-1"],
"issue_decisions": [],
"updated_at": "2026-07-21T00:00:00+00:00",
"updated_by_user_id": "reviewer-1",
}
approval = {
"request_id": "approval-1",
"request_revision": 2,
"subject_version": "version-1",
"subject_digest": "a" * 64,
"requested_at": "2026-07-21T00:00:00+00:00",
"requested_by_user_id": "approver-1",
}
stored = {
"created_from": "minimal_campaign",
"opt_ins": {"inline_guidance": True},
"review_send": review,
"approval_gate": approval,
"credentials": {"password": "legacy-secret"},
}
updated = campaign_editor_state_with_client_update(
stored, {"opt_ins": {"inline_guidance": False}}
)
assert updated == {
"opt_ins": {"inline_guidance": False},
"review_send": review,
"approval_gate": approval,
}
assert updated["review_send"] is not review
assert updated["approval_gate"] is not approval
assert stored["opt_ins"] == {"inline_guidance": True}
assert "legacy-secret" not in repr(updated)
@pytest.mark.parametrize("server_key", ["review_send", "approval_gate"])
def test_client_metadata_update_cannot_replace_server_evidence(server_key: str) -> None:
with pytest.raises(CampaignMailProfileBoundaryError, match="unsupported"):
campaign_editor_state_with_client_update({}, {server_key: {}})