项目文件夹

文件
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 行
26 KiB
Python

"""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