7ab85e9ea6
prd-number-check / require-numbered-prds (pull_request) Failing after 12s
test / integration-docker (pull_request) Successful in 19s
tracker-policy-pr / check-pr (pull_request) Successful in 9s
test / unit (pull_request) Failing after 52s
lint / lint (push) Successful in 1m1s
test / coverage (pull_request) Has been skipped
Codex review on #496: - **High — ambiguous delivery no longer orphans a launched bottle.** A timeout / dropped response from the host controller is now the ambiguous BrokerUnavailableError (distinct from the definite BrokerAuthError / BrokerClientError). OrchestratorCore.launch_bottle keeps the registry row on the ambiguous case instead of deregistering — deregistering would orphan a running container with no record (reconcile reaps rows, never containers). The row is left for reconcile to reap iff the bottle is not actually live. Definite failures still roll back, so a real failure leaves no orphan row. - **Medium — the privileged endpoint bounds request bodies.** The host server rejects an oversized Content-Length with 413 before reading it, and sets a per-request socket timeout, so a caller that can merely reach the socket (no signed token) can't exhaust memory or a handler thread. Tests: ambiguous-keep vs definite-rollback in the launch path; the BrokerUnavailableError/BrokerClientError split in BrokerClient; the 413 body cap + handler error paths (driven in-thread, since daemon request threads lose coverage); and the __main__ entrypoint broker selection. Diff-coverage 98%; pyright clean; pylint 9.88. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
65 lines
2.3 KiB
Python
65 lines
2.3 KiB
Python
"""Unit: the orchestrator dev-harness entrypoint (`python -m bot_bottle.orchestrator`).
|
|
|
|
Exercises broker selection (stub / docker / http) and the fail-closed http path,
|
|
patching `make_server` so the serve loop returns instead of blocking.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import secrets
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from bot_bottle.orchestrator.__main__ import main
|
|
|
|
|
|
def _fake_server() -> MagicMock:
|
|
fake = MagicMock()
|
|
fake.server_address = ("127.0.0.1", 0)
|
|
# Break out of serve_forever immediately, exercising the try/finally.
|
|
fake.serve_forever.side_effect = KeyboardInterrupt
|
|
return fake
|
|
|
|
|
|
class TestMain(unittest.TestCase):
|
|
def _run(self, broker: str, env: dict[str, str] | None = None) -> tuple[int, MagicMock]:
|
|
fake = _fake_server()
|
|
with tempfile.TemporaryDirectory() as d:
|
|
argv = ["--db", str(Path(d) / "r.db"), "--port", "0", "--broker", broker]
|
|
with patch("bot_bottle.orchestrator.__main__.make_server", return_value=fake), \
|
|
patch.dict("os.environ", env or {}, clear=False):
|
|
if env is None:
|
|
os.environ.pop("BOT_BOTTLE_BROKER_SECRET", None)
|
|
rc = main(argv)
|
|
return rc, fake
|
|
|
|
def test_stub_broker_serves_and_closes(self) -> None:
|
|
rc, fake = self._run("stub")
|
|
self.assertEqual(0, rc)
|
|
fake.serve_forever.assert_called_once()
|
|
fake.server_close.assert_called_once()
|
|
|
|
def test_docker_broker_serves(self) -> None:
|
|
rc, _ = self._run("docker")
|
|
self.assertEqual(0, rc)
|
|
|
|
def test_http_broker_with_secret_serves(self) -> None:
|
|
rc, _ = self._run(
|
|
"http", env={"BOT_BOTTLE_BROKER_SECRET": secrets.token_bytes(16).hex()})
|
|
self.assertEqual(0, rc)
|
|
|
|
def test_http_broker_without_secret_exits(self) -> None:
|
|
# Fail-closed: --broker http with no shared secret is a usage error.
|
|
with tempfile.TemporaryDirectory() as d:
|
|
with patch.dict("os.environ", {}, clear=False):
|
|
os.environ.pop("BOT_BOTTLE_BROKER_SECRET", None)
|
|
with self.assertRaises(SystemExit):
|
|
main(["--db", str(Path(d) / "r.db"), "--broker", "http"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|