from datetime import datetime, timezone import pytest from fastapi import FastAPI from fastapi.testclient import TestClient from ntr_fetcher.dashboard import create_dashboard_router from ntr_fetcher.db import Database from ntr_fetcher.models import Track from ntr_fetcher.websocket import AnnounceManager @pytest.fixture def db(tmp_path): database = Database(str(tmp_path / "test.db")) database.initialize() return database @pytest.fixture def manager(): return AnnounceManager() @pytest.fixture def app(db, manager): a = FastAPI() router = create_dashboard_router( db=db, manager=manager, admin_token="test-token", web_user="nick", web_password="secret", secret_key="test-secret-key", show_day=2, show_hour=22, ) a.include_router(router) return a @pytest.fixture def client(app): return TestClient(app) def _seed_show(db): week_start = datetime(2026, 3, 12, 2, 0, 0, tzinfo=timezone.utc) week_end = datetime(2026, 3, 19, 2, 0, 0, tzinfo=timezone.utc) show = db.get_or_create_show(week_start, week_end) t1 = Track(1, "Song A", "Artist A", "https://soundcloud.com/a/1", None, 180000, "cc-by", datetime(2026, 3, 14, 1, 0, 0, tzinfo=timezone.utc), "{}") db.upsert_track(t1) db.set_show_tracks(show.id, [t1.id]) return show # --- Session auth tests --- def test_dashboard_redirects_without_session(client): resp = client.get("/dashboard", follow_redirects=False) assert resp.status_code == 303 assert "/login" in resp.headers["location"] def test_login_page_renders(client): resp = client.get("/login") assert resp.status_code == 200 assert "login" in resp.text.lower() def test_login_with_valid_credentials(client): resp = client.post( "/login", data={"username": "nick", "password": "secret"}, follow_redirects=False, ) assert resp.status_code == 303 assert "/dashboard" in resp.headers["location"] assert "ntr_session" in resp.cookies def test_login_with_invalid_credentials(client): resp = client.post( "/login", data={"username": "nick", "password": "wrong"}, follow_redirects=False, ) assert resp.status_code == 200 assert "invalid" in resp.text.lower() or "incorrect" in resp.text.lower() def test_dashboard_accessible_with_session(client): client.post( "/login", data={"username": "nick", "password": "secret"}, follow_redirects=False, ) resp = client.get("/dashboard") assert resp.status_code == 200 assert "dashboard" in resp.text.lower() def test_logout_clears_session(client): client.post( "/login", data={"username": "nick", "password": "secret"}, follow_redirects=False, ) resp = client.get("/logout", follow_redirects=False) assert resp.status_code == 303 assert "/login" in resp.headers["location"] resp2 = client.get("/dashboard", follow_redirects=False) assert resp2.status_code == 303 # --- Announce endpoint tests --- def test_announce_with_session(client, db): _seed_show(db) client.post( "/login", data={"username": "nick", "password": "secret"}, follow_redirects=False, ) resp = client.post("/admin/announce", json={"show_id": 1, "position": 1}) assert resp.status_code == 200 data = resp.json() assert data["status"] == "announced" assert "Now Playing:" in data["message"] assert "Song A" in data["message"] assert "Artist A" in data["message"] def test_announce_with_bearer(client, db): _seed_show(db) resp = client.post( "/admin/announce", json={"show_id": 1, "position": 1}, headers={"Authorization": "Bearer test-token"}, ) assert resp.status_code == 200 assert "Now Playing:" in resp.json()["message"] def test_announce_without_auth(client, db): _seed_show(db) resp = client.post("/admin/announce", json={"show_id": 1, "position": 1}) assert resp.status_code == 401 def test_announce_invalid_position(client, db): _seed_show(db) resp = client.post( "/admin/announce", json={"show_id": 1, "position": 99}, headers={"Authorization": "Bearer test-token"}, ) assert resp.status_code == 404 # --- WebSocket tests --- def test_ws_subscribe_with_valid_token(app): with TestClient(app) as c: with c.websocket_connect("/ws/announce") as ws: ws.send_json({"type": "subscribe", "token": "test-token"}) data = ws.receive_json() assert data["type"] == "status" def test_ws_subscribe_with_invalid_token(app): with TestClient(app) as c: with c.websocket_connect("/ws/announce") as ws: ws.send_json({"type": "subscribe", "token": "wrong"}) with pytest.raises(Exception): ws.receive_json()