import pytest @pytest.mark.asyncio async def test_deep_link_redirects_to_login_with_next(client): resp = await client.get("/admin/users") assert resp.status_code == 302 assert resp.headers["location"] == "/login?next=%2Fadmin%2Fusers" @pytest.mark.asyncio async def test_deep_link_preserves_query_string(client): resp = await client.get("/admin/users?sort=email") assert resp.status_code == 302 assert resp.headers["location"] == "/login?next=%2Fadmin%2Fusers%3Fsort%3Demail" @pytest.mark.asyncio async def test_root_redirects_anonymous_to_scopes(client): resp = await client.get("/") assert resp.status_code == 302 assert resp.headers["location"] == "/scopes" @pytest.mark.asyncio async def test_root_renders_dashboard_when_logged_in(client, make_user): await make_user("dash@example.com") resp = await client.post( "/login", data={"email": "dash@example.com", "password": "secret123"} ) assert resp.status_code == 303 resp = await client.get("/") assert resp.status_code == 200 assert "dash@example.com" in resp.text @pytest.mark.asyncio async def test_login_page_renders_next_field(client): resp = await client.get("/login?next=/scopes") assert resp.status_code == 200 assert 'name="next" value="/scopes"' in resp.text @pytest.mark.asyncio async def test_login_redirects_to_next_on_success(client, make_user): await make_user("u@example.com") resp = await client.post( "/login", data={"email": "u@example.com", "password": "secret123", "next": "/scopes"}, ) assert resp.status_code == 303 assert resp.headers["location"] == "/scopes" @pytest.mark.asyncio async def test_login_defaults_to_root_without_next(client, make_user): await make_user("u2@example.com") resp = await client.post( "/login", data={"email": "u2@example.com", "password": "secret123"} ) assert resp.status_code == 303 assert resp.headers["location"] == "/" @pytest.mark.asyncio async def test_login_rejects_unsafe_next(client, make_user): await make_user("u3@example.com") for bad in ["https://evil.com", "//evil.com", "/\\evil.com", "/login"]: resp = await client.post( "/login", data={"email": "u3@example.com", "password": "secret123", "next": bad}, ) assert resp.status_code == 303 assert resp.headers["location"] == "/", bad @pytest.mark.asyncio async def test_failed_login_preserves_next(client, make_user): await make_user("u4@example.com") resp = await client.post( "/login", data={"email": "u4@example.com", "password": "wrong", "next": "/scopes"}, ) assert resp.status_code == 401 assert 'name="next" value="/scopes"' in resp.text