"""Tests for optional HTTP Basic auth (src.web.auth).""" from __future__ import annotations import base64 import pytest from fastapi.testclient import TestClient from src.web.app import app @pytest.fixture def client(): return TestClient(app) def _basic(user: str, password: str) -> dict[str, str]: token = base64.b64encode(f"{user}:{password}".encode()).decode() return {"Authorization": f"Basic {token}"} def test_no_auth_when_env_unset(client, monkeypatch): """Without RMC_AUTH_USER/RMC_AUTH_PASS, all routes are public.""" monkeypatch.delenv("RMC_AUTH_USER", raising=False) monkeypatch.delenv("RMC_AUTH_PASS", raising=False) response = client.get("/api/stats") assert response.status_code == 200 def test_partial_env_does_not_enable_auth(client, monkeypatch): monkeypatch.setenv("RMC_AUTH_USER", "alice") monkeypatch.delenv("RMC_AUTH_PASS", raising=False) response = client.get("/api/stats") assert response.status_code == 200 def test_auth_required_when_env_set(client, monkeypatch): monkeypatch.setenv("RMC_AUTH_USER", "alice") monkeypatch.setenv("RMC_AUTH_PASS", "s3cret") response = client.get("/api/stats") assert response.status_code == 401 assert response.headers.get("www-authenticate", "").lower().startswith("basic") def test_auth_accepts_valid_credentials(client, monkeypatch): monkeypatch.setenv("RMC_AUTH_USER", "alice") monkeypatch.setenv("RMC_AUTH_PASS", "s3cret") response = client.get("/api/stats", headers=_basic("alice", "s3cret")) assert response.status_code == 200 def test_auth_rejects_invalid_credentials(client, monkeypatch): monkeypatch.setenv("RMC_AUTH_USER", "alice") monkeypatch.setenv("RMC_AUTH_PASS", "s3cret") response = client.get("/api/stats", headers=_basic("alice", "wrong")) assert response.status_code == 401 def test_auth_rejects_wrong_username(client, monkeypatch): monkeypatch.setenv("RMC_AUTH_USER", "alice") monkeypatch.setenv("RMC_AUTH_PASS", "s3cret") response = client.get("/api/stats", headers=_basic("bob", "s3cret")) assert response.status_code == 401