84 lines
2.9 KiB
Python
84 lines
2.9 KiB
Python
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"]
|