import uuid import pytest from sqlalchemy import select from app.models.user import User from app.models.account import Account from app.models.oauth_identity import OAuthIdentity async def _make_oauth_only_user(test_db, email, *, with_identity=True): """Create an OAuth-only user (password_hash=None) directly in the test DB.""" import secrets account = Account( name=f"{email}-acct", display_code=secrets.token_hex(4).upper(), ) test_db.add(account) await test_db.flush() user = User( email=email, name="OAuth User", password_hash=None, account_id=account.id, account_role="owner", ) test_db.add(user) await test_db.flush() if with_identity: test_db.add(OAuthIdentity( user_id=user.id, provider="google", provider_subject=f"google_{email}", provider_email_at_link=email, )) await test_db.commit() return user @pytest.mark.asyncio async def test_login_form_rejects_oauth_only_user_with_helpful_error(client, test_db): await _make_oauth_only_user(test_db, "oauth-only@example.com") response = await client.post( "/api/v1/auth/login", data={"username": "oauth-only@example.com", "password": "wontwork"}, ) assert response.status_code == 400 body = response.json() assert body["detail"]["error"] == "use_oauth_provider" assert "google" in body["detail"]["providers"] @pytest.mark.asyncio async def test_login_json_rejects_oauth_only_user(client, test_db): await _make_oauth_only_user(test_db, "oauth-only2@example.com") response = await client.post( "/api/v1/auth/login/json", json={"email": "oauth-only2@example.com", "password": "wontwork"}, ) assert response.status_code == 400 assert response.json()["detail"]["error"] == "use_oauth_provider" @pytest.mark.asyncio async def test_password_forgot_silent_for_oauth_only_user(client, test_db): """OAuth-only users get the generic message; no email is sent.""" await _make_oauth_only_user(test_db, "oauth-forgot@example.com", with_identity=False) from unittest.mock import AsyncMock, patch with patch("app.core.email.EmailService.send_password_reset_email", new_callable=AsyncMock) as mock_send: response = await client.post( "/api/v1/auth/password/forgot", json={"email": "oauth-forgot@example.com"}, ) assert response.status_code == 200 mock_send.assert_not_called() @pytest.mark.asyncio async def test_login_for_password_user_still_works(client, test_user): """Regression: existing password-based login must still succeed.""" response = await client.post( "/api/v1/auth/login/json", json={"email": test_user["email"], "password": test_user["password"]}, ) assert response.status_code == 200 assert response.json()["access_token"]