99 lines
3.7 KiB
Python
99 lines
3.7 KiB
Python
import pytest
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_health_no_auth(app_client):
|
|
r = await app_client.get("/api/v1/health")
|
|
assert r.status_code == 200
|
|
assert r.json()["status"] == "ok"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_metrics_no_auth(app_client):
|
|
r = await app_client.get("/metrics")
|
|
assert r.status_code == 200
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_missing_token_401(app_client):
|
|
r = await app_client.get("/api/v1/agents")
|
|
assert r.status_code == 401
|
|
body = r.json()
|
|
assert body["error"]["code"] == "unauthorized"
|
|
assert body["error"]["request_id"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_wrong_token_401(app_client):
|
|
r = await app_client.get("/api/v1/agents", headers={"Authorization": "Bearer nope"})
|
|
assert r.status_code == 401
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_valid_token_200(app_client, ui_auth_headers):
|
|
r = await app_client.get("/api/v1/agents", headers=ui_auth_headers)
|
|
assert r.status_code == 200
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ui_token_is_separate_from_agent_self_key(monkeypatch, database_url):
|
|
from httpx import ASGITransport, AsyncClient
|
|
|
|
from monlet_server import db as db_module
|
|
from monlet_server.main import create_app
|
|
from monlet_server.settings import reset_settings_cache
|
|
from tests._helpers import agent_headers, make_heartbeat
|
|
|
|
monkeypatch.setenv("MONLET_AUTH_TOKEN", "ui-token")
|
|
reset_settings_cache()
|
|
await db_module.dispose_engine()
|
|
app = create_app()
|
|
async with app.router.lifespan_context(app):
|
|
transport = ASGITransport(app=app)
|
|
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
|
ui = {"Authorization": "Bearer ui-token"}
|
|
agent_a = agent_headers("agent-a")
|
|
agent_b = agent_headers("agent-b")
|
|
assert (await client.get("/api/v1/agents", headers=ui)).status_code == 200
|
|
assert (await client.get("/api/v1/agents", headers=agent_a)).status_code == 401
|
|
|
|
r = await client.post(
|
|
"/api/v1/heartbeat", json=make_heartbeat("agent-a"), headers=agent_a
|
|
)
|
|
assert r.status_code == 202
|
|
r = await client.post(
|
|
"/api/v1/heartbeat", json=make_heartbeat("agent-b"), headers=agent_b
|
|
)
|
|
assert r.status_code == 202
|
|
r = await client.post("/api/v1/heartbeat", json=make_heartbeat("agent-c"), headers=ui)
|
|
assert r.status_code == 401
|
|
await db_module.dispose_engine()
|
|
monkeypatch.setenv("MONLET_AUTH_TOKEN", "test-ui-token")
|
|
reset_settings_cache()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_multi_token_overlap(monkeypatch, database_url):
|
|
"""Two active tokens both authenticate; rotation can overlap without 401s."""
|
|
from httpx import ASGITransport, AsyncClient
|
|
|
|
from monlet_server import db as db_module
|
|
from monlet_server.main import create_app
|
|
from monlet_server.settings import reset_settings_cache
|
|
|
|
monkeypatch.setenv("MONLET_AUTH_TOKEN", "old-token new-token")
|
|
reset_settings_cache()
|
|
await db_module.dispose_engine()
|
|
app = create_app()
|
|
async with app.router.lifespan_context(app):
|
|
transport = ASGITransport(app=app)
|
|
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
|
for tok in ("old-token", "new-token"):
|
|
r = await client.get("/api/v1/agents", headers={"Authorization": f"Bearer {tok}"})
|
|
assert r.status_code == 200, tok
|
|
r = await client.get("/api/v1/agents", headers={"Authorization": "Bearer revoked"})
|
|
assert r.status_code == 401
|
|
await db_module.dispose_engine()
|
|
monkeypatch.setenv("MONLET_AUTH_TOKEN", "test-ui-token")
|
|
reset_settings_cache()
|