Files
tracer-cloud--opensre/tests/utils/test_discord_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

394 lines
15 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/discord_delivery.py."""
from __future__ import annotations
import logging
from typing import Any
from unittest.mock import MagicMock
import pytest
from integrations.discord import delivery as discord_delivery
from integrations.discord.delivery import (
create_discord_thread,
post_discord_message,
send_discord_report,
)
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _mock_response(status_code: int, body: dict[str, Any]) -> MagicMock:
resp = MagicMock()
resp.status_code = status_code
resp.json.return_value = body
return resp
# ---------------------------------------------------------------------------
# post_discord_message
# ---------------------------------------------------------------------------
def test_post_discord_message_success(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(
"platform.notifications.delivery_transport.httpx.post",
lambda *_a, **_kw: _mock_response(200, {"id": "msg-123"}),
)
ok, error, message_id = post_discord_message("chan-1", [{"title": "Alert"}], "bot-token")
assert ok is True
assert error == ""
assert message_id == "msg-123"
def test_post_discord_message_201_also_succeeds(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(
"platform.notifications.delivery_transport.httpx.post",
lambda *_a, **_kw: _mock_response(201, {"id": "msg-456"}),
)
ok, _, message_id = post_discord_message("chan-1", [], "bot-token")
assert ok is True
assert message_id == "msg-456"
def test_post_discord_message_sends_correct_payload(monkeypatch: pytest.MonkeyPatch) -> None:
captured: dict[str, Any] = {}
def _fake_post(
url: str, *, json: dict[str, Any], headers: dict[str, str], **_kw: Any
) -> MagicMock:
captured["url"] = url
captured["json"] = json
captured["headers"] = headers
return _mock_response(200, {"id": "x"})
monkeypatch.setattr("platform.notifications.delivery_transport.httpx.post", _fake_post)
embeds = [{"title": "Test"}]
post_discord_message("chan-42", embeds, "my-token", content="hello")
assert "chan-42" in captured["url"]
assert captured["json"]["content"] == "hello"
assert captured["json"]["embeds"] == embeds
assert captured["headers"]["Authorization"] == "Bot my-token"
def test_post_discord_message_failure_returns_api_error(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(
"platform.notifications.delivery_transport.httpx.post",
lambda *_a, **_kw: _mock_response(403, {"message": "Missing Permissions"}),
)
ok, error, message_id = post_discord_message("chan-1", [], "bot-token")
assert ok is False
assert "Missing Permissions" in error
assert message_id == ""
def test_post_discord_message_failure_falls_back_to_error_key(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setattr(
"platform.notifications.delivery_transport.httpx.post",
lambda *_a, **_kw: _mock_response(400, {"error": "Bad Request"}),
)
ok, error, _ = post_discord_message("chan-1", [], "bot-token")
assert ok is False
assert "Bad Request" in error
def test_post_discord_message_exception_returns_false(monkeypatch: pytest.MonkeyPatch) -> None:
def _raise(*_a: Any, **_kw: Any) -> None:
raise ConnectionError("network down")
monkeypatch.setattr("platform.notifications.delivery_transport.httpx.post", _raise)
ok, error, message_id = post_discord_message("chan-1", [], "bot-token")
assert ok is False
assert "network down" in error
assert message_id == ""
# ---------------------------------------------------------------------------
# create_discord_thread
# ---------------------------------------------------------------------------
def test_create_discord_thread_success(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(
"platform.notifications.delivery_transport.httpx.post",
lambda *_a, **_kw: _mock_response(201, {"id": "thread-99"}),
)
ok, error, thread_id = create_discord_thread("chan-1", "msg-1", "My Thread", "bot-token")
assert ok is True
assert error == ""
assert thread_id == "thread-99"
def test_create_discord_thread_sends_correct_url(monkeypatch: pytest.MonkeyPatch) -> None:
captured: dict[str, str] = {}
def _fake_post(url: str, **_kw: Any) -> MagicMock:
captured["url"] = url
return _mock_response(200, {"id": "t-1"})
monkeypatch.setattr("platform.notifications.delivery_transport.httpx.post", _fake_post)
create_discord_thread("chan-5", "msg-5", "Thread Name", "bot-token")
assert "chan-5" in captured["url"]
assert "msg-5" in captured["url"]
assert "threads" in captured["url"]
def test_create_discord_thread_failure(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(
"platform.notifications.delivery_transport.httpx.post",
lambda *_a, **_kw: _mock_response(403, {"message": "Forbidden"}),
)
ok, error, thread_id = create_discord_thread("chan-1", "msg-1", "name", "bot-token")
assert ok is False
assert "Forbidden" in error
assert thread_id == ""
def test_create_discord_thread_exception(monkeypatch: pytest.MonkeyPatch) -> None:
def _raise(*_a: Any, **_kw: Any) -> None:
raise TimeoutError("timed out")
monkeypatch.setattr("platform.notifications.delivery_transport.httpx.post", _raise)
ok, error, thread_id = create_discord_thread("chan-1", "msg-1", "name", "bot-token")
assert ok is False
assert "timed out" in error
assert thread_id == ""
# ---------------------------------------------------------------------------
# send_discord_report
# ---------------------------------------------------------------------------
def test_send_discord_report_posts_to_channel(monkeypatch: pytest.MonkeyPatch) -> None:
captured: dict[str, Any] = {}
def _fake_post(url: str, *, json: dict[str, Any], **_kw: Any) -> MagicMock:
captured["url"] = url
captured["embeds"] = json.get("embeds", [])
return _mock_response(200, {"id": "m-1"})
monkeypatch.setattr("platform.notifications.delivery_transport.httpx.post", _fake_post)
ok, error = send_discord_report("Report text", {"channel_id": "chan-1", "bot_token": "tok"})
assert ok is True
assert error == ""
assert "chan-1" in captured["url"]
embed = captured["embeds"][0]
assert embed["description"] == "Report text"
assert embed["title"] == "Investigation Complete"
assert embed["color"] == 15158332
assert embed["footer"]["text"] == "OpenSRE Investigation"
def test_send_discord_report_prefers_thread_over_channel(monkeypatch: pytest.MonkeyPatch) -> None:
captured: dict[str, Any] = {}
def _fake_post(url: str, **_kw: Any) -> MagicMock:
captured["url"] = url
return _mock_response(200, {"id": "m-1"})
monkeypatch.setattr("platform.notifications.delivery_transport.httpx.post", _fake_post)
send_discord_report(
"Report",
{"channel_id": "chan-1", "thread_id": "thread-99", "bot_token": "tok"},
)
assert "thread-99" in captured["url"]
assert "chan-1" not in captured["url"]
def test_send_discord_report_returns_false_on_api_error(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(
"platform.notifications.delivery_transport.httpx.post",
lambda *_a, **_kw: _mock_response(403, {"message": "Forbidden"}),
)
ok, error = send_discord_report("Report", {"channel_id": "chan-1", "bot_token": "tok"})
assert ok is False
assert "Forbidden" in error
def test_send_discord_report_truncates_description_to_4096(monkeypatch: pytest.MonkeyPatch) -> None:
captured: dict[str, Any] = {}
monkeypatch.setattr(
"platform.notifications.delivery_transport.httpx.post",
lambda *_a, **kw: (
captured.update({"embeds": kw["json"].get("embeds", [])})
or _mock_response(200, {"id": "m-1"})
), # type: ignore[misc]
)
long_report = "x" * 5000
send_discord_report(long_report, {"channel_id": "chan-1", "bot_token": "tok"})
description = captured["embeds"][0]["description"]
assert len(description) == 4096
assert description.endswith("…")
# ---------------------------------------------------------------------------
# Shared-transport delegation (regression coverage for the #864 refactor)
# ---------------------------------------------------------------------------
class TestDelegatesToSharedTransport:
"""After #864 the discord 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 ``discord_delivery`` is
caught immediately."""
def test_module_does_not_import_httpx(self) -> None:
# Reuse the module-level ``from integrations.discord_delivery import delivery as discord_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(discord_delivery, "httpx"), (
"discord_delivery should not import httpx directly — "
"it must go through delivery_transport.post_json"
)
def test_post_message_uses_post_json_helper(self, monkeypatch: pytest.MonkeyPatch) -> None:
from platform.notifications.delivery_transport import DeliveryResponse
calls: list[dict[str, Any]] = []
def _stub_post_json(url: str, payload: dict, **kw: Any) -> DeliveryResponse:
calls.append({"url": url, "payload": payload, **kw})
return DeliveryResponse(ok=True, status_code=200, data={"id": "m-via-helper"})
monkeypatch.setattr("integrations.discord.delivery.post_json", _stub_post_json)
ok, _err, mid = post_discord_message("c1", [], "tok", content="hi")
assert ok is True
assert mid == "m-via-helper"
assert calls and calls[0]["url"].endswith("/channels/c1/messages")
assert calls[0]["headers"]["Authorization"] == "Bot tok"
def test_create_thread_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, **kw: Any) -> DeliveryResponse:
captured["url"] = url
captured["payload"] = payload
return DeliveryResponse(ok=True, status_code=201, data={"id": "thread-9"})
monkeypatch.setattr("integrations.discord.delivery.post_json", _stub_post_json)
ok, _err, tid = create_discord_thread("c1", "m1", "Investigation", "tok")
assert ok is True
assert tid == "thread-9"
assert "/messages/m1/threads" in captured["url"]
assert captured["payload"]["name"] == "Investigation"
assert captured["payload"]["auto_archive_duration"] == 1440
# ---------------------------------------------------------------------------
# Issue #865 Discord hardening: non-JSON bodies and token redaction
# ---------------------------------------------------------------------------
class TestDiscordNonJsonBody:
def test_post_discord_message_handles_html_error_body(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
from platform.notifications.delivery_transport import DeliveryResponse
monkeypatch.setattr(
"integrations.discord.delivery.post_json",
lambda *_a, **_kw: DeliveryResponse(
ok=True,
status_code=502,
data={},
text="<html>Bad Gateway</html>",
),
)
ok, error, message_id = discord_delivery.post_discord_message(
"chan-1", [{"title": "Alert"}], "bot-token"
)
assert ok is False
assert "<html>Bad Gateway</html>" in error
assert message_id == ""
def test_create_discord_thread_handles_html_error_body(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
from platform.notifications.delivery_transport import DeliveryResponse
monkeypatch.setattr(
"integrations.discord.delivery.post_json",
lambda *_a, **_kw: DeliveryResponse(
ok=True,
status_code=502,
data={},
text="<html>Bad Gateway</html>",
),
)
ok, error, thread_id = discord_delivery.create_discord_thread(
"chan-1", "msg-1", "Test Thread", "bot-token"
)
assert ok is False
assert "<html>Bad Gateway</html>" in error
assert thread_id == ""
class TestDiscordExceptionRedaction:
def test_exception_error_redacts_token(self, monkeypatch: pytest.MonkeyPatch) -> None:
from platform.notifications.delivery_transport import DeliveryResponse
token = "MTIzNDU2Nzg5.MTg4NjY2.NqIIjOjHrFJzE5jgwSGM1Nz"
leak_msg = f"connect failed with {token}"
monkeypatch.setattr(
"integrations.discord.delivery.post_json",
lambda *_a, **_kw: DeliveryResponse(ok=False, error=leak_msg),
)
ok, error, message_id = discord_delivery.post_discord_message(
"chan-1", [{"title": "Alert"}], token
)
assert ok is False
assert token not in error
assert "<redacted>" in error
assert message_id == ""
def test_send_discord_report_returns_redacted_error(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
from platform.notifications.delivery_transport import DeliveryResponse
token = "MTIzNDU2Nzg5.MTg4NjY2.NqIIjOjHrFJzE5jgwSGM1Nz"
leak_msg = f"connect failed with {token}"
monkeypatch.setattr(
"integrations.discord.delivery.post_json",
lambda *_a, **_kw: DeliveryResponse(ok=False, error=leak_msg),
)
ok, error = discord_delivery.send_discord_report(
"Report", {"channel_id": "c1", "bot_token": token}
)
assert ok is False
assert token not in error
assert "<redacted>" in error
class TestDiscordExceptionLogRedaction:
def test_exception_log_redacts_token(
self, monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
) -> None:
from platform.notifications.delivery_transport import DeliveryResponse
token = "MTIzNDU2Nzg5.MTg4NjY2.NqIIjOjHrFJzE5jgwSGM1Nz"
leak_msg = f"connect failed with {token}"
monkeypatch.setattr(
"integrations.discord.delivery.post_json",
lambda *_a, **_kw: DeliveryResponse(ok=False, error=leak_msg),
)
with caplog.at_level(logging.WARNING, logger="integrations.discord.delivery"):
discord_delivery.post_discord_message("chan-1", [{"title": "Alert"}], token)
joined = " ".join(rec.getMessage() for rec in caplog.records)
assert token not in joined
assert "<redacted>" in joined