Spaces:
Running
Running
File size: 3,814 Bytes
4b09d2d 3fde616 4b09d2d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | """Google sign-in (Supabase PKCE): feature flag, redirect flow, callback
find-or-create, and password-login guard for Google-only accounts. The
Supabase exchange itself is mocked - no network in tests."""
import pytest
from fastapi.testclient import TestClient
from app import auth, auth_google
from app.main import app
@pytest.fixture()
def client():
with TestClient(app) as c:
yield c
@pytest.fixture()
def google_env(monkeypatch):
monkeypatch.setenv("SUPABASE_URL", "https://fakeproj.supabase.co")
monkeypatch.setenv("SUPABASE_ANON_KEY", "fake-anon-key")
monkeypatch.setenv("CCR_APP_URL", "http://testserver")
def test_unconfigured_instance_hides_and_refuses_google(client):
assert client.get("/api/auth/me").json().get("google_available") is False
assert client.get("/api/auth/google/login", follow_redirects=False).status_code == 503
def test_login_redirects_to_supabase_with_pkce(client, google_env):
me = client.get("/api/auth/me").json()
assert me["google_available"] is True
resp = client.get("/api/auth/google/login", follow_redirects=False)
assert resp.status_code == 307
loc = resp.headers["location"]
assert loc.startswith("https://fakeproj.supabase.co/auth/v1/authorize?")
assert "provider=google" in loc
assert "code_challenge=" in loc and "code_challenge_method=s256" in loc
assert "redirect_to=http%3A%2F%2Ftestserver%2Fapi%2Fauth%2Fgoogle%2Fcallback" in loc
assert auth_google.VERIFIER_COOKIE in resp.cookies
def test_app_url_ignores_pasted_whitespace(monkeypatch):
"""A dashboard-pasted CCR_APP_URL can carry a trailing newline. Untrimmed
it reaches Supabase as %0A inside redirect_to, so the redirect never
matches the allow list (production, 2026-08-17)."""
monkeypatch.setenv("CCR_APP_URL", " https://example.org/\n")
assert auth_google.app_url() == "https://example.org"
def test_callback_creates_user_and_signs_in(client, google_env, monkeypatch):
monkeypatch.setattr(
auth_google, "exchange",
lambda code, verifier: {"email": "pi@lab.edu", "name": "The PI"},
)
client.cookies.set(auth_google.VERIFIER_COOKIE, auth.sign_payload({"v": "verifier123"}))
resp = client.get("/api/auth/google/callback?code=abc", follow_redirects=False)
assert resp.status_code == 307 and resp.headers["location"] == "/"
me = client.get("/api/auth/me").json()
assert me["signed_in"] is True and me["email"] == "pi@lab.edu" and me["name"] == "The PI"
# second sign-in reuses the same account (no duplicate users)
client.cookies.set(auth_google.VERIFIER_COOKIE, auth.sign_payload({"v": "verifier456"}))
client.get("/api/auth/google/callback?code=def", follow_redirects=False)
from app.db import SessionLocal
from app.models import User
db = SessionLocal()
try:
assert db.query(User).filter_by(email="pi@lab.edu").count() == 1
finally:
db.close()
def test_callback_without_verifier_fails_safely(client, google_env):
resp = client.get("/api/auth/google/callback?code=abc", follow_redirects=False)
assert resp.status_code == 307
assert "auth_error=" in resp.headers["location"]
def test_google_only_account_cannot_password_login(client, google_env, monkeypatch):
monkeypatch.setattr(
auth_google, "exchange",
lambda code, verifier: {"email": "gonly@lab.edu", "name": "G Only"},
)
client.cookies.set(auth_google.VERIFIER_COOKIE, auth.sign_payload({"v": "v1"}))
client.get("/api/auth/google/callback?code=abc", follow_redirects=False)
client.post("/api/auth/logout")
resp = client.post(
"/api/auth/login", json={"email": "gonly@lab.edu", "password": "password123"}
)
assert resp.status_code == 401
assert "Google" in resp.json()["detail"]
|