state-hub/tests/test_memo_notification.py
tegwick 49eeddbfed
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Multi-Context Image / build-and-push (push) Successful in 27s
Add a dev memo-forwarding fixture and deduplicated intake notifications.
Pose, replay, and conflict stay on an injected transport with local retry
evidence. Notification receipts keep only memo id, question, class, and
submitter, and a resolve conflict stays readable.

Assistant: grok
Assistant-Session: 01a10329-31cb-78d3-b7a3-789e1fb83139
2026-10-03 21:25:46 +02:00

152 lines
5.5 KiB
Python

"""Deduplicated informed-decision notification contract."""
from __future__ import annotations
import json
NOTICE = {
"memo_id": "infd-20261003-p01",
"question": "Accept the forwarded draft?",
"memo_class": "work-record-accept",
"submitter": "example-submitter",
}
KEY = "state-hub.delivery.0001"
async def _messages(client):
listed = await client.get("/messages/", params={"to_agent": "bernd"})
assert listed.status_code == 200, listed.text
return listed.json()
async def test_notification_is_deduplicated_and_limited_to_four_fields(client):
first = await client.post(
"/informed-decision/notifications",
json=NOTICE,
headers={"Idempotency-Key": KEY},
)
assert first.status_code == 201, first.text
body = first.json()
assert body["status"] == "accepted"
assert body["authoritative_review"] == "signed-in-overview"
assert body["delivery_id"] == KEY
raw = json.dumps({
"submitter": NOTICE["submitter"],
"memo_class": NOTICE["memo_class"],
"question": NOTICE["question"],
"memo_id": NOTICE["memo_id"],
})
second = await client.post(
"/informed-decision/notifications",
content=raw,
headers={"Idempotency-Key": KEY, "Content-Type": "application/json"},
)
assert second.status_code == 200, second.text
assert second.headers["x-statehub-idempotency-replay"] == "true"
assert second.json()["message_id"] == body["message_id"]
messages = await _messages(client)
assert len(messages) == 1
text = messages[0]["body"]
assert text.startswith("The signed-in overview is the authoritative review entry point.\n")
assert set(json.loads(text.split("\n", 1)[1])) == set(NOTICE)
assert "://" not in text
assert "packet" not in text
assert "highlight" not in text
assert messages[0]["from_agent"] == "informed-decision"
assert messages[0]["to_agent"] == "bernd"
async def test_conflict_leaves_the_original_receipt_readable(client):
created = await client.post(
"/informed-decision/notifications",
json=NOTICE,
headers={"Idempotency-Key": KEY},
)
assert created.status_code == 201, created.text
changed = dict(NOTICE, question="Decline the forwarded draft?")
conflict = await client.post(
"/informed-decision/notifications",
json=changed,
headers={"Idempotency-Key": KEY},
)
assert conflict.status_code == 409
assert conflict.json() == {"error": "notification_conflict", "delivery_id": KEY}
assert "Decline" not in conflict.text
current = await client.get(f"/informed-decision/notifications/{KEY}")
assert current.status_code == 200
assert current.json()["question"] == NOTICE["question"]
assert len(await _messages(client)) == 1
async def test_notification_refuses_extra_secret_and_link_without_echo(client):
extra = dict(NOTICE, packet="SECRET-PACKET-VALUE")
refused = await client.post(
"/informed-decision/notifications",
json=extra,
headers={"Idempotency-Key": KEY},
)
assert refused.status_code == 400
assert refused.json() == {"error": "unknown_field"}
assert "SECRET-PACKET-VALUE" not in refused.text
secret = dict(NOTICE, question="Is Bearer " + ("A" * 16) + " acceptable?")
marked = await client.post(
"/informed-decision/notifications",
json=secret,
headers={"Idempotency-Key": "state-hub.delivery.0002"},
)
assert marked.status_code == 400
assert marked.json() == {"error": "secret_marker"}
assert "AAAA" not in marked.text
linked = dict(NOTICE, question="Open https://overview.example/review?")
link = await client.post(
"/informed-decision/notifications",
json=linked,
headers={"Idempotency-Key": "state-hub.delivery.0003"},
)
assert link.status_code == 400
assert link.json() == {"error": "notification_has_link"}
assert "overview.example" not in link.text
assert await _messages(client) == []
async def test_delivery_id_and_size_are_required(client):
missing = await client.post("/informed-decision/notifications", json=NOTICE)
assert missing.status_code == 400
assert missing.json()["error"] == "delivery_id_required"
short = await client.post(
"/informed-decision/notifications",
json=NOTICE,
headers={"Idempotency-Key": "short"},
)
assert short.status_code == 400
assert short.json()["error"] == "delivery_id_invalid"
huge = await client.post(
"/informed-decision/notifications",
content=b"{" + b"x" * 9000,
headers={"Idempotency-Key": KEY, "Content-Type": "application/json"},
)
assert huge.status_code == 413
assert huge.json()["error"] == "notification_too_large"
assert b"xxx" not in huge.content
missing_receipt = await client.get("/informed-decision/notifications/" + KEY)
assert missing_receipt.status_code == 404
async def test_distinct_delivery_ids_are_distinct_notices(client):
first = await client.post(
"/informed-decision/notifications",
json=NOTICE,
headers={"Idempotency-Key": "state-hub.delivery.0001"},
)
second_body = dict(NOTICE, memo_id="infd-20261003-p02", question="Decline the other draft?")
second = await client.post(
"/informed-decision/notifications",
json=second_body,
headers={"Idempotency-Key": "state-hub.delivery.0002"},
)
assert first.status_code == 201 and second.status_code == 201
assert first.json()["message_id"] != second.json()["message_id"]
assert len(await _messages(client)) == 2