open-swe/tests/test_account_link_completion.py

55 lines
1.8 KiB
Python
Raw Normal View History

feat: Store-backed GitHub/Slack user mapping (self-service + admin) (#1369) * Replace hardcoded GitHub-email map with Store-backed user mapping Move the static GITHUB_USER_EMAIL_MAP to a Store-backed bidirectional mapping (GitHub login <-> work email <-> optional Slack ID) with an in-process cache, self-service onboarding, and admin management. - agent/dashboard/user_mappings.py: Store CRUD + login/email/slack-id indexes, sync cache readers for hot paths, async fallthrough, and a bulk_import that preserves existing richer records. - Migrate all read sites (auth.py, agent_overrides.py, authorship.py, github_comments.py, webapp.py x2) off the dict. - Unmapped Slack tags now run on the GitHub App installation token (use_installation_token_fallback) and get an ephemeral "link your GitHub account" prompt carrying the Slack id + email via a signed account-link token threaded through the OAuth state. - OAuth callback completes a self-service (org-gated) mapping from that token, falling back to the verified GitHub email. - Admin CRUD endpoints + one-time legacy import; dashboard UI section. - Legacy dict retained only as the import payload (no longer read). Tests: mapping store, account-link round-trip + completion, mapped vs unmapped Slack flows; existing trust-gate tests updated to prime cache. * Address review: cold-cache email resolution + stale alias de-indexing - agent_overrides: add resolve_login_from_email_async that falls through to the Store on a cold cache; use it at the async repo-resolution call sites (Slack repo config, Linear comment, owner-metadata) so a mapped user still resolves to their GitHub login + dashboard default_repo on a fresh worker. - user_mappings.upsert_mapping: de-index the existing login before re-indexing so a changed email/Slack id no longer leaves stale aliases resolving to the login in-process. - Tests for both fixes; update Slack repo-config test to patch the async resolver.
2026-06-01 14:37:19 -07:00
"""Tests for self-service mapping completion in the OAuth callback."""
from __future__ import annotations
from unittest.mock import AsyncMock
import pytest
from agent.dashboard import oauth, routes
@pytest.fixture(autouse=True)
def _jwt_secret(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("DASHBOARD_JWT_SECRET", "test-secret")
@pytest.mark.asyncio
async def test_completion_uses_link_token_slack_identity(monkeypatch: pytest.MonkeyPatch) -> None:
mock_upsert = AsyncMock()
monkeypatch.setattr(routes, "upsert_mapping", mock_upsert)
link = oauth.issue_account_link(slack_user_id="U999", work_email="slack@x.com")
await routes._complete_account_mapping("octo", "gh@x.com", link)
mock_upsert.assert_awaited_once()
kwargs = mock_upsert.await_args.kwargs
assert kwargs["github_login"] == "octo"
# Slack work email from the link token wins over the GitHub email.
assert kwargs["work_email"] == "slack@x.com"
assert kwargs["slack_user_id"] == "U999"
assert kwargs["source"] == "self"
@pytest.mark.asyncio
async def test_completion_falls_back_to_github_email(monkeypatch: pytest.MonkeyPatch) -> None:
mock_upsert = AsyncMock()
monkeypatch.setattr(routes, "upsert_mapping", mock_upsert)
await routes._complete_account_mapping("octo", "gh@x.com", None)
kwargs = mock_upsert.await_args.kwargs
assert kwargs["work_email"] == "gh@x.com"
assert kwargs["slack_user_id"] is None
assert kwargs["source"] == "self"
@pytest.mark.asyncio
async def test_completion_noop_without_any_email(monkeypatch: pytest.MonkeyPatch) -> None:
mock_upsert = AsyncMock()
monkeypatch.setattr(routes, "upsert_mapping", mock_upsert)
await routes._complete_account_mapping("octo", None, None)
mock_upsert.assert_not_awaited()