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

410 lines
14 KiB
Python

"""Tests for publish_findings node — _build_mr_note and GitLab MR write-back."""
from __future__ import annotations
from typing import Any
from unittest.mock import MagicMock, patch
import pytest
from tools.investigation.reporting.gitlab_writeback import _build_mr_note
# ---------------------------------------------------------------------------
# _build_mr_note
# ---------------------------------------------------------------------------
def test_build_mr_note_wraps_in_details_block() -> None:
result = _build_mr_note("root cause is X")
assert "<details>" in result
assert "<summary>Investigation summary</summary>" in result
assert "root cause is X" in result
assert "### RCA Finding" in result
def test_build_mr_note_truncates_long_messages() -> None:
long_message = "x" * 5000
result = _build_mr_note(long_message)
assert len(result) < 5000 + 200 # body capped + wrapper overhead
assert result.endswith("</details>")
assert "..." in result
def test_build_mr_note_does_not_truncate_short_messages() -> None:
message = "short message"
result = _build_mr_note(message)
assert "..." not in result
assert message in result
# ---------------------------------------------------------------------------
# GitLab MR write-back in generate_report
# ---------------------------------------------------------------------------
def _make_state(**overrides: Any) -> dict[str, Any]:
base: dict[str, Any] = {
"problem_md": "something broke",
"root_cause_category": "infra",
"slack_context": {},
"organization_slug": None,
"available_sources": {
"gitlab": {
"project_id": "my-org/my-repo",
"merge_request_iid": "5",
"gitlab_url": "https://gitlab.example.com/api/v4",
"gitlab_token": "gl-token",
}
},
"resolved_integrations": {},
}
base.update(overrides)
return base
def _patch_generate_report_deps(monkeypatch: pytest.MonkeyPatch) -> None:
"""Patch all heavy dependencies of generate_report so we can run it in tests."""
from tools.investigation.reporting.formatters.messages import ReportMessages
monkeypatch.setattr(
"tools.investigation.reporting.node.build_report_context",
lambda _state: {},
)
monkeypatch.setattr(
"tools.investigation.reporting.node.build_report_messages",
lambda _ctx: ReportMessages(
slack_text="slack report text",
telegram_html="telegram report text",
whatsapp_text="whatsapp report text",
slack_blocks=[],
),
)
monkeypatch.setattr(
"tools.investigation.reporting.node.create_investigation_and_attach_url",
lambda _state, _msg, _summary: (
"inv-id-123",
"https://app.example.com/inv/1",
),
)
monkeypatch.setattr(
"tools.investigation.reporting.node.render_report",
lambda _msg, **_kw: None,
)
monkeypatch.setattr(
"tools.investigation.reporting.node.open_in_editor",
lambda _msg: None,
)
def test_gitlab_writeback_calls_post_when_enabled(monkeypatch: pytest.MonkeyPatch) -> None:
_patch_generate_report_deps(monkeypatch)
monkeypatch.setenv("GITLAB_MR_WRITEBACK", "true")
mock_send_slack = MagicMock(return_value=(False, None))
mock_post_note = MagicMock(return_value={"id": 1})
mock_build_action_blocks = MagicMock(return_value=[])
with (
patch("integrations.slack.delivery.send_slack_report", mock_send_slack),
patch("integrations.slack.delivery.build_action_blocks", mock_build_action_blocks),
patch(
"tools.investigation.reporting.gitlab_writeback.post_gitlab_mr_note",
mock_post_note,
),
patch(
"tools.investigation.reporting.gitlab_writeback.build_gitlab_config",
return_value=MagicMock(),
),
):
from tools.investigation.reporting.node import generate_report
generate_report(_make_state()) # type: ignore[arg-type]
mock_post_note.assert_called_once()
_, kwargs = mock_post_note.call_args
assert kwargs["project_id"] == "my-org/my-repo"
assert kwargs["mr_iid"] == "5"
def test_gitlab_writeback_skipped_when_env_var_not_set(monkeypatch: pytest.MonkeyPatch) -> None:
_patch_generate_report_deps(monkeypatch)
monkeypatch.delenv("GITLAB_MR_WRITEBACK", raising=False)
mock_post_note = MagicMock()
mock_send_slack = MagicMock(return_value=(False, None))
mock_build_action_blocks = MagicMock(return_value=[])
with (
patch("integrations.slack.delivery.send_slack_report", mock_send_slack),
patch("integrations.slack.delivery.build_action_blocks", mock_build_action_blocks),
patch(
"tools.investigation.reporting.gitlab_writeback.post_gitlab_mr_note",
mock_post_note,
),
):
from tools.investigation.reporting.node import generate_report
generate_report(_make_state()) # type: ignore[arg-type]
mock_post_note.assert_not_called()
def test_gitlab_writeback_skipped_when_mr_iid_missing(monkeypatch: pytest.MonkeyPatch) -> None:
_patch_generate_report_deps(monkeypatch)
monkeypatch.setenv("GITLAB_MR_WRITEBACK", "true")
state = _make_state(
available_sources={"gitlab": {"project_id": "my-org/my-repo", "merge_request_iid": ""}}
)
mock_post_note = MagicMock()
mock_send_slack = MagicMock(return_value=(False, None))
mock_build_action_blocks = MagicMock(return_value=[])
with (
patch("integrations.slack.delivery.send_slack_report", mock_send_slack),
patch("integrations.slack.delivery.build_action_blocks", mock_build_action_blocks),
patch(
"tools.investigation.reporting.gitlab_writeback.post_gitlab_mr_note",
mock_post_note,
),
):
from tools.investigation.reporting.node import generate_report
generate_report(state) # type: ignore[arg-type]
mock_post_note.assert_not_called()
def test_gitlab_writeback_failure_does_not_raise(monkeypatch: pytest.MonkeyPatch) -> None:
_patch_generate_report_deps(monkeypatch)
monkeypatch.setenv("GITLAB_MR_WRITEBACK", "true")
mock_send_slack = MagicMock(return_value=(False, None))
mock_build_action_blocks = MagicMock(return_value=[])
with (
patch("integrations.slack.delivery.send_slack_report", mock_send_slack),
patch("integrations.slack.delivery.build_action_blocks", mock_build_action_blocks),
patch(
"tools.investigation.reporting.gitlab_writeback.post_gitlab_mr_note",
side_effect=RuntimeError("network error"),
),
patch(
"tools.investigation.reporting.gitlab_writeback.build_gitlab_config",
return_value=MagicMock(),
),
):
from tools.investigation.reporting.node import generate_report
result = generate_report(_make_state()) # type: ignore[arg-type]
assert "slack_message" in result # report returned despite write-back failure
def test_generate_report_can_skip_terminal_render_and_editor(
monkeypatch: pytest.MonkeyPatch,
) -> None:
_patch_generate_report_deps(monkeypatch)
mock_send_slack = MagicMock(return_value=(False, None))
mock_build_action_blocks = MagicMock(return_value=[])
mock_render_report = MagicMock()
mock_open_in_editor = MagicMock()
with (
patch("integrations.slack.delivery.send_slack_report", mock_send_slack),
patch("integrations.slack.delivery.build_action_blocks", mock_build_action_blocks),
patch("tools.investigation.reporting.node.render_report", mock_render_report),
patch("tools.investigation.reporting.node.open_in_editor", mock_open_in_editor),
):
from tools.investigation.reporting.node import generate_report
generate_report(
_make_state(), # type: ignore[arg-type]
render_terminal=False,
open_editor=False,
)
mock_render_report.assert_not_called()
mock_open_in_editor.assert_not_called()
def test_openclaw_writeback_calls_delivery_when_configured(
monkeypatch: pytest.MonkeyPatch,
) -> None:
_patch_generate_report_deps(monkeypatch)
mock_send_slack = MagicMock(return_value=(False, None))
mock_build_action_blocks = MagicMock(return_value=[])
mock_openclaw_delivery = MagicMock(return_value=(True, None))
with (
patch("integrations.slack.delivery.send_slack_report", mock_send_slack),
patch("integrations.slack.delivery.build_action_blocks", mock_build_action_blocks),
patch("integrations.openclaw.delivery.send_openclaw_report", mock_openclaw_delivery),
):
from tools.investigation.reporting.node import generate_report
generate_report(
_make_state(
resolved_integrations={
"openclaw": {
"mode": "streamable-http",
"url": "https://openclaw.example.com/mcp",
"auth_token": "tok",
}
}
)
) # type: ignore[arg-type]
mock_openclaw_delivery.assert_called_once()
def test_whatsapp_delivery_uses_twilio_credentials(monkeypatch: pytest.MonkeyPatch) -> None:
_patch_generate_report_deps(monkeypatch)
mock_send_slack = MagicMock(return_value=(False, None))
mock_build_action_blocks = MagicMock(return_value=[])
mock_whatsapp_delivery = MagicMock(return_value=(True, ""))
with (
patch("integrations.slack.delivery.send_slack_report", mock_send_slack),
patch("integrations.slack.delivery.build_action_blocks", mock_build_action_blocks),
patch("integrations.whatsapp.delivery.send_whatsapp_report", mock_whatsapp_delivery),
):
from tools.investigation.reporting.node import generate_report
generate_report(
_make_state(
resolved_integrations={
"whatsapp": {
"account_sid": "AC123",
"auth_token": "tok",
"from_number": "whatsapp:+14155238886",
"default_to": "+1234567890",
}
}
)
) # type: ignore[arg-type]
mock_whatsapp_delivery.assert_called_once_with(
"whatsapp report text",
{
"account_sid": "AC123",
"auth_token": "tok",
"from_number": "whatsapp:+14155238886",
"to": "+1234567890",
},
)
def test_twilio_sms_dispatched_when_enabled(monkeypatch: pytest.MonkeyPatch) -> None:
_patch_generate_report_deps(monkeypatch)
mock_send_slack = MagicMock(return_value=(False, None))
mock_build_action_blocks = MagicMock(return_value=[])
mock_sms = MagicMock(return_value=(True, "", "SM-XYZ"))
with (
patch("integrations.slack.delivery.send_slack_report", mock_send_slack),
patch("integrations.slack.delivery.build_action_blocks", mock_build_action_blocks),
patch("integrations.twilio.delivery.send_twilio_sms_report", mock_sms),
):
from tools.investigation.reporting.node import generate_report
generate_report(
_make_state(
resolved_integrations={
"twilio": {
"account_sid": "AC1",
"auth_token": "tok",
"sms": {
"enabled": True,
"from_number": "+14155551111",
"default_to": "+14155550000",
"messaging_service_sid": "",
},
}
}
)
) # type: ignore[arg-type]
mock_sms.assert_called_once_with(
"whatsapp report text",
{
"account_sid": "AC1",
"auth_token": "tok",
"from_number": "+14155551111",
"messaging_service_sid": "",
"to": "+14155550000",
},
)
def test_twilio_sms_skipped_when_channel_disabled(monkeypatch: pytest.MonkeyPatch) -> None:
_patch_generate_report_deps(monkeypatch)
mock_send_slack = MagicMock(return_value=(False, None))
mock_build_action_blocks = MagicMock(return_value=[])
mock_sms = MagicMock(return_value=(True, "", "SM-X"))
with (
patch("integrations.slack.delivery.send_slack_report", mock_send_slack),
patch("integrations.slack.delivery.build_action_blocks", mock_build_action_blocks),
patch("integrations.twilio.delivery.send_twilio_sms_report", mock_sms),
):
from tools.investigation.reporting.node import generate_report
generate_report(
_make_state(
resolved_integrations={
"twilio": {
"account_sid": "AC1",
"auth_token": "tok",
"sms": {"enabled": False, "from_number": "+14155551111"},
}
}
)
) # type: ignore[arg-type]
mock_sms.assert_not_called()
def test_twilio_sms_skipped_without_recipient(monkeypatch: pytest.MonkeyPatch) -> None:
_patch_generate_report_deps(monkeypatch)
mock_send_slack = MagicMock(return_value=(False, None))
mock_build_action_blocks = MagicMock(return_value=[])
mock_sms = MagicMock(return_value=(True, "", "SM-X"))
with (
patch("integrations.slack.delivery.send_slack_report", mock_send_slack),
patch("integrations.slack.delivery.build_action_blocks", mock_build_action_blocks),
patch("integrations.twilio.delivery.send_twilio_sms_report", mock_sms),
):
from tools.investigation.reporting.node import generate_report
generate_report(
_make_state(
resolved_integrations={
"twilio": {
"account_sid": "AC1",
"auth_token": "tok",
"sms": {
"enabled": True,
"from_number": "+14155551111",
"default_to": "",
"messaging_service_sid": "",
},
}
}
)
) # type: ignore[arg-type]
mock_sms.assert_not_called()