open-swe/tests/test_account_link.py
Johannes du Plessis 58e0f84470
fix: use Slack OIDC mappings for Slack thread ownership (#1410)
* fix: tag Slack threads with stored identity so they surface in web

process_slack_mention gated the run on mapped_login (resolved from the stable
Slack user id), but upsert_agent_thread_owner_metadata independently re-resolved
the GitHub login from the Slack profile email. When that email differs from the
user's mapping email (e.g. a personal vs work address), the lookup returned None,
so github_login was never stamped on the thread and the thread never surfaced in
the web Agents UI (which searches by github_login / triggering_user_email).

Resolve the GitHub user from the store via the Slack id, pass that login through
to the owner metadata, and use the mapping's stored work email (falling back to
the Slack profile email for unmapped users) for both the run config and the
thread tagging, so Slack-started threads reliably appear in web.

* fix: stamp github_login on Slack threads so they surface in web

process_slack_mention gated the run on mapped_login (resolved from the stable
Slack user id) but upsert_agent_thread_owner_metadata re-resolved the login from
the Slack profile email; when that email isn't the user's mapping email the
lookup returns None and github_login is never stamped, so the thread is invisible
in the web Agents UI (which searches by github_login / triggering_user_email).

Pass the already-resolved mapped_login through to the owner metadata. The
dashboard match keys on the stable GitHub login, so this is sufficient; the
triggering email stays the live Slack profile value.

* fix: preserve Slack email during account mapping

* fix: require Slack OIDC for email mappings

* chore: format Slack OIDC mapping cleanup
2026-06-04 19:58:30 +00:00

75 lines
2.3 KiB
Python

"""Tests for the Slack account-link prompt."""
from __future__ import annotations
import pytest
@pytest.fixture(autouse=True)
def _jwt_secret(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("DASHBOARD_JWT_SECRET", "test-secret")
def test_account_link_prompt_posts_generic_token_free_link(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""The prompt posts a plain settings link in the thread — no per-user token."""
import asyncio
from agent import webapp
monkeypatch.setenv("DASHBOARD_BASE_URL", "https://app.example.com")
calls: dict[str, object] = {}
async def fake_reply(channel_id, thread_ts, text):
calls["reply"] = {"channel_id": channel_id, "thread_ts": thread_ts, "text": text}
return True
monkeypatch.setattr(webapp, "post_slack_thread_reply", fake_reply)
asyncio.run(webapp._post_account_link_prompt("C1", "1.1", "U1", "d@x.com", reason="unlinked"))
assert calls["reply"]["channel_id"] == "C1"
assert calls["reply"]["thread_ts"] == "1.1"
assert "https://app.example.com/my-settings" in calls["reply"]["text"]
# No signed account-link token may appear in the public thread.
assert "link=" not in calls["reply"]["text"]
def test_account_link_prompt_revoked_wording(monkeypatch: pytest.MonkeyPatch) -> None:
import asyncio
from agent import webapp
monkeypatch.setenv("DASHBOARD_BASE_URL", "https://app.example.com")
calls: dict[str, object] = {}
async def fake_reply(channel_id, thread_ts, text):
calls["text"] = text
return True
monkeypatch.setattr(webapp, "post_slack_thread_reply", fake_reply)
asyncio.run(webapp._post_account_link_prompt("C1", "1.1", "U1", "d@x.com", reason="revoked"))
assert "no longer valid" in calls["text"]
assert "link=" not in calls["text"]
def test_account_link_prompt_skips_when_dashboard_url_unset(
monkeypatch: pytest.MonkeyPatch,
) -> None:
import asyncio
from agent import webapp
monkeypatch.delenv("DASHBOARD_BASE_URL", raising=False)
posted = False
async def fake_reply(channel_id, thread_ts, text):
nonlocal posted
posted = True
return True
monkeypatch.setattr(webapp, "post_slack_thread_reply", fake_reply)
asyncio.run(webapp._post_account_link_prompt("C1", "1.1", "U1", "d@x.com", reason="unlinked"))
assert posted is False