From 21bbe3f98d03670d0012a61fcc67ca709ed75d6a Mon Sep 17 00:00:00 2001 From: Yun Chan Date: Sun, 30 Aug 2026 00:00:04 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B5=AC=EA=B8=80=20=EA=B3=84=EC=A0=95=20?= =?UTF-8?q?=EC=8B=9D=EB=B3=84=20=ED=9A=8C=EA=B7=80=EB=A5=BC=20=EA=B3=A0?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/app/test_auth_providers.py | 90 ++++++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 3 deletions(-) diff --git a/apps/api/app/test_auth_providers.py b/apps/api/app/test_auth_providers.py index a324a67..c26fc98 100644 --- a/apps/api/app/test_auth_providers.py +++ b/apps/api/app/test_auth_providers.py @@ -430,6 +430,28 @@ class AuthProviderScaffoldTest(unittest.IsolatedAsyncioTestCase): ) self.assertEqual(unverified.exception.status_code, 403) + def test_google_provider_identity_is_subject_based_and_separates_accounts( + self, + ) -> None: + first_login = auth_routes._provider_external_id( + "google", + "google-subject-a", + "first-address@gmail.com", + ) + same_account_new_email = auth_routes._provider_external_id( + "google", + "google-subject-a", + "renamed-address@gmail.com", + ) + different_google_account = auth_routes._provider_external_id( + "google", + "google-subject-b", + "second-address@gmail.com", + ) + + self.assertEqual(first_login, same_account_new_email) + self.assertNotEqual(first_login, different_google_account) + async def test_super_admin_email_is_admin_and_auto_approved(self) -> None: with ( patched_settings( @@ -461,7 +483,7 @@ class AuthProviderScaffoldTest(unittest.IsolatedAsyncioTestCase): async def test_existing_primary_role_super_admin_persists_admin_access_on_login( self, ) -> None: - email = "persistent-root@twentyoz.kr" + email = "yunchan8804@gmail.com" existing = _managed_user(email=email, role="learner", admin_access=False) auth_sessions._users[existing.user_id] = existing auth_sessions._email_index[email] = existing.user_id @@ -478,9 +500,9 @@ class AuthProviderScaffoldTest(unittest.IsolatedAsyncioTestCase): ): _, user = await auth_sessions.create_session( email=email, - display_name="Persistent Root", + display_name="Yun Chan", role="learner", - external_id="google:persistent-root", + external_id="google:yunchan8804", ) self.assertEqual(user.role, "learner") @@ -705,6 +727,68 @@ class AuthProviderScaffoldTest(unittest.IsolatedAsyncioTestCase): self.assertIn("$7::boolean", conn.queries[0]) self.assertIn("$9::boolean", conn.queries[1]) + async def test_google_oauth_claims_only_exact_normalized_email_placeholder( + self, + ) -> None: + class RecordingConn: + def __init__(self) -> None: + self.query = "" + self.args: tuple[Any, ...] = () + + async def fetchrow(self, query: str, *args: Any) -> dict[str, Any]: + self.query = query + self.args = args + now = datetime.now(timezone.utc) + return { + "user_id": "00000000-0000-0000-0000-000000000607", + "email": "claim.me@example.com", + "display_name": "Claim Me", + "role": "learner", + "admin_access": False, + "account_status": "approved", + "cohort": "", + "affiliation": "", + "created_at": now, + "last_seen_at": now, + } + + class RecordingAcquire: + def __init__(self, conn: RecordingConn) -> None: + self.conn = conn + + async def __aenter__(self) -> RecordingConn: + return self.conn + + async def __aexit__(self, exc_type, exc, tb) -> None: + return None + + class RecordingPool: + def __init__(self, conn: RecordingConn) -> None: + self.conn = conn + + def acquire(self) -> RecordingAcquire: + return RecordingAcquire(self.conn) + + conn = RecordingConn() + with patch.object(auth_sessions, "get_pool", return_value=RecordingPool(conn)): + user = await auth_sessions.upsert_managed_user( + auth_sessions.ManagedUserUpsertInput( + email=" Claim.Me@Example.com ", + display_name="Claim Me", + role="learner", + external_id="google:claim-subject", + account_status="approved", + ) + ) + + self.assertEqual(user.email, "claim.me@example.com") + self.assertIn("WHERE lower(email) = $2", conn.query) + self.assertIn("AND (external_id = $6 OR external_id IS NULL)", conn.query) + self.assertIn("WHERE existing.external_id = $1", conn.query) + self.assertEqual(conn.args[0], "google:claim-subject") + self.assertEqual(conn.args[1], "claim.me@example.com") + self.assertEqual(conn.args[5], "email:claim.me@example.com") + async def test_google_oauth_promotes_preregistered_pending_user_in_db(self) -> None: class RecordingConn: def __init__(self) -> None: