from __future__ import annotations from typing import Any import pytest from agent.dashboard import thread_api from agent.dashboard import user_mappings as um class _FakeStore: def __init__(self) -> None: self.items: dict[tuple[tuple[str, ...], str], dict[str, Any]] = {} async def get_item(self, namespace: list[str], key: str): value = self.items.get((tuple(namespace), key)) return {"value": value} if value is not None else None async def put_item(self, namespace: list[str], key: str, value: dict[str, Any]) -> None: self.items[(tuple(namespace), key)] = value async def search_items(self, namespace: list[str], *, limit: int = 1000): ns = tuple(namespace) items = [{"value": v} for (n, _k), v in self.items.items() if n == ns] return {"items": items[:limit]} class _FakeClient: def __init__(self, store: _FakeStore) -> None: self.store = store @pytest.fixture() def fake_store(monkeypatch: pytest.MonkeyPatch) -> _FakeStore: store = _FakeStore() monkeypatch.setattr(um, "_client", lambda: _FakeClient(store)) um.clear_cache() return store @pytest.mark.asyncio async def test_run_email_prefers_github_mapping(fake_store: _FakeStore) -> None: await um.upsert_mapping( github_login="johannes117", work_email="johannes@langchain.dev", ) # OAuth profile carries a personal account that isn't an org member. profile = {"email": "johannesduplessis117@gmail.com"} assert await thread_api._resolve_run_email("johannes117", profile) == "johannes@langchain.dev" @pytest.mark.asyncio async def test_run_email_falls_back_to_profile_when_unmapped(fake_store: _FakeStore) -> None: profile = {"email": "someone@example.com"} assert await thread_api._resolve_run_email("nomapping", profile) == "someone@example.com"