Files
tracer-cloud--opensre/tests/utils/test_slack_delivery.py
T
wehub-resource-sync 4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

595 lines
26 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Tests for ``utils/slack_delivery.py``.
Covers the four ``slack.com`` / NextJS-proxy / incoming-webhook code paths
after the refactor onto the shared ``delivery_transport.post_json`` helper:
- ``_call_reactions_api`` / ``add_reaction`` / ``remove_reaction``
- ``_post_direct`` (chat.postMessage as thread reply)
- ``_post_via_webapp`` (NextJS ``/api/slack`` fallback)
- ``_post_via_incoming_webhook`` (standalone ``SLACK_WEBHOOK_URL``)
- ``send_slack_report`` orchestration across direct / webapp / webhook
All tests stub ``platform.notifications.delivery_transport.httpx.post`` so the real
network is never touched. Provider-specific success criteria
(``data["ok"]``, status codes, etc.) are exercised explicitly.
"""
from __future__ import annotations
import logging
from typing import Any
from unittest.mock import MagicMock
import pytest
from integrations.slack import delivery as slack_delivery
def _mock_response(status_code: int, json_body: Any = None, text: str = "") -> MagicMock:
resp = MagicMock()
resp.status_code = status_code
resp.text = text
if isinstance(json_body, Exception):
def _raise() -> Any:
raise json_body
resp.json.side_effect = _raise
else:
resp.json.return_value = json_body if json_body is not None else {}
return resp
# ---------------------------------------------------------------------------
# Reactions API
# ---------------------------------------------------------------------------
class TestCallReactionsApi:
def test_add_reaction_success(self, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(
"platform.notifications.delivery_transport.httpx.post",
lambda *_a, **_kw: _mock_response(200, {"ok": True}),
)
ok = slack_delivery._call_reactions_api(
"reactions.add", "tok", "C123", "1.0", "white_check_mark"
)
assert ok is True
@pytest.mark.parametrize("err", ["already_reacted", "no_reaction", "message_not_found"])
def test_known_idempotent_failures_swallowed(
self, err: str, monkeypatch: pytest.MonkeyPatch
) -> None:
"""``already_reacted`` and ``no_reaction`` are not real errors —
they happen during normal swap_reaction flows and must not log."""
monkeypatch.setattr(
"platform.notifications.delivery_transport.httpx.post",
lambda *_a, **_kw: _mock_response(200, {"ok": False, "error": err}),
)
assert slack_delivery._call_reactions_api("reactions.add", "tok", "C", "1.0", "x") is False
def test_unexpected_error_returns_false(self, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(
"platform.notifications.delivery_transport.httpx.post",
lambda *_a, **_kw: _mock_response(200, {"ok": False, "error": "channel_not_found"}),
)
assert slack_delivery._call_reactions_api("reactions.add", "tok", "C", "1.0", "x") is False
def test_transport_exception_returns_false(self, monkeypatch: pytest.MonkeyPatch) -> None:
def _raise(*_a: Any, **_kw: Any) -> Any:
raise ConnectionError("dns failure")
monkeypatch.setattr("platform.notifications.delivery_transport.httpx.post", _raise)
assert slack_delivery._call_reactions_api("reactions.add", "tok", "C", "1.0", "x") is False
def test_sends_correct_url_and_headers(self, monkeypatch: pytest.MonkeyPatch) -> None:
captured: dict[str, Any] = {}
def _capture(url: str, **kwargs: Any) -> MagicMock:
captured["url"] = url
captured.update(kwargs)
return _mock_response(200, {"ok": True})
monkeypatch.setattr("platform.notifications.delivery_transport.httpx.post", _capture)
slack_delivery._call_reactions_api(
"reactions.remove", "my-token", "C9", "1.5", "thinking_face"
)
assert captured["url"] == "https://slack.com/api/reactions.remove"
assert captured["headers"]["Authorization"] == "Bearer my-token"
# Slack emits a `missing_charset` warning without the explicit
# ``charset=utf-8`` suffix on JSON POSTs (httpx alone only sets
# the bare ``application/json``). Pin the header to match Slack's
# documented recommendation: https://api.slack.com/web#posting_json
assert captured["headers"]["Content-Type"] == "application/json; charset=utf-8"
assert captured["json"] == {"channel": "C9", "timestamp": "1.5", "name": "thinking_face"}
assert captured["timeout"] == 8.0
# ---------------------------------------------------------------------------
# _post_direct (chat.postMessage)
# ---------------------------------------------------------------------------
class TestPostDirect:
def test_success_returns_true_empty_error(self, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(
"platform.notifications.delivery_transport.httpx.post",
lambda *_a, **_kw: _mock_response(200, {"ok": True, "ts": "1.234"}),
)
ok, err = slack_delivery._post_direct("hello", "C1", "1.000", "tok")
assert ok is True
assert err == ""
def test_slack_error_returned_with_prefix(self, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(
"platform.notifications.delivery_transport.httpx.post",
lambda *_a, **_kw: _mock_response(200, {"ok": False, "error": "channel_not_found"}),
)
ok, err = slack_delivery._post_direct("hello", "C1", "1.000", "tok")
assert ok is False
assert err == "slack_error=channel_not_found"
def test_transport_exception_returns_exception_prefix(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
def _raise(*_a: Any, **_kw: Any) -> Any:
raise TimeoutError("read timeout")
monkeypatch.setattr("platform.notifications.delivery_transport.httpx.post", _raise)
ok, err = slack_delivery._post_direct("hello", "C1", "1.000", "tok")
assert ok is False
assert err.startswith("exception=")
assert "read timeout" in err
def test_sends_thread_reply_payload(self, monkeypatch: pytest.MonkeyPatch) -> None:
captured: dict[str, Any] = {}
def _capture(url: str, **kwargs: Any) -> MagicMock:
captured["url"] = url
captured.update(kwargs)
return _mock_response(200, {"ok": True})
monkeypatch.setattr("platform.notifications.delivery_transport.httpx.post", _capture)
slack_delivery._post_direct("the body", "C42", "9.876", "secret-tok", blocks=[{"x": 1}])
assert captured["url"] == "https://slack.com/api/chat.postMessage"
assert captured["headers"]["Authorization"] == "Bearer secret-tok"
# Pin the charset header — Slack emits ``missing_charset`` without it.
assert captured["headers"]["Content-Type"] == "application/json; charset=utf-8"
assert captured["json"]["channel"] == "C42"
assert captured["json"]["thread_ts"] == "9.876"
assert captured["json"]["text"] == "the body"
assert captured["json"]["blocks"] == [{"x": 1}]
# ---------------------------------------------------------------------------
# _post_via_incoming_webhook
# ---------------------------------------------------------------------------
class TestIncomingWebhook:
def test_success_returns_true(self, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(
"platform.notifications.delivery_transport.httpx.post",
lambda *_a, **_kw: _mock_response(200, None, "ok"),
)
assert (
slack_delivery._post_via_incoming_webhook("hi", "https://hooks.slack.test/abc") is True
)
@pytest.mark.parametrize("status", [400, 403, 404, 500, 502])
def test_non_2xx_status_returns_false(
self, status: int, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setattr(
"platform.notifications.delivery_transport.httpx.post",
lambda *_a, **_kw: _mock_response(status, None, f"err {status}"),
)
assert (
slack_delivery._post_via_incoming_webhook("hi", "https://hooks.slack.test/abc") is False
)
def test_transport_exception_returns_false(self, monkeypatch: pytest.MonkeyPatch) -> None:
def _raise(*_a: Any, **_kw: Any) -> Any:
raise ConnectionError("refused")
monkeypatch.setattr("platform.notifications.delivery_transport.httpx.post", _raise)
assert (
slack_delivery._post_via_incoming_webhook("hi", "https://hooks.slack.test/abc") is False
)
def test_blocks_and_extra_merged_into_payload(self, monkeypatch: pytest.MonkeyPatch) -> None:
captured: dict[str, Any] = {}
monkeypatch.setattr(
"platform.notifications.delivery_transport.httpx.post",
lambda *_a, **kw: captured.update(kw) or _mock_response(200, None, ""),
)
slack_delivery._post_via_incoming_webhook(
"hi", "https://hooks.slack.test/abc", blocks=[{"b": 1}], unfurl_links=False
)
body = captured["json"]
assert body["text"] == "hi"
assert body["blocks"] == [{"b": 1}]
assert body["unfurl_links"] is False
assert captured["follow_redirects"] is True
# ---------------------------------------------------------------------------
# _post_via_webapp
# ---------------------------------------------------------------------------
class TestPostViaWebapp:
def test_skips_when_tracer_api_url_missing(self, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delenv("TRACER_API_URL", raising=False)
# httpx.post must NOT be called
called = {"n": 0}
monkeypatch.setattr(
"platform.notifications.delivery_transport.httpx.post",
lambda *_a, **_kw: (called.update(n=called["n"] + 1), _mock_response(200, None, ""))[1],
)
assert slack_delivery._post_via_webapp("hi", "C1", "1.0") is False
assert called["n"] == 0
def test_success_path(self, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("TRACER_API_URL", "https://api.tracer.test")
captured: dict[str, Any] = {}
monkeypatch.setattr(
"platform.notifications.delivery_transport.httpx.post",
lambda url, **kw: captured.update({"url": url}, **kw) or _mock_response(200, None, ""),
)
assert slack_delivery._post_via_webapp("hi", "C1", "1.0") is True
assert captured["url"] == "https://api.tracer.test/api/slack"
assert captured["follow_redirects"] is True
def test_5xx_returns_false(self, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("TRACER_API_URL", "https://api.tracer.test")
monkeypatch.setattr(
"platform.notifications.delivery_transport.httpx.post",
lambda *_a, **_kw: _mock_response(500, None, "boom"),
)
assert slack_delivery._post_via_webapp("hi", "C1", "1.0") is False
# ---------------------------------------------------------------------------
# send_slack_report orchestration
# ---------------------------------------------------------------------------
class TestSendSlackReport:
def test_no_thread_ts_no_webhook_returns_false(self, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delenv("SLACK_WEBHOOK_URL", raising=False)
monkeypatch.setattr(slack_delivery, "_configured_webhook_url", lambda: "")
ok, err = slack_delivery.send_slack_report("hi", channel="C1", thread_ts=None)
assert ok is False
assert err == "no_thread_ts"
def test_no_thread_ts_with_webhook_uses_webhook(self, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("SLACK_WEBHOOK_URL", "https://hooks.slack.test/abc")
captured: dict[str, Any] = {}
monkeypatch.setattr(
"platform.notifications.delivery_transport.httpx.post",
lambda url, **kw: captured.update({"url": url}, **kw) or _mock_response(200, None, ""),
)
ok, err = slack_delivery.send_slack_report("hi", channel="C1", thread_ts=None)
assert ok is True
assert err == ""
assert captured["url"] == "https://hooks.slack.test/abc"
def test_no_thread_ts_uses_store_webhook_when_env_missing(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.delenv("SLACK_WEBHOOK_URL", raising=False)
monkeypatch.setattr(
"integrations.catalog.resolve_effective_integrations",
lambda: {
"slack": {
"source": "local store",
"config": {"webhook_url": "https://hooks.slack.test/store"},
}
},
)
captured: dict[str, Any] = {}
monkeypatch.setattr(
"platform.notifications.delivery_transport.httpx.post",
lambda url, **kw: captured.update({"url": url}, **kw) or _mock_response(200, None, ""),
)
ok, err = slack_delivery.send_slack_report("hi", channel="C1", thread_ts=None)
assert ok is True
assert err == ""
assert captured["url"] == "https://hooks.slack.test/store"
def test_direct_post_used_when_token_and_channel(self, monkeypatch: pytest.MonkeyPatch) -> None:
captured: list[str] = []
def _capture(url: str, **_kw: Any) -> MagicMock:
captured.append(url)
return _mock_response(200, {"ok": True, "ts": "x"})
monkeypatch.setattr("platform.notifications.delivery_transport.httpx.post", _capture)
ok, err = slack_delivery.send_slack_report(
"hi", channel="C1", thread_ts="1.0", access_token="tok"
)
assert ok is True
assert err == ""
assert captured == ["https://slack.com/api/chat.postMessage"]
def test_direct_failure_falls_back_to_webapp(self, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("TRACER_API_URL", "https://api.tracer.test")
urls: list[str] = []
def _capture(url: str, **kw: Any) -> MagicMock:
urls.append(url)
if "chat.postMessage" in url:
return _mock_response(200, {"ok": False, "error": "channel_not_found"})
return _mock_response(200, None, "")
monkeypatch.setattr("platform.notifications.delivery_transport.httpx.post", _capture)
ok, err = slack_delivery.send_slack_report(
"hi", channel="C1", thread_ts="1.0", access_token="tok"
)
assert ok is True
assert err == ""
assert urls == [
"https://slack.com/api/chat.postMessage",
"https://api.tracer.test/api/slack",
]
# ---------------------------------------------------------------------------
# Exception-type triage log line (regression for the #864 refactor)
# ---------------------------------------------------------------------------
class TestPostDirectExceptionLog:
"""The original ``_post_direct`` log embedded ``type(exc).__name__``
as ``type=…`` so on-call could distinguish ``TimeoutError`` from
``ConnectionError`` at a glance. The shared transport now exposes
``exc_type`` on ``DeliveryResponse`` and the Slack helper threads it
back into the log line under the original ``type=`` key for
log-parser compatibility with the pre-refactor format."""
def test_log_includes_exception_class_name(
self, monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
) -> None:
def _raise(*_a: Any, **_kw: Any) -> Any:
raise TimeoutError("read timeout")
monkeypatch.setattr("platform.notifications.delivery_transport.httpx.post", _raise)
with caplog.at_level(logging.ERROR, logger="integrations.slack.delivery"):
slack_delivery._post_direct("hi", "C1", "1.0", "tok")
joined = " ".join(rec.getMessage() for rec in caplog.records)
assert "type=TimeoutError" in joined
assert "read timeout" in joined
def test_log_distinguishes_connection_from_timeout(
self, monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
) -> None:
def _raise(*_a: Any, **_kw: Any) -> Any:
raise ConnectionError("dns failure")
monkeypatch.setattr("platform.notifications.delivery_transport.httpx.post", _raise)
with caplog.at_level(logging.ERROR, logger="integrations.slack.delivery"):
slack_delivery._post_direct("hi", "C1", "1.0", "tok")
joined = " ".join(rec.getMessage() for rec in caplog.records)
assert "type=ConnectionError" in joined
# ---------------------------------------------------------------------------
# Shared-transport delegation (regression coverage for the #864 refactor)
# ---------------------------------------------------------------------------
class TestDelegatesToSharedTransport:
"""After #864 the slack helper uses ``delivery_transport.post_json``
rather than calling httpx directly. These tests pin that contract so
a future regression that re-imports httpx into ``slack_delivery`` —
or that bypasses ``post_json`` from any of the four code paths — is
caught immediately. Mirrors the same regression class on the Discord
and Telegram test files."""
def test_module_does_not_import_httpx(self) -> None:
# Reuse the top-level ``from integrations.slack_delivery import delivery as slack_delivery`` (renamed for cleanliness) to
# avoid importing the same module via both ``import`` and
# ``from import`` styles (CodeQL py/import-and-import-from).
assert not hasattr(slack_delivery, "httpx"), (
"slack_delivery should not import httpx directly — "
"it must go through delivery_transport.post_json"
)
def test_call_reactions_api_uses_post_json_helper(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
from platform.notifications.delivery_transport import DeliveryResponse
captured: dict[str, Any] = {}
def _stub_post_json(url: str, payload: dict[str, Any], **kw: Any) -> DeliveryResponse:
captured["url"] = url
captured["payload"] = payload
captured["headers"] = kw.get("headers")
captured["timeout"] = kw.get("timeout")
return DeliveryResponse(ok=True, status_code=200, data={"ok": True})
monkeypatch.setattr("integrations.slack.delivery.post_json", _stub_post_json)
ok = slack_delivery._call_reactions_api(
"reactions.add", "tok", "C9", "1.5", "thinking_face"
)
assert ok is True
assert captured["url"] == "https://slack.com/api/reactions.add"
assert captured["headers"]["Authorization"] == "Bearer tok"
# Reactions API uses a tighter 8s timeout than the default 15s.
assert captured["timeout"] == 8.0
def test_post_direct_uses_post_json_helper(self, monkeypatch: pytest.MonkeyPatch) -> None:
from platform.notifications.delivery_transport import DeliveryResponse
captured: dict[str, Any] = {}
def _stub_post_json(url: str, payload: dict[str, Any], **kw: Any) -> DeliveryResponse:
captured["url"] = url
captured["payload"] = payload
captured["headers"] = kw.get("headers")
return DeliveryResponse(ok=True, status_code=200, data={"ok": True, "ts": "1.234"})
monkeypatch.setattr("integrations.slack.delivery.post_json", _stub_post_json)
ok, err = slack_delivery._post_direct(
"hello", "C1", "1.000", "secret-tok", blocks=[{"x": 1}]
)
assert ok is True
assert err == ""
assert captured["url"] == "https://slack.com/api/chat.postMessage"
assert captured["headers"]["Authorization"] == "Bearer secret-tok"
assert captured["payload"]["blocks"] == [{"x": 1}]
def test_post_via_webapp_uses_post_json_helper(self, monkeypatch: pytest.MonkeyPatch) -> None:
from platform.notifications.delivery_transport import DeliveryResponse
monkeypatch.setenv("TRACER_API_URL", "https://api.tracer.test")
captured: dict[str, Any] = {}
def _stub_post_json(url: str, payload: dict[str, Any], **kw: Any) -> DeliveryResponse:
captured["url"] = url
captured["payload"] = payload
captured["follow_redirects"] = kw.get("follow_redirects")
return DeliveryResponse(ok=True, status_code=200, data={}, text="")
monkeypatch.setattr("integrations.slack.delivery.post_json", _stub_post_json)
ok = slack_delivery._post_via_webapp("hi", "C1", "1.0")
assert ok is True
assert captured["url"] == "https://api.tracer.test/api/slack"
assert captured["follow_redirects"] is True
def test_post_via_incoming_webhook_uses_post_json_helper(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
from platform.notifications.delivery_transport import DeliveryResponse
captured: dict[str, Any] = {}
def _stub_post_json(url: str, payload: dict[str, Any], **kw: Any) -> DeliveryResponse:
captured["url"] = url
captured["payload"] = payload
captured["follow_redirects"] = kw.get("follow_redirects")
return DeliveryResponse(ok=True, status_code=200, data={}, text="ok")
monkeypatch.setattr("integrations.slack.delivery.post_json", _stub_post_json)
ok = slack_delivery._post_via_incoming_webhook(
"hi", "https://hooks.slack.test/abc", blocks=[{"b": 1}]
)
assert ok is True
assert captured["url"] == "https://hooks.slack.test/abc"
assert captured["payload"]["text"] == "hi"
assert captured["payload"]["blocks"] == [{"b": 1}]
assert captured["follow_redirects"] is True
# ---------------------------------------------------------------------------
# Issue #865 Slack hardening: non-JSON bodies and token redaction
# ---------------------------------------------------------------------------
class TestNonJsonBody:
def test_post_direct_handles_html_error_body(self, monkeypatch: pytest.MonkeyPatch) -> None:
from platform.notifications.delivery_transport import DeliveryResponse
monkeypatch.setattr(
"integrations.slack.delivery.post_json",
lambda *_a, **_kw: DeliveryResponse(
ok=True,
status_code=502,
data={},
text="<html>Bad Gateway</html>",
),
)
ok, err = slack_delivery._post_direct("hi", "C1", "1.0", "tok")
assert ok is False
assert "<html>Bad Gateway</html>" in err
def test_post_direct_redacts_token_from_html_error_body(
self, monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
) -> None:
from platform.notifications.delivery_transport import DeliveryResponse
token = "xoxb-1234567890-abcdefghij"
monkeypatch.setattr(
"integrations.slack.delivery.post_json",
lambda *_a, **_kw: DeliveryResponse(
ok=True,
status_code=502,
data={},
text=f"<html>proxy echoed {token}</html>",
),
)
with caplog.at_level(logging.ERROR, logger="integrations.slack.delivery"):
ok, err = slack_delivery._post_direct("hi", "C1", "1.0", token)
joined = " ".join(rec.getMessage() for rec in caplog.records)
assert ok is False
assert token not in err
assert token not in joined
assert "<redacted>" in err
assert "<redacted>" in joined
class TestExceptionRedaction:
def test_exception_error_redacts_token(self, monkeypatch: pytest.MonkeyPatch) -> None:
from platform.notifications.delivery_transport import DeliveryResponse
token = "xoxb-1234567890-abcdefghij"
leak_msg = f"connect failed with {token}"
monkeypatch.setattr(
"integrations.slack.delivery.post_json",
lambda *_a, **_kw: DeliveryResponse(ok=False, error=leak_msg),
)
ok, err = slack_delivery._post_direct("hi", "C1", "1.0", token)
assert ok is False
assert token not in err
assert "<redacted>" in err
def test_send_slack_report_redacts_token_in_composed_error(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
from platform.notifications.delivery_transport import DeliveryResponse
token = "xoxb-1234567890-abcdefghij"
def _stub_post_json(url: str, payload: dict, **kw: Any) -> DeliveryResponse:
if "chat.postMessage" in url:
return DeliveryResponse(ok=False, error=f"connect with {token}")
return DeliveryResponse(ok=False, error="webapp down")
monkeypatch.setattr("integrations.slack.delivery.post_json", _stub_post_json)
ok, err = slack_delivery.send_slack_report(
"hi", channel="C1", thread_ts="1.0", access_token=token
)
assert ok is False
assert token not in err
assert "<redacted>" in err
class TestExceptionLogRedaction:
def test_exception_log_redacts_token(
self, monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
) -> None:
from platform.notifications.delivery_transport import DeliveryResponse
token = "xoxb-1234567890-abcdefghij"
leak_msg = f"connect failed with {token}"
monkeypatch.setattr(
"integrations.slack.delivery.post_json",
lambda *_a, **_kw: DeliveryResponse(ok=False, error=leak_msg),
)
with caplog.at_level(logging.ERROR, logger="integrations.slack.delivery"):
slack_delivery._post_direct("hi", "C1", "1.0", token)
joined = " ".join(rec.getMessage() for rec in caplog.records)
assert token not in joined
assert "<redacted>" in joined