diff --git a/backend/packages/harness/deerflow/persistence/external_user/sql.py b/backend/packages/harness/deerflow/persistence/external_user/sql.py index bb27d66e..b075ac11 100644 --- a/backend/packages/harness/deerflow/persistence/external_user/sql.py +++ b/backend/packages/harness/deerflow/persistence/external_user/sql.py @@ -65,7 +65,12 @@ class ExternalUserRepository: metadata: dict[str, Any] | None = None, ) -> dict[str, Any]: """Insert a new external user or refresh ``last_seen_at`` on an - existing (service_account_id, external_id) row.""" + existing (service_account_id, external_id) row. + + ``display_name`` and ``metadata`` are only written when explicitly + passed (non-None); ``None`` means "leave unchanged" — you cannot + clear ``display_name`` back to None via this method. ``workspace_id`` + is only used on insert; it is ignored on update.""" now = datetime.now(UTC) async with self._sf() as session: result = await session.execute( @@ -86,6 +91,9 @@ class ExternalUserRepository: created_at=now, last_seen_at=now, ) + # NOTE: concurrent inserts of the same pair will raise IntegrityError + # from uq_external_users_sa_external — the future auth caller should + # catch it and re-read rather than treat it as fatal. session.add(row) else: row.last_seen_at = now diff --git a/backend/tests/test_external_user_repo.py b/backend/tests/test_external_user_repo.py index a17646eb..a27c5cb1 100644 --- a/backend/tests/test_external_user_repo.py +++ b/backend/tests/test_external_user_repo.py @@ -64,6 +64,19 @@ async def test_upsert_inserts_then_updates_same_row(tmp_path): await _cleanup() +async def test_get_by_id_hit_and_miss(tmp_path): + repo = await _make_repo(tmp_path) + try: + await _seed_sa(repo) + created = await repo.upsert(workspace_id="w-1", service_account_id="sa-1", external_id="ext-9") + fetched = await repo.get(created["id"]) + assert fetched is not None + assert fetched["id"] == created["id"] + assert await repo.get("nonexistent") is None + finally: + await _cleanup() + + async def test_get_by_external_id(tmp_path): repo = await _make_repo(tmp_path) try: