Files
resolutionflow/backend/tests/test_oauth_callbacks.py
2026-05-06 19:14:30 -04:00

121 lines
4.3 KiB
Python

import uuid
import pytest
from unittest.mock import patch
from sqlalchemy import select
from app.models.user import User
from app.models.oauth_identity import OAuthIdentity
from app.models.subscription import Subscription
from app.services.oauth_providers import OAuthProfile
@pytest.mark.asyncio
async def test_google_callback_creates_user_account_subscription(
client, test_db, monkeypatch
):
"""Brand-new user via Google OAuth -> User + Account + Subscription + OAuthIdentity."""
from app.core.config import settings
monkeypatch.setattr(settings, "GOOGLE_CLIENT_ID", "client_dummy")
monkeypatch.setattr(settings, "GOOGLE_CLIENT_SECRET", "secret_dummy")
profile = OAuthProfile(
provider_subject="google_subject_123",
email="newuser@example.com",
name="New User",
)
with patch("app.api.endpoints.oauth.google_exchange_code", return_value=profile):
response = await client.post(
"/api/v1/auth/google/callback", json={"code": "auth_code_xyz"}
)
assert response.status_code == 200, response.json()
body = response.json()
assert body["is_new_user"] is True
assert body["access_token"]
user = (await test_db.execute(
select(User).where(User.email == "newuser@example.com")
)).scalar_one()
assert user.password_hash is None
assert user.email_verified_at is not None
identity = (await test_db.execute(
select(OAuthIdentity).where(OAuthIdentity.user_id == user.id)
)).scalar_one()
assert identity.provider == "google"
assert identity.provider_subject == "google_subject_123"
sub = (await test_db.execute(
select(Subscription).where(Subscription.account_id == user.account_id)
)).scalar_one()
assert sub.status == "trialing"
assert sub.plan == "pro"
@pytest.mark.asyncio
async def test_google_callback_existing_user_is_idempotent(
client, test_db, test_user, monkeypatch
):
"""When test_user's email is already registered, OAuth links + returns the
same user. Two calls with same provider_subject must not duplicate
OAuthIdentity rows."""
from app.core.config import settings
monkeypatch.setattr(settings, "GOOGLE_CLIENT_ID", "client_dummy")
monkeypatch.setattr(settings, "GOOGLE_CLIENT_SECRET", "secret_dummy")
user_id = uuid.UUID(test_user["user_data"]["id"])
email = test_user["email"]
name = test_user["user_data"]["name"]
profile = OAuthProfile(
provider_subject="google_subject_456",
email=email,
name=name,
)
with patch("app.api.endpoints.oauth.google_exchange_code", return_value=profile):
r1 = await client.post("/api/v1/auth/google/callback", json={"code": "x"})
r2 = await client.post("/api/v1/auth/google/callback", json={"code": "x"})
assert r1.status_code == 200
assert r2.status_code == 200
assert r1.json()["is_new_user"] is False
assert r2.json()["is_new_user"] is False
identities = (await test_db.execute(
select(OAuthIdentity).where(OAuthIdentity.user_id == user_id)
)).scalars().all()
assert len(identities) == 1
@pytest.mark.asyncio
async def test_google_callback_503_when_unconfigured(client, monkeypatch):
from app.core.config import settings
monkeypatch.setattr(settings, "GOOGLE_CLIENT_ID", None)
response = await client.post(
"/api/v1/auth/google/callback", json={"code": "x"}
)
assert response.status_code == 503
@pytest.mark.asyncio
async def test_microsoft_callback_creates_user(client, test_db, monkeypatch):
from app.core.config import settings
monkeypatch.setattr(settings, "MS_CLIENT_ID", "client_dummy")
monkeypatch.setattr(settings, "MS_CLIENT_SECRET", "secret_dummy")
profile = OAuthProfile(
provider_subject="ms_subject_789",
email="msuser@example.com",
name="MS User",
)
with patch("app.api.endpoints.oauth.microsoft_exchange_code", return_value=profile):
response = await client.post(
"/api/v1/auth/microsoft/callback", json={"code": "auth_code"}
)
assert response.status_code == 200, response.json()
user = (await test_db.execute(
select(User).where(User.email == "msuser@example.com")
)).scalar_one()
identity = (await test_db.execute(
select(OAuthIdentity).where(OAuthIdentity.user_id == user.id)
)).scalar_one()
assert identity.provider == "microsoft"