Files
momentum-system/tests/unit/api/test_dashboard_post_auth.py
T
momentum devandClaude Fable 5 07570fb0e4 operator-plane: fix dashboard POST NameError (import os/hmac)
Every POST endpoint (approve/reject/exit/watch) crashed with NameError:
do_POST references os.environ + hmac.compare_digest but neither was imported.
Adds the imports, a non-loopback regression test for the token gate (no token
-> routes; token set -> 401 without/with-wrong header, 200 with correct), and
plumbs DASHBOARD_TOKEN + PG_SKIP_SCHEMA=1 into both compose dashboard services
(+ .env.example docs for DASHBOARD_TOKEN / COMMAND_BUS_ENABLED).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 00:04:01 -04:00

79 lines
2.8 KiB
Python

"""POST auth + no-NameError regression for the dashboard handler (plan 007 step 1).
These drive ``DashboardHandler.do_POST`` DIRECTLY — the handler is constructed
without a socket and its ``_send_json`` is captured — so the test never binds a
loopback port (which is refused in this WSL sandbox) yet still exercises the real
token gate that used to ``NameError`` on missing ``os``/``hmac`` imports.
"""
import io
import json
import pytest
from api.main import DashboardHandler, DashboardState
class CaptureHandler(DashboardHandler):
"""DashboardHandler wired to in-memory buffers instead of a live socket."""
def __init__(self, state, path, body=b"", headers=None): # noqa: D401 — no super().__init__
self.state = state
self.path = path
self.rfile = io.BytesIO(body)
self.headers = headers or {}
self.captured = [] # list of (status, payload)
def _send_json(self, payload, status=200):
self.captured.append((status, payload))
def _state():
# add_watch needs no DB, so this routes without opening a connection.
return DashboardState("momentum", watch_inject=lambda s: None)
def _post(handler_state, path, body, headers=None):
body_bytes = json.dumps(body).encode()
hdrs = {"Content-Length": str(len(body_bytes))}
if headers:
hdrs.update(headers)
h = CaptureHandler(handler_state, path, body_bytes, hdrs)
h.do_POST()
return h.captured[-1] if h.captured else None
def test_post_without_token_configured_routes(monkeypatch):
# No DASHBOARD_TOKEN -> the token gate is skipped; the POST must ROUTE, not
# crash with NameError (the os/hmac import bug this plan fixes).
monkeypatch.delenv("DASHBOARD_TOKEN", raising=False)
status, payload = _post(_state(), "/api/watch/add", {"symbol": "aapl"})
assert status == 200
assert payload == {"ok": True, "symbol": "AAPL"}
def test_post_with_token_missing_header_401(monkeypatch):
monkeypatch.setenv("DASHBOARD_TOKEN", "s3cret")
status, payload = _post(_state(), "/api/watch/add", {"symbol": "aapl"})
assert status == 401
assert "X-Dashboard-Token" in payload["error"]
def test_post_with_token_wrong_header_401(monkeypatch):
monkeypatch.setenv("DASHBOARD_TOKEN", "s3cret")
status, _ = _post(_state(), "/api/watch/add", {"symbol": "aapl"},
headers={"X-Dashboard-Token": "nope"})
assert status == 401
def test_post_with_token_correct_header_routes(monkeypatch):
monkeypatch.setenv("DASHBOARD_TOKEN", "s3cret")
status, payload = _post(_state(), "/api/watch/add", {"symbol": "aapl"},
headers={"X-Dashboard-Token": "s3cret"})
assert status == 200
assert payload["ok"] is True
if __name__ == "__main__": # pragma: no cover
pytest.main([__file__, "-q"])