"""Tests that URL routing is case-insensitive. All routes pass the incoming path through lowercase_path_middleware before reaching the router, so any casing variant must resolve to the same handler. """ import pytest from httpx import ASGITransport, AsyncClient from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine from sqlalchemy.pool import StaticPool from sqlmodel import SQLModel from playlist_sync.api import app from playlist_sync.services.database import get_session @pytest.fixture() async def client(): """In-memory SQLite client — no Docker needed.""" engine = create_async_engine( "sqlite+aiosqlite:///:memory:", connect_args={"check_same_thread": False}, poolclass=StaticPool, ) async with engine.begin() as conn: await conn.run_sync(SQLModel.metadata.create_all) async def override_get_session(): async with AsyncSession(engine) as session: yield session app.dependency_overrides[get_session] = override_get_session async with AsyncClient( transport=ASGITransport(app=app), base_url="http://test" ) as ac: yield ac app.dependency_overrides.clear() await engine.dispose() async def test_health_lowercase(client): """Canonical lowercase path routes to the health handler (not 404).""" response = await client.get("/health") # 200 = healthy, 503 = degraded (Redis not running) — either means routing worked assert response.status_code in (200, 503) async def test_health_uppercase(client): """/HEALTH must resolve to the same handler as /health (not 404).""" response = await client.get("/HEALTH") assert response.status_code in (200, 503) async def test_health_mixed_case(client): """/Health must resolve to the same handler as /health (not 404).""" response = await client.get("/Health") assert response.status_code in (200, 503) async def test_playlistsync_all_lowercase(client): """All-lowercase variant works.""" response = await client.get("/playlistsync/playlists") # No auth configured in tests — expect 200, not 404 assert response.status_code == 200 async def test_playlistsync_original_casing(client): """/PlaylistSync/playlists (original .NET casing) still works.""" response = await client.get("/PlaylistSync/playlists") assert response.status_code == 200 async def test_playlistsync_all_uppercase(client): """/PLAYLISTSYNC/PLAYLISTS must not return 404.""" response = await client.get("/PLAYLISTSYNC/PLAYLISTS") assert response.status_code == 200 async def test_serviceaccounts_original_casing(client): """/serviceAccounts (original casing) still works.""" response = await client.get("/serviceAccounts") assert response.status_code == 200 async def test_serviceaccounts_all_lowercase(client): """/serviceaccounts (fully lowercase) works.""" response = await client.get("/serviceaccounts") assert response.status_code == 200 async def test_serviceaccounts_all_uppercase(client): """/SERVICEACCOUNTS must not return 404.""" response = await client.get("/SERVICEACCOUNTS") assert response.status_code == 200 async def test_unknown_path_still_404(client): """A genuinely unknown path still returns 404 (sanity check).""" response = await client.get("/this-route-does-not-exist") assert response.status_code == 404