166 lines
5.8 KiB
Python
166 lines
5.8 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
from gyxx_flow.adapters import feishu_messages
|
|
|
|
|
|
def _completed(payload: dict, *, returncode: int = 0) -> SimpleNamespace:
|
|
return SimpleNamespace(
|
|
returncode=returncode,
|
|
stdout=json.dumps(payload),
|
|
stderr="",
|
|
)
|
|
|
|
|
|
def test_bot_text_send_has_one_fixed_profile_identity_and_stable_key(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
commands: list[list[str]] = []
|
|
monkeypatch.setattr(feishu_messages, "_lark_command", lambda: ["node", "run.js"])
|
|
monkeypatch.setattr(
|
|
feishu_messages,
|
|
"resolve_notification_recipients",
|
|
lambda recipients, *, app_profile: tuple(recipients),
|
|
)
|
|
|
|
def fake_run(command: list[str], **_kwargs: object) -> SimpleNamespace:
|
|
commands.append(command)
|
|
return _completed({"message_id": "om_receipt123", "chat_id": "oc_chat123"})
|
|
|
|
monkeypatch.setattr(feishu_messages.subprocess, "run", fake_run)
|
|
|
|
first = feishu_messages.send_lark_bot_message(
|
|
user_id="ou_recipient123",
|
|
text="告警正文",
|
|
idempotency_key="product.alert:2026-08-10",
|
|
)
|
|
second = feishu_messages.send_lark_bot_message(
|
|
user_id="ou_recipient123",
|
|
text="告警正文",
|
|
idempotency_key="product.alert:2026-08-10",
|
|
)
|
|
|
|
assert first["message_id"] == second["message_id"] == "om_receipt123"
|
|
assert len(commands) == 2
|
|
assert commands[0][:4] == ["node", "run.js", "--profile", "hermes-analyzer"]
|
|
assert commands[0][commands[0].index("--as") + 1] == "bot"
|
|
assert commands[0][commands[0].index("--user-id") + 1] == "ou_recipient123"
|
|
assert commands[0][commands[0].index("--text") + 1] == "告警正文"
|
|
key_index = commands[0].index("--idempotency-key") + 1
|
|
assert commands[0][key_index] == commands[1][key_index]
|
|
assert commands[0][key_index].startswith("gyxx-")
|
|
assert len(commands[0][key_index]) == 45
|
|
|
|
|
|
def test_interactive_bot_send_preserves_card_json_and_nested_receipt(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
observed: dict[str, object] = {}
|
|
monkeypatch.setattr(feishu_messages, "_lark_command", lambda: ["node", "run.js"])
|
|
monkeypatch.setattr(
|
|
feishu_messages,
|
|
"resolve_notification_recipients",
|
|
lambda recipients, *, app_profile: tuple(recipients),
|
|
)
|
|
|
|
def fake_run(command: list[str], **kwargs: object) -> SimpleNamespace:
|
|
observed["command"] = command
|
|
observed["kwargs"] = kwargs
|
|
return _completed({"data": {"message_id": "om_nested-123"}})
|
|
|
|
monkeypatch.setattr(feishu_messages.subprocess, "run", fake_run)
|
|
card = {"header": {"title": {"tag": "plain_text", "content": "日报"}}}
|
|
|
|
result = feishu_messages.send_lark_bot_message(
|
|
user_id="ou_recipient123",
|
|
content=card,
|
|
msg_type="interactive",
|
|
)
|
|
|
|
command = observed["command"]
|
|
assert isinstance(command, list)
|
|
assert json.loads(command[command.index("--content") + 1]) == card
|
|
assert command[command.index("--msg-type") + 1] == "interactive"
|
|
assert command[command.index("--as") + 1] == "bot"
|
|
assert result["message_id"] == "om_nested-123"
|
|
|
|
|
|
def test_chat_and_image_messages_use_bot_identity(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
commands: list[list[str]] = []
|
|
image = tmp_path / "qr.png"
|
|
image.write_bytes(b"png")
|
|
monkeypatch.setattr(feishu_messages, "_lark_command", lambda: ["node", "run.js"])
|
|
|
|
def fake_run(command: list[str], **_kwargs: object) -> SimpleNamespace:
|
|
commands.append(command)
|
|
return _completed({"message_id": f"om_image{len(commands)}"})
|
|
|
|
monkeypatch.setattr(feishu_messages.subprocess, "run", fake_run)
|
|
|
|
feishu_messages.send_lark_bot_message(chat_id="oc_group123", text="群通知")
|
|
feishu_messages.send_lark_bot_message(user_id="ou_owner123", image=image)
|
|
|
|
assert commands[0][commands[0].index("--chat-id") + 1] == "oc_group123"
|
|
assert commands[1][commands[1].index("--image") + 1] == "./qr.png"
|
|
assert all(command[command.index("--as") + 1] == "bot" for command in commands)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("completed", "message"),
|
|
(
|
|
(_completed({}, returncode=1), "exit=1"),
|
|
(_completed({"ok": False, "error": "denied"}), "denied"),
|
|
(_completed({"id": "chatcmpl-not-a-receipt"}), "no valid message_id"),
|
|
),
|
|
)
|
|
def test_bot_send_fails_without_a_verified_lark_receipt(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
completed: SimpleNamespace,
|
|
message: str,
|
|
) -> None:
|
|
monkeypatch.setattr(feishu_messages, "_lark_command", lambda: ["node", "run.js"])
|
|
monkeypatch.setattr(
|
|
feishu_messages,
|
|
"resolve_notification_recipients",
|
|
lambda recipients, *, app_profile: tuple(recipients),
|
|
)
|
|
monkeypatch.setattr(feishu_messages.subprocess, "run", lambda *_args, **_kwargs: completed)
|
|
|
|
with pytest.raises(RuntimeError, match=message):
|
|
feishu_messages.send_lark_bot_message(
|
|
user_id="ou_recipient123",
|
|
text="告警正文",
|
|
)
|
|
|
|
|
|
def test_sender_accepts_collector_bot_and_rejects_other_profiles(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr(feishu_messages, "_lark_command", lambda: ["node", "run.js"])
|
|
monkeypatch.setattr(
|
|
feishu_messages.subprocess,
|
|
"run",
|
|
lambda *_args, **_kwargs: _completed({"message_id": "om_collector123"}),
|
|
)
|
|
result = feishu_messages.send_lark_bot_message(
|
|
user_id="ou_recipient123",
|
|
text="采集心跳",
|
|
profile=feishu_messages.COLLECTOR_NOTIFICATION_APP_PROFILE,
|
|
)
|
|
assert result["message_id"] == "om_collector123"
|
|
|
|
with pytest.raises(ValueError, match="Hermes application profile"):
|
|
feishu_messages.send_lark_bot_message(
|
|
user_id="ou_recipient123",
|
|
text="告警正文",
|
|
profile="collector",
|
|
)
|