import unittest import tempfile from pathlib import Path from unittest.mock import patch from fastapi import HTTPException from fastapi.testclient import TestClient from music_server.app import create_app from music_server.auth import require_bearer_token from music_server.services.token_service import TokenService from music_server.settings import get_settings class HealthRouteTests(unittest.TestCase): def test_healthz_returns_ok(self): client = TestClient(create_app()) response = client.get("/healthz") self.assertEqual(200, response.status_code) self.assertEqual({"status": "ok"}, response.json()) class SettingsAndAuthTests(unittest.TestCase): def test_get_settings_reflects_runtime_env_changes(self): with patch.dict( "os.environ", { "PUBLIC_MUSIC_ACCESS_TOKEN": "token-one", "CATALOG_DB_PATH": "./tmp/catalog-one.db", "PLAYER_DB_PATH": "./tmp/player-one.db", "MUSIC_SERVER_DISABLE_AUTH": "0", }, clear=False, ): first = get_settings() with patch.dict( "os.environ", { "PUBLIC_MUSIC_ACCESS_TOKEN": "token-two", "CATALOG_DB_PATH": "./tmp/catalog-two.db", "PLAYER_DB_PATH": "./tmp/player-two.db", "MUSIC_SERVER_DISABLE_AUTH": "1", }, clear=False, ): second = get_settings() self.assertEqual("token-one", first.access_token) self.assertEqual("./tmp/catalog-one.db", first.catalog_db_path) self.assertEqual("./tmp/player-one.db", first.player_db_path) self.assertEqual("token-two", second.access_token) self.assertEqual("./tmp/catalog-two.db", second.catalog_db_path) self.assertEqual("./tmp/player-two.db", second.player_db_path) self.assertFalse(first.disable_auth) self.assertTrue(second.disable_auth) def test_get_settings_reads_cache_relay_switch(self): with patch.dict( "os.environ", {"MUSIC_SERVER_CACHE_RELAY_ENABLED": "0"}, clear=False, ): disabled = get_settings() with patch.dict( "os.environ", {"MUSIC_SERVER_CACHE_RELAY_ENABLED": "1"}, clear=False, ): enabled = get_settings() self.assertFalse(disabled.cache_relay_enabled) self.assertTrue(enabled.cache_relay_enabled) def test_require_bearer_token_accepts_case_insensitive_scheme(self): with tempfile.TemporaryDirectory() as tmpdir: player_db_path = Path(tmpdir) / "player.db" service = TokenService(str(player_db_path)) issued = service.issue_token(label="health-auth") with patch.dict( "os.environ", {"PLAYER_DB_PATH": str(player_db_path)}, clear=False, ): require_bearer_token( authorization=f" bearer {issued.plaintext_token} ", x_music_client_id="client-case", x_music_client_label="Case Client", ) def test_require_bearer_token_is_bypassed_when_auth_disabled(self): with patch.dict( "os.environ", {"MUSIC_SERVER_DISABLE_AUTH": "1"}, clear=False, ): require_bearer_token( authorization=None, x_music_client_id=None, ) def test_require_bearer_token_raises_specific_error_codes(self): with self.assertRaises(HTTPException) as missing: require_bearer_token( authorization=None, x_music_client_id="client-a", ) self.assertEqual(401, missing.exception.status_code) self.assertEqual("authorization_missing", missing.exception.detail) with self.assertRaises(HTTPException) as invalid: require_bearer_token( authorization="Basic abc", x_music_client_id="client-a", ) self.assertEqual(401, invalid.exception.status_code) self.assertEqual("authorization_invalid", invalid.exception.detail) with tempfile.TemporaryDirectory() as tmpdir: player_db_path = Path(tmpdir) / "player.db" service = TokenService(str(player_db_path)) issued = service.issue_token(label="health-auth") with patch.dict( "os.environ", {"PLAYER_DB_PATH": str(player_db_path)}, clear=False, ): with self.assertRaises(HTTPException) as missing_client_id: require_bearer_token( authorization=f"Bearer {issued.plaintext_token}", x_music_client_id=None, ) self.assertEqual(401, missing_client_id.exception.status_code) self.assertEqual("client_id_missing", missing_client_id.exception.detail) with self.assertRaises(HTTPException) as wrong: require_bearer_token( authorization="Bearer not-exists", x_music_client_id="client-a", ) self.assertEqual(401, wrong.exception.status_code) self.assertEqual("token_not_found", wrong.exception.detail) if __name__ == "__main__": unittest.main()