128 lines
4.9 KiB
Python
128 lines
4.9 KiB
Python
from __future__ import annotations
|
|
|
|
import datetime as dt
|
|
import pathlib
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
APP = pathlib.Path(__file__).resolve().parents[1] / "active-runtime" / "app"
|
|
if str(APP) not in sys.path:
|
|
sys.path.insert(0, str(APP))
|
|
|
|
from message_handoff import (
|
|
ConfidentialityScopeDenied,
|
|
DeliveryContext,
|
|
DurableHandoffStore,
|
|
HandoffFabric,
|
|
IdempotencyConflict,
|
|
MessageEnvelope,
|
|
MessageExpired,
|
|
MessageScopeMismatch,
|
|
SequenceGap,
|
|
authorize_delivery,
|
|
bind_envelope_scope,
|
|
stable_event_id,
|
|
)
|
|
|
|
|
|
def actor(**overrides):
|
|
values = dict(
|
|
tenant_id="tenant-a", user_id="user-a", project_id="project-a",
|
|
conversation_id="conversation-a", run_id="run-a",
|
|
capabilities=("task.read", "message.read"),
|
|
)
|
|
values.update(overrides)
|
|
return DeliveryContext(**values)
|
|
|
|
|
|
def envelope(**overrides):
|
|
values = dict(
|
|
source="agent-a", target="agent-b", message_type="handoff.request", payload={"value": 1},
|
|
tenant_id="tenant-a", user_id="user-a", project_id="project-a",
|
|
conversation_id="conversation-a", task_id="task-a", run_id="run-a",
|
|
sequence=1, idempotency_key="idem-1", capability_scope=("message.read",),
|
|
confidentiality_scope="private",
|
|
)
|
|
values.update(overrides)
|
|
return MessageEnvelope(**values)
|
|
|
|
|
|
def test_MESSAGE_TARGET_COLLISION_DENY():
|
|
a = stable_event_id(source="a", target="user-a", message_type="event", payload={"x": 1},
|
|
tenant_id="tenant-a", sequence=1, idempotency_key="same")
|
|
b = stable_event_id(source="a", target="user-b", message_type="event", payload={"x": 1},
|
|
tenant_id="tenant-a", sequence=1, idempotency_key="same")
|
|
assert a != b
|
|
|
|
|
|
def test_MESSAGE_SCOPE_MISMATCH_DENY():
|
|
with pytest.raises(MessageScopeMismatch, match="MESSAGE_SCOPE_MISMATCH_DENY"):
|
|
bind_envelope_scope(envelope(tenant_id="tenant-b"), actor())
|
|
|
|
|
|
def test_HANDOFF_CROSS_TENANT_DENY(tmp_path):
|
|
store = DurableHandoffStore(tmp_path / "handoff.sqlite3")
|
|
env = envelope()
|
|
store.put(env)
|
|
with pytest.raises(MessageScopeMismatch, match="MESSAGE_SCOPE_MISMATCH_DENY"):
|
|
store.get(env.envelope_id, actor=actor(tenant_id="tenant-b"))
|
|
|
|
|
|
def test_MESSAGE_CONFIDENTIALITY_SCOPE_ENFORCE():
|
|
with pytest.raises(ConfidentialityScopeDenied, match="MESSAGE_CONFIDENTIALITY_SCOPE_ENFORCE"):
|
|
authorize_delivery(actor(project_id="project-b"), envelope(confidentiality_scope="project"))
|
|
with pytest.raises(ConfidentialityScopeDenied, match="MESSAGE_CONFIDENTIALITY_SCOPE_ENFORCE"):
|
|
authorize_delivery(actor(), envelope(capability_scope=("message.read", "secret.read")))
|
|
|
|
|
|
def test_MESSAGE_IDEMPOTENCY_REPLAY(tmp_path):
|
|
store = DurableHandoffStore(tmp_path / "handoff.sqlite3")
|
|
env = envelope()
|
|
assert store.put(env)[0] == "CREATED"
|
|
status, replay = store.put(env)
|
|
assert status == "REPLAY"
|
|
assert replay.envelope_id == env.envelope_id
|
|
with pytest.raises(IdempotencyConflict, match="MESSAGE_IDEMPOTENCY_CONFLICT"):
|
|
store.put(envelope(payload={"value": 2}))
|
|
|
|
|
|
def test_MESSAGE_SEQUENCE_GAP_DETECT(tmp_path):
|
|
store = DurableHandoffStore(tmp_path / "handoff.sqlite3")
|
|
store.put(envelope(sequence=1, idempotency_key="one"))
|
|
with pytest.raises(SequenceGap, match="MESSAGE_SEQUENCE_GAP_DETECT"):
|
|
store.put(envelope(sequence=3, idempotency_key="three"))
|
|
|
|
|
|
def test_sequence_advances_and_scope_filters_pending(tmp_path):
|
|
store = DurableHandoffStore(tmp_path / "handoff.sqlite3")
|
|
store.put(envelope(sequence=1, idempotency_key="one"))
|
|
store.put(envelope(sequence=2, idempotency_key="two", payload={"value": 2}))
|
|
assert [item.sequence for item in store.pending(target="agent-b", actor=actor())] == [1, 2]
|
|
assert store.pending(target="agent-b", actor=actor(tenant_id="tenant-b")) == []
|
|
|
|
|
|
def test_expired_message_rejected(tmp_path):
|
|
expired = (dt.datetime.now(dt.timezone.utc) - dt.timedelta(seconds=1)).isoformat()
|
|
with pytest.raises(MessageExpired, match="MESSAGE_EXPIRED"):
|
|
DurableHandoffStore(tmp_path / "handoff.sqlite3").put(envelope(expires_at=expired))
|
|
|
|
|
|
def test_server_actor_binding_fills_scope_and_recomputes_identity():
|
|
public = MessageEnvelope(
|
|
source="agent-a", target="agent-b", message_type="handoff.request", payload={"value": 1},
|
|
sequence=1, idempotency_key="bind-1", confidentiality_scope="public",
|
|
)
|
|
bound = bind_envelope_scope(public, actor())
|
|
assert (bound.tenant_id, bound.user_id, bound.project_id, bound.run_id) == (
|
|
"tenant-a", "user-a", "project-a", "run-a"
|
|
)
|
|
assert bound.envelope_id != public.envelope_id
|
|
|
|
|
|
def test_handoff_fabric_publish_is_durable(tmp_path):
|
|
store = DurableHandoffStore(tmp_path / "handoff.sqlite3")
|
|
status, env = HandoffFabric(store).publish(envelope(), actor=actor())
|
|
assert status == "CREATED"
|
|
assert store.get(env.envelope_id, actor=actor()).payload == {"value": 1}
|