Files

125 lines
4.7 KiB
Python

"""'/' root redirect and the '/login' landing page."""
from __future__ import annotations
from uuid import uuid4
from fastapi.testclient import TestClient
from ragcore.application.auth import SessionService, SignedTokenCodec
from ragcore.application.auth.ports import AuthorizationChallenge
from ragcore.domain.auth import OidcClaims
from ragcore.domain.control import Role
from ragcore.main import create_app
from ragcore.web.auth.dependencies import get_session_codec, get_session_service
SECRET = b"0" * 32
class FakeOidcProvider:
def __init__(self, claims: OidcClaims) -> None:
self.claims = claims
async def build_authorization_challenge(self, *, redirect_uri: str) -> AuthorizationChallenge:
return AuthorizationChallenge(
redirect_url="https://idp.example.test/authorize?mock=1",
state="fixed-state-for-tests",
nonce="fixed-nonce-for-tests",
code_verifier="fixed-verifier-for-tests",
)
async def complete_login(
self, *, code: str, code_verifier: str, nonce: str, redirect_uri: str
) -> OidcClaims:
return self.claims
def _signed_in_client() -> TestClient:
provider = FakeOidcProvider(
OidcClaims(subject="alice", email="alice@example.test", groups=("ragcore-editors",))
)
service = SessionService(
provider=provider, group_roles={"ragcore-editors": Role.EDITOR}, workspace_id=uuid4()
)
codec = SignedTokenCodec(secret=SECRET)
app = create_app()
app.dependency_overrides[get_session_service] = lambda: service
app.dependency_overrides[get_session_codec] = lambda: codec
# SessionAuthenticationMiddleware reads the codec straight off app.state
# (it runs outside FastAPI's dependency-injection system), so the
# dependency override above -- which only affects the /auth/* routes --
# is not enough to make it recognize the cookie those routes just set.
app.state.auth_session_codec = codec
client = TestClient(app, follow_redirects=False)
client.get("/auth/login")
client.get("/auth/callback", params={"code": "auth-code", "state": "fixed-state-for-tests"})
return client
def test_root_redirects_unauthenticated_visitors_to_login() -> None:
client = TestClient(create_app(), follow_redirects=False)
response = client.get("/")
assert response.status_code == 302
assert response.headers["location"] == "/login"
def test_root_redirects_authenticated_visitors_to_dashboard() -> None:
client = _signed_in_client()
response = client.get("/")
assert response.status_code == 302
assert response.headers["location"] == "/admin/dashboard"
def test_login_page_shows_not_configured_when_oidc_is_absent() -> None:
client = TestClient(create_app(), follow_redirects=False)
response = client.get("/login")
assert response.status_code == 200
assert "No identity provider is configured" in response.text
assert "Sign in with SSO" not in response.text
assert "OIDC secured" not in response.text
assert "Setup required" in response.text
def test_login_page_offers_sso_when_oidc_is_configured() -> None:
app = create_app()
app.state.session_service = object()
client = TestClient(app, follow_redirects=False)
response = client.get("/login")
assert response.status_code == 200
assert "Sign in with SSO" in response.text
assert 'href="/auth/login"' in response.text
def test_login_page_introduces_the_real_ragcore_value_flow() -> None:
app = create_app()
app.state.session_service = object()
response = TestClient(app, follow_redirects=False).get("/login")
assert response.status_code == 200
assert 'data-auth-stage="ragcore"' in response.text
assert "Private knowledge. Verifiable answers." in response.text
assert 'data-rag-flow="sources-retrieval-answers"' in response.text
assert "Dense + sparse" in response.text
assert "Cited answers" in response.text
assert 'aria-describedby="sign-in-description"' in response.text
assert "https://" not in response.text
def test_login_page_declares_a_loadable_local_favicon() -> None:
client = TestClient(create_app(), follow_redirects=False)
response = client.get("/login")
favicon = client.get("/static/favicon.svg")
assert response.status_code == 200
assert '<link rel="icon" href="/static/favicon.svg" type="image/svg+xml">' in response.text
assert favicon.status_code == 200
assert favicon.headers["content-type"].startswith("image/svg+xml")
def test_login_page_redirects_an_already_authenticated_visitor() -> None:
client = _signed_in_client()
response = client.get("/login")
assert response.status_code == 302
assert response.headers["location"] == "/admin/dashboard"