ACTIVITY-WP-0043: owner-return pickup adapter (STATE-WP-0094-T03 owner side)
Disabled reviewed definition, pinned profile/grant, server-side unique submission adapter with receipts, and opt-in activation plan. No schedule, deployment or dispatch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 671493@bnt-lap001 Assistant-Session: 60d7b11f-78f9-4731-9367-918511b2762d
This commit is contained in:
parent
ab10835a0c
commit
5a9012453c
5 changed files with 541 additions and 0 deletions
159
tests/test_owner_return_pickup.py
Normal file
159
tests/test_owner_return_pickup.py
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
"""Tests for the owner-return pickup adapter (ACTIVITY-WP-0043)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from activity_core import owner_return_pickup as orp
|
||||
from activity_core.definition_parser import parse_file
|
||||
from activity_core.sync_activity_definitions import _definition_uuid
|
||||
|
||||
REPO, TASK = "ops-warden", "WARDEN-WP-0041-T03"
|
||||
MSG = "11111111-2222-3333-4444-555555555555"
|
||||
DEF_PATH = Path(__file__).parent.parent / "activity-definitions" / "owner-return-pickup.md"
|
||||
|
||||
|
||||
def _candidate(**over):
|
||||
c = {
|
||||
"target_repo": REPO, "task_record_id": TASK, "message_id": MSG,
|
||||
"activity_definition_id": orp.ACTIVITY_DEFINITION_ID,
|
||||
"triggering_event_id": orp.owner_return_event_id(REPO, TASK, MSG),
|
||||
"disposition": "reviewable", "stop_reason": None, "ops_run_id": None,
|
||||
}
|
||||
c.update(over)
|
||||
return c
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def enabled(monkeypatch):
|
||||
monkeypatch.setenv(orp.ENABLE_ENV, "true")
|
||||
monkeypatch.delenv("OPS_RUN_QUEUE_ENABLED", raising=False)
|
||||
|
||||
|
||||
def _session(insert_id, existing=None):
|
||||
session = MagicMock()
|
||||
ins = MagicMock(); ins.scalar_one_or_none.return_value = insert_id
|
||||
sel = MagicMock(); sel.first.return_value = existing
|
||||
session.execute = AsyncMock(side_effect=[ins, sel])
|
||||
return session
|
||||
|
||||
|
||||
def test_identity_matches_sync_derivation():
|
||||
assert orp.ACTIVITY_DEFINITION_ID == str(_definition_uuid("owner-return-pickup"))
|
||||
|
||||
|
||||
def test_definition_source_is_disabled_and_inert():
|
||||
d = parse_file(DEF_PATH)
|
||||
assert d.id == "owner-return-pickup"
|
||||
assert d.enabled is False
|
||||
text = DEF_PATH.read_text()
|
||||
assert "enabled: false" in text and 'condition: "False"' in text
|
||||
assert orp.HARNESS_PROFILE_REF in text
|
||||
|
||||
|
||||
def test_event_id_matches_state_hub_planner_encoding():
|
||||
# Reference value: json.dumps([repo, task, msg], separators=(",", ":")) sha256.
|
||||
import hashlib, json
|
||||
raw = json.dumps([REPO, TASK, MSG], separators=(",", ":")).encode()
|
||||
assert orp.owner_return_event_id(REPO, TASK, MSG) == (
|
||||
"owner-return:" + hashlib.sha256(raw).hexdigest()
|
||||
)
|
||||
|
||||
|
||||
def test_grant_is_local_only():
|
||||
assert orp.REPOSITORY_GRANT["publish"] is False
|
||||
assert orp.REPOSITORY_GRANT["allowed_paths"] == ["workplans"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("over", [
|
||||
{"triggering_event_id": "owner-return:" + "0" * 64},
|
||||
{"message_id": "not-a-uuid"},
|
||||
{"activity_definition_id": str(uuid.uuid4())},
|
||||
{"target_repo": "Bad Repo"},
|
||||
{"task_record_id": ""},
|
||||
])
|
||||
def test_binding_rejects_mismatch(over):
|
||||
with pytest.raises(orp.OwnerReturnError):
|
||||
orp.OwnerReturnBinding.from_candidate(_candidate(**over))
|
||||
|
||||
|
||||
def test_spec_key_is_stable():
|
||||
b = orp.OwnerReturnBinding.from_candidate(_candidate())
|
||||
spec = b.task_spec()
|
||||
assert spec.source_id == f"{REPO}/{TASK}"
|
||||
assert spec.triggering_event_id == b.triggering_event_id
|
||||
assert spec.activity_definition_id == orp.ACTIVITY_DEFINITION_ID
|
||||
|
||||
|
||||
async def test_refuses_when_not_enabled(monkeypatch):
|
||||
monkeypatch.delenv(orp.ENABLE_ENV, raising=False)
|
||||
session = _session(None)
|
||||
with pytest.raises(orp.OwnerReturnError):
|
||||
await orp.submit_owner_returns(session, [_candidate()], collection_complete=True)
|
||||
session.execute.assert_not_called()
|
||||
|
||||
|
||||
async def test_refuses_when_queue_disabled(enabled, monkeypatch):
|
||||
monkeypatch.setenv("OPS_RUN_QUEUE_ENABLED", "false")
|
||||
with pytest.raises(orp.OwnerReturnError):
|
||||
await orp.submit_owner_returns(_session(None), [_candidate()], collection_complete=True)
|
||||
|
||||
|
||||
async def test_refuses_incomplete_collection(enabled):
|
||||
session = _session(None)
|
||||
with pytest.raises(orp.OwnerReturnError, match="paginated"):
|
||||
await orp.submit_owner_returns(session, [_candidate()], collection_complete=False)
|
||||
session.execute.assert_not_called()
|
||||
|
||||
|
||||
async def test_invalid_candidate_aborts_before_any_write(enabled):
|
||||
session = _session(None)
|
||||
bad = _candidate(triggering_event_id="owner-return:" + "f" * 64)
|
||||
with pytest.raises(orp.OwnerReturnError):
|
||||
await orp.submit_owner_returns(session, [_candidate(), bad], collection_complete=True)
|
||||
session.execute.assert_not_called()
|
||||
|
||||
|
||||
async def test_inserted_receipt(enabled):
|
||||
rid = uuid.uuid4()
|
||||
session = _session(rid)
|
||||
[r] = await orp.submit_owner_returns(session, [_candidate()], collection_complete=True)
|
||||
assert r["status"] == "inserted" and r["ops_run_id"] == str(rid)
|
||||
assert r["idempotency_key"] == (
|
||||
f"{orp.ACTIVITY_DEFINITION_ID}:{REPO}/{TASK}:{orp.owner_return_event_id(REPO, TASK, MSG)}"
|
||||
)
|
||||
stmt = session.execute.await_args_list[0].args[0]
|
||||
from sqlalchemy.dialects import postgresql
|
||||
sql = str(stmt.compile(dialect=postgresql.dialect())).upper()
|
||||
assert "ON CONFLICT (IDEMPOTENCY_KEY) DO NOTHING" in sql
|
||||
assert session.execute.await_count == 1
|
||||
|
||||
|
||||
async def test_duplicate_reports_existing_row(enabled):
|
||||
existing_id = uuid.uuid4()
|
||||
session = _session(None, existing=(existing_id, "succeeded"))
|
||||
[r] = await orp.submit_owner_returns(session, [_candidate()], collection_complete=True)
|
||||
assert r["status"] == "duplicate"
|
||||
assert r["ops_run_id"] == str(existing_id) and r["state"] == "succeeded"
|
||||
|
||||
|
||||
async def test_duplicate_without_row_raises(enabled):
|
||||
with pytest.raises(orp.OwnerReturnError):
|
||||
await orp.submit_owner_returns(_session(None, existing=None), [_candidate()],
|
||||
collection_complete=True)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("over", [
|
||||
{"disposition": "stopped", "stop_reason": "needs_human"},
|
||||
{"disposition": "pending", "ops_run_id": str(uuid.uuid4())},
|
||||
{"disposition": "completed"},
|
||||
])
|
||||
async def test_non_reviewable_is_skipped_without_write(enabled, over):
|
||||
session = _session(None)
|
||||
[r] = await orp.submit_owner_returns(session, [_candidate(**over)], collection_complete=True)
|
||||
assert r["status"] == "skipped"
|
||||
session.execute.assert_not_called()
|
||||
Loading…
Add table
Add a link
Reference in a new issue