approval-engine/tests/test_reconciliation.py

87 lines
4 KiB
Python
Raw Normal View History

"""Bounded source counts do not equate occurrence, acknowledgment and acceptance."""
from dataclasses import replace
from datetime import timedelta, timezone
from urllib.parse import urlencode
import pytest
from approval_engine.api import App, call
from approval_engine.auth import StaticTokenAuthenticator
from approval_engine.errors import StoreUnavailable
from approval_engine.store import EVENT_CLASSES
from tests.conftest import FROZEN, approve
def query(since=FROZEN, until=FROZEN + timedelta(seconds=1)):
return urlencode({"since": since.isoformat(), "until": until.isoformat()})
def test_occurrence_window_separates_pending_and_delayed_acknowledgments(engine, app):
engine.clock = lambda: FROZEN - timedelta(seconds=1)
approve(engine)
engine.clock = lambda: FROZEN
current = approve(engine)
engine.clock = lambda: FROZEN + timedelta(seconds=1)
engine.consume(current.id, current.binding_digest)
status, before = call(app, "GET", "/v1/cadence", query_string=query())
assert status == 200
assert before["counts"]["issuance"] == {
"committed": 1, "drained": 0, "pending": 1, "acknowledged_in_window": 0,
}
assert before["counts"]["use"]["committed"] == 0 # until is exclusive
engine.clock = lambda: FROZEN + timedelta(seconds=2)
engine.drain(lambda _: None)
_, after = call(app, "GET", "/v1/cadence", query_string=query())
assert after["counts"]["issuance"] == {
"committed": 1, "drained": 1, "pending": 0, "acknowledged_in_window": 0,
}
_, ack = call(app, "GET", "/v1/cadence", query_string=query(
FROZEN + timedelta(seconds=2), FROZEN + timedelta(seconds=3)))
assert ack["counts"]["issuance"]["acknowledged_in_window"] == 2
assert ack["counts"]["use"]["acknowledged_in_window"] == 1
assert all(c["committed"] == 0 for c in ack["counts"].values())
assert set(after["counts"]) == set(EVENT_CLASSES)
assert after["source"] == "approval-engine" and after["tenant"] == engine.tenant
assert after["window_basis"] == "created_at"
assert "acceptance-time" in after["means"]
assert "approvers" not in str(after) and current.id not in str(after)
def test_timezone_and_subsecond_bounds_are_preserved(app, engine):
approve(engine)
offset = FROZEN.astimezone(timezone(timedelta(hours=2)))
_, result = call(app, "GET", "/v1/cadence", query_string=query(offset, offset + timedelta(seconds=1)))
assert result["counts"]["issuance"]["committed"] == 1
_, result = call(app, "GET", "/v1/cadence", query_string=query(
FROZEN + timedelta(microseconds=1), FROZEN + timedelta(seconds=1)))
assert result["counts"]["issuance"]["committed"] == 0
@pytest.mark.parametrize("qs", [
"since=bad&until=bad", "since=2026-10-04&until=2026-10-05",
query(FROZEN, FROZEN), query(FROZEN + timedelta(seconds=1), FROZEN),
"since=x", "until=x", "since=&until=x", "since=x&since=y&until=z",
query() + "&tenant=other", "window=all",
])
def test_bad_or_unbounded_queries_are_refused(app, qs):
assert call(app, "GET", "/v1/cadence", query_string=qs)[0] == 422
def test_window_counts_preserve_lifetime_endpoint_and_authentication(app, engine):
approve(engine)
_, counts = call(app, "GET", "/v1/cadence")
assert counts["issuance"] == 1 and counts["form"] == "heartbeat-or-reconciliation"
assert call(app, "GET", "/v1/cadence", authorization=None, query_string=query())[0] == 401
identity = app.authenticator.identities["test-token"]
for altered in (replace(identity, scopes=frozenset({"approval:read"})),
replace(identity, tenant="tenant:other")):
other = App(engine, StaticTokenAuthenticator({"test-token": altered}))
assert call(other, "GET", "/v1/cadence", query_string=query())[0] == 403
def test_window_counts_fail_closed_on_store_failure(app, engine, monkeypatch):
def unavailable():
raise StoreUnavailable("unavailable")
monkeypatch.setattr(engine, "_conn", unavailable)
assert call(app, "GET", "/v1/cadence", query_string=query())[0] == 503