refactor: update Clerk authentication integration and improve organization handling
This commit is contained in:
165
backend/tests/test_auth_claims.py
Normal file
165
backend/tests/test_auth_claims.py
Normal file
@@ -0,0 +1,165 @@
|
||||
# ruff: noqa: SLF001
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from types import SimpleNamespace
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from app.core import auth
|
||||
from app.models.users import User
|
||||
|
||||
|
||||
@dataclass
|
||||
class _FakeSession:
|
||||
added: list[Any] = field(default_factory=list)
|
||||
committed: int = 0
|
||||
refreshed: list[Any] = field(default_factory=list)
|
||||
|
||||
def add(self, value: Any) -> None:
|
||||
self.added.append(value)
|
||||
|
||||
async def commit(self) -> None:
|
||||
self.committed += 1
|
||||
|
||||
async def refresh(self, value: Any) -> None:
|
||||
self.refreshed.append(value)
|
||||
|
||||
|
||||
def test_extract_claim_email_prefers_direct_email() -> None:
|
||||
claims: dict[str, object] = {
|
||||
"email": " User@Example.com ",
|
||||
"primary_email_address": "ignored@example.com",
|
||||
}
|
||||
assert auth._extract_claim_email(claims) == "user@example.com"
|
||||
|
||||
|
||||
def test_extract_claim_email_from_primary_id() -> None:
|
||||
claims: dict[str, object] = {
|
||||
"primary_email_address_id": "id-2",
|
||||
"email_addresses": [
|
||||
{"id": "id-1", "email_address": "first@example.com"},
|
||||
{"id": "id-2", "email_address": "chosen@example.com"},
|
||||
],
|
||||
}
|
||||
assert auth._extract_claim_email(claims) == "chosen@example.com"
|
||||
|
||||
|
||||
def test_extract_claim_email_falls_back_to_first_address() -> None:
|
||||
claims: dict[str, object] = {
|
||||
"email_addresses": [
|
||||
{"id": "id-1", "email_address": "first@example.com"},
|
||||
{"id": "id-2", "email_address": "second@example.com"},
|
||||
],
|
||||
}
|
||||
assert auth._extract_claim_email(claims) == "first@example.com"
|
||||
|
||||
|
||||
def test_extract_claim_name_from_parts() -> None:
|
||||
claims: dict[str, object] = {
|
||||
"given_name": "Alex",
|
||||
"family_name": "Morgan",
|
||||
}
|
||||
assert auth._extract_claim_name(claims) == "Alex Morgan"
|
||||
|
||||
|
||||
def test_extract_clerk_profile_prefers_primary_email() -> None:
|
||||
profile = SimpleNamespace(
|
||||
primary_email_address_id="e2",
|
||||
email_addresses=[
|
||||
SimpleNamespace(id="e1", email_address="first@example.com"),
|
||||
SimpleNamespace(id="e2", email_address="primary@example.com"),
|
||||
],
|
||||
first_name="Asha",
|
||||
last_name="Rao",
|
||||
)
|
||||
email, name = auth._extract_clerk_profile(profile)
|
||||
assert email == "primary@example.com"
|
||||
assert name == "Asha"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_or_sync_user_updates_email_and_name(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
existing = User(clerk_user_id="user_123", email="old@example.com", name=None)
|
||||
|
||||
async def _fake_get_or_create(*_args: Any, **_kwargs: Any) -> tuple[User, bool]:
|
||||
return existing, False
|
||||
|
||||
async def _fake_fetch(_clerk_user_id: str) -> tuple[str | None, str | None]:
|
||||
return "new@example.com", "New Name"
|
||||
|
||||
monkeypatch.setattr(auth.crud, "get_or_create", _fake_get_or_create)
|
||||
monkeypatch.setattr(auth, "_fetch_clerk_profile", _fake_fetch)
|
||||
|
||||
session = _FakeSession()
|
||||
out = await auth._get_or_sync_user(
|
||||
session, # type: ignore[arg-type]
|
||||
clerk_user_id="user_123",
|
||||
claims={},
|
||||
)
|
||||
|
||||
assert out is existing
|
||||
assert existing.email == "new@example.com"
|
||||
assert existing.name == "New Name"
|
||||
assert session.committed == 1
|
||||
assert session.refreshed == [existing]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_or_sync_user_uses_clerk_profile_when_claims_are_minimal(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
existing = User(clerk_user_id="user_123", email=None, name=None)
|
||||
|
||||
async def _fake_get_or_create(*_args: Any, **_kwargs: Any) -> tuple[User, bool]:
|
||||
return existing, False
|
||||
|
||||
async def _fake_fetch(_clerk_user_id: str) -> tuple[str | None, str | None]:
|
||||
return "from-clerk@example.com", "From Clerk"
|
||||
|
||||
monkeypatch.setattr(auth.crud, "get_or_create", _fake_get_or_create)
|
||||
monkeypatch.setattr(auth, "_fetch_clerk_profile", _fake_fetch)
|
||||
|
||||
session = _FakeSession()
|
||||
out = await auth._get_or_sync_user(
|
||||
session, # type: ignore[arg-type]
|
||||
clerk_user_id="user_123",
|
||||
claims={"sub": "user_123"},
|
||||
)
|
||||
|
||||
assert out is existing
|
||||
assert existing.email == "from-clerk@example.com"
|
||||
assert existing.name == "From Clerk"
|
||||
assert session.committed == 1
|
||||
assert session.refreshed == [existing]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_or_sync_user_skips_commit_when_unchanged(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
existing = User(clerk_user_id="user_123", email="same@example.com", name="Name")
|
||||
|
||||
async def _fake_get_or_create(*_args: Any, **_kwargs: Any) -> tuple[User, bool]:
|
||||
return existing, False
|
||||
|
||||
async def _fake_fetch(_clerk_user_id: str) -> tuple[str | None, str | None]:
|
||||
return "same@example.com", "Different Name"
|
||||
|
||||
monkeypatch.setattr(auth.crud, "get_or_create", _fake_get_or_create)
|
||||
monkeypatch.setattr(auth, "_fetch_clerk_profile", _fake_fetch)
|
||||
|
||||
session = _FakeSession()
|
||||
out = await auth._get_or_sync_user(
|
||||
session, # type: ignore[arg-type]
|
||||
clerk_user_id="user_123",
|
||||
claims={},
|
||||
)
|
||||
|
||||
assert out is existing
|
||||
assert existing.email == "same@example.com"
|
||||
assert existing.name == "Name"
|
||||
assert session.committed == 0
|
||||
assert session.refreshed == []
|
||||
Reference in New Issue
Block a user