agentcache / tests /test_routes_auth_matrix.py
Yash030's picture
refactor: consolidate route auth + service accessors (closes #34)
fce12d3
Raw
History Blame Contribute Delete
5.11 kB
"""
Auth matrix — every registered HTTP rule in the Flask app.
For each rule this test asserts:
1. 401 when a secret is set and no token is provided
2. non-401 when a valid token is provided
3. non-401 when no secret is configured (auth disabled mode)
Rules explicitly marked in ``UNPROTECTED_PATHS`` are expected to skip auth
even when the secret is set (livez, health, auth.md, static viewer routes).
Rules that don't take our synthetic empty payload cleanly (e.g. WebSocket,
POST endpoints requiring specific fields) may 4xx/5xx on the *body* — that's
still not a 401, which is all this matrix cares about.
"""
from __future__ import annotations
import pytest
import agentcache.app as app_mod
from agentcache.app import create_app
UNPROTECTED_PATHS = {
"/auth.md",
"/agentcache/livez",
"/agentmemory/livez",
"/agentcache/health",
"/agentmemory/health",
"/",
"/viewer",
"/agentcache/viewer",
"/agentmemory/viewer",
"/favicon.svg",
}
def _reset_app_globals() -> None:
app_mod.kv = None
app_mod.search_service = None
app_mod.observation_store = None
def _iter_http_rules(app):
"""Yield (method, path) for every non-static HTTP rule, one method per row."""
for rule in app.url_map.iter_rules():
# skip rules with URL parameters — we can't synthesise a valid one
if "<" in rule.rule:
continue
# skip static asset endpoint
if rule.endpoint == "static":
continue
# skip WebSocket rules — they can't be exercised via HTTP test client
if rule.rule.startswith("/stream/"):
continue
methods = (rule.methods or set()) - {"HEAD", "OPTIONS"}
for method in sorted(methods):
yield method, rule.rule
@pytest.fixture
def secured_app(tmp_path, monkeypatch):
monkeypatch.setenv("AGENTCACHE_DB_PATH", str(tmp_path / "matrix_secured.db"))
monkeypatch.setenv("AGENTCACHE_SECRET", "matrix-secret")
_reset_app_globals()
return create_app()
@pytest.fixture
def open_app(tmp_path, monkeypatch):
monkeypatch.setenv("AGENTCACHE_DB_PATH", str(tmp_path / "matrix_open.db"))
monkeypatch.delenv("AGENTCACHE_SECRET", raising=False)
monkeypatch.delenv("AGENTMEMORY_SECRET", raising=False)
_reset_app_globals()
return create_app()
def _invoke(client, method: str, path: str, headers=None):
fn = getattr(client, method.lower())
if method in ("POST", "PUT", "PATCH", "DELETE"):
return fn(path, json={}, headers=headers or {})
return fn(path, headers=headers or {})
def test_every_route_requires_auth_when_secret_is_set(secured_app):
"""Protected routes 401 without a token; unprotected routes pass through."""
client = secured_app.test_client()
failures = []
for method, path in _iter_http_rules(secured_app):
res = _invoke(client, method, path)
if path in UNPROTECTED_PATHS:
if res.status_code == 401:
failures.append(
f"{method} {path} unexpectedly required auth (401)"
)
else:
if res.status_code != 401:
failures.append(
f"{method} {path} did not require auth (got {res.status_code})"
)
assert not failures, "auth-matrix mismatches:\n " + "\n ".join(failures)
def test_every_route_accepts_valid_token(secured_app):
"""With a valid Bearer token, no route returns 401."""
client = secured_app.test_client()
headers = {"Authorization": "Bearer matrix-secret"}
failures = []
for method, path in _iter_http_rules(secured_app):
res = _invoke(client, method, path, headers=headers)
if res.status_code == 401:
failures.append(f"{method} {path} rejected a valid token (401)")
assert not failures, "rejected valid token:\n " + "\n ".join(failures)
def test_every_route_open_when_no_secret_configured(open_app):
"""When no secret env var is set, every route passes auth (no 401 anywhere)."""
client = open_app.test_client()
failures = []
for method, path in _iter_http_rules(open_app):
res = _invoke(client, method, path)
if res.status_code == 401:
failures.append(
f"{method} {path} returned 401 in open (no-secret) mode"
)
assert not failures, "unexpected 401s in open mode:\n " + "\n ".join(failures)
def test_every_route_rejects_invalid_token(secured_app):
"""With a wrong Bearer token, protected routes must still 401."""
client = secured_app.test_client()
bad_headers = {"Authorization": "Bearer not-the-secret"}
failures = []
for method, path in _iter_http_rules(secured_app):
if path in UNPROTECTED_PATHS:
continue
res = _invoke(client, method, path, headers=bad_headers)
if res.status_code != 401:
failures.append(
f"{method} {path} accepted a bad token (got {res.status_code})"
)
assert not failures, "invalid-token acceptance:\n " + "\n ".join(failures)