153 lines
5.5 KiB
Python
153 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
|