from api.edge.outbox import OutboxStore, PayloadRejected from api.services.write_idempotency import route_class_for def test_route_classifier_matches_safe_writes(): assert route_class_for("POST", "/progress/") == "append" assert route_class_for("PATCH", "/tasks/abc") == "replace" assert route_class_for("DELETE", "/tasks/abc") is None def test_outbox_scrubs_secret_fields_and_tracks_status(tmp_path): store = OutboxStore(tmp_path / "outbox.sqlite3") envelope = store.enqueue( method="POST", path="/progress/", body={"summary": "offline", "password": "secret", "tokens_in": 12}, source_agent="pytest", source_host="host-a", ) assert envelope.status == "queued" assert envelope.route_class == "append" assert envelope.body["password"] == "[redacted]" assert envelope.body["tokens_in"] == 12 assert store.summary()["pending_count"] == 1 store.mark_acked(envelope.id, response_status=201, response_body={"id": "central"}) acked = store.get(envelope.id) assert acked.status == "acked" assert acked.response_body == {"id": "central"} assert store.summary()["pending_count"] == 0 def test_outbox_rejects_non_queueable_routes(tmp_path): store = OutboxStore(tmp_path / "outbox.sqlite3") try: store.enqueue(method="DELETE", path="/tasks/abc", body={}) except PayloadRejected as exc: assert "not queueable" in str(exc) else: raise AssertionError("DELETE should not be queueable") def test_replace_writes_coalesce_superseded_queued_envelopes(tmp_path): store = OutboxStore(tmp_path / "outbox.sqlite3") first = store.enqueue(method="PATCH", path="/tasks/task-1", body={"status": "progress"}) second = store.enqueue(method="PATCH", path="/tasks/task-1", body={"status": "done"}) assert store.get(first.id).status == "cancelled" assert store.get(second.id).status == "queued" assert len(store.due()) == 1