9cd8292acc
Lint & Validate / lint (push) Has been cancelled
P2-R1: ALSA + JACK2 low-latency config (scripts, quirks, tuning) P2-R2: Carla integration (build scripts, 8ch rack config, NAM LV2 support) P2-R3: Plugin manager, categories, blacklist, NAM model support P3-R1: Mixer DSP engine (channel strip, routing matrix, bus mgr, automation) P4-R1: MIDI engine (learn mode, clock sync, HID discovery, mapping store) P4-R2: Network API (OSC server, FastAPI REST, WebSocket, auth, rate limiter) P5-R1: Touchscreen UI evaluation + main entry point docs: Audio stack, Carla integration, MIDI support, UI evaluation tests: Full test suite (292 passing)
113 lines
3.3 KiB
Python
113 lines
3.3 KiB
Python
"""Tests for API key authentication."""
|
|
|
|
import os
|
|
import pytest
|
|
|
|
from fastapi import FastAPI, Depends
|
|
from fastapi.testclient import TestClient
|
|
|
|
from src.network.auth import (
|
|
APIKeyAuth,
|
|
APIKeyHeader,
|
|
require_api_key,
|
|
API_KEY_HEADER,
|
|
)
|
|
|
|
|
|
class TestAPIKeyAuth:
|
|
"""Tests for APIKeyAuth."""
|
|
|
|
def test_validate_correct_key(self):
|
|
auth = APIKeyAuth(keys=["my-secret-key"])
|
|
assert auth.validate("my-secret-key")
|
|
|
|
def test_validate_wrong_key(self):
|
|
auth = APIKeyAuth(keys=["my-secret-key"])
|
|
assert not auth.validate("wrong-key")
|
|
|
|
def test_validate_empty_key(self):
|
|
auth = APIKeyAuth(keys=["my-secret-key"])
|
|
assert not auth.validate("")
|
|
assert not auth.validate(None)
|
|
|
|
def test_multiple_keys(self):
|
|
auth = APIKeyAuth(keys=["key-a", "key-b", "key-c"])
|
|
assert auth.validate("key-a")
|
|
assert auth.validate("key-b")
|
|
assert auth.validate("key-c")
|
|
assert not auth.validate("key-d")
|
|
assert auth.get_keys_count() == 3
|
|
|
|
def test_empty_keys_list(self):
|
|
auth = APIKeyAuth(keys=[])
|
|
assert not auth.validate("anything")
|
|
assert auth.get_keys_count() == 0
|
|
|
|
def test_key_with_whitespace(self):
|
|
auth = APIKeyAuth(keys=[" key-with-spaces "])
|
|
assert auth.validate("key-with-spaces")
|
|
|
|
def test_constant_time_comparison(self):
|
|
"""Ensure timing doesn't leak key info (basic check)."""
|
|
auth = APIKeyAuth(keys=["a" * 32])
|
|
# Should not raise
|
|
for _ in range(10):
|
|
auth.validate("b" * 32)
|
|
auth.validate("a" * 32)
|
|
|
|
def test_key_from_env(self, monkeypatch):
|
|
monkeypatch.setenv("MIXER_API_KEY", "env-secret")
|
|
auth = APIKeyAuth()
|
|
assert auth.validate("env-secret")
|
|
assert not auth.validate("wrong")
|
|
|
|
def test_multikey_from_env(self, monkeypatch):
|
|
monkeypatch.setenv("MIXER_API_KEY", "key1, key2,key3")
|
|
auth = APIKeyAuth()
|
|
assert auth.validate("key1")
|
|
assert auth.validate("key2")
|
|
assert auth.validate("key3")
|
|
assert auth.get_keys_count() == 3
|
|
|
|
|
|
class TestRequireAPIKey:
|
|
"""Tests for require_api_key FastAPI dependency."""
|
|
|
|
@pytest.fixture
|
|
def app(self):
|
|
auth = APIKeyAuth(keys=["test-key"])
|
|
app = FastAPI()
|
|
|
|
@app.get("/protected")
|
|
async def protected(_: None = Depends(require_api_key(auth))):
|
|
return {"ok": True}
|
|
|
|
@app.get("/public")
|
|
async def public():
|
|
return {"ok": True}
|
|
|
|
return app
|
|
|
|
@pytest.fixture
|
|
def client(self, app):
|
|
return TestClient(app)
|
|
|
|
def test_public_endpoint_no_key(self, client):
|
|
resp = client.get("/public")
|
|
assert resp.status_code == 200
|
|
assert resp.json() == {"ok": True}
|
|
|
|
def test_protected_no_key(self, client):
|
|
resp = client.get("/protected")
|
|
assert resp.status_code == 401
|
|
assert "Missing" in resp.json()["detail"]
|
|
|
|
def test_protected_wrong_key(self, client):
|
|
resp = client.get("/protected", headers={API_KEY_HEADER: "wrong"})
|
|
assert resp.status_code == 403
|
|
|
|
def test_protected_correct_key(self, client):
|
|
resp = client.get("/protected", headers={API_KEY_HEADER: "test-key"})
|
|
assert resp.status_code == 200
|
|
assert resp.json() == {"ok": True}
|