mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 22:34:18 +08:00
_post_feishu 的退避判断与最终 WARNING 仍用模块常量 _FEISHU_MAX_ATTEMPTS(3), 设置页「发送测试消息」传 max_attempts=1 时: 唯一一次失败后仍 sleep 1 秒才返回, 日志写「已重试 3 次」而实际只试了 1 次。两处改用 max_attempts; 生产路径不变。 测试: 单次尝试不退避且日志计数为 1 (未修复时失败), 默认 3 次仍退避 1s、2s。
141 lines
4.7 KiB
Python
141 lines
4.7 KiB
Python
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import hmac
|
|
import json
|
|
import logging
|
|
|
|
from app.services import email_adapter, webhook_adapter
|
|
|
|
|
|
def test_custom_webhook_posts_stable_signed_json(monkeypatch):
|
|
captured = {}
|
|
|
|
class Response:
|
|
status_code = 204
|
|
text = ""
|
|
|
|
def fake_post(url, *, content, headers, timeout):
|
|
captured.update(url=url, content=content, headers=headers, timeout=timeout)
|
|
return Response()
|
|
|
|
monkeypatch.setattr("httpx.post", fake_post)
|
|
monkeypatch.setattr(webhook_adapter.time, "time", lambda: 1_700_000_000)
|
|
|
|
assert webhook_adapter.send_custom(
|
|
"https://example.com/tickflow",
|
|
"价格预警",
|
|
"600000.SH 触发",
|
|
"monitor_alert",
|
|
{"symbol": "600000.SH"},
|
|
"shared-secret",
|
|
)
|
|
payload = json.loads(captured["content"])
|
|
assert payload == {
|
|
"event": "monitor_alert",
|
|
"timestamp": 1_700_000_000,
|
|
"title": "价格预警",
|
|
"body": "600000.SH 触发",
|
|
"data": {"symbol": "600000.SH"},
|
|
}
|
|
expected = hmac.new(b"shared-secret", captured["content"], hashlib.sha256).hexdigest()
|
|
assert captured["headers"]["X-TickFlow-Signature"] == f"sha256={expected}"
|
|
assert captured["headers"]["X-TickFlow-Timestamp"] == "1700000000"
|
|
|
|
|
|
def test_custom_webhook_rejects_non_http_urls(monkeypatch):
|
|
monkeypatch.setattr("httpx.post", lambda *args, **kwargs: (_ for _ in ()).throw(AssertionError))
|
|
assert not webhook_adapter.send_custom("file:///tmp/hook", "title", "body", "test")
|
|
|
|
|
|
def test_email_adapter_uses_starttls_login_and_multiple_recipients(monkeypatch):
|
|
instances = []
|
|
|
|
class FakeSmtp:
|
|
def __init__(self, host, port, timeout):
|
|
self.host = host
|
|
self.port = port
|
|
self.timeout = timeout
|
|
self.calls = []
|
|
instances.append(self)
|
|
|
|
def ehlo(self):
|
|
self.calls.append(("ehlo",))
|
|
|
|
def starttls(self):
|
|
self.calls.append(("starttls",))
|
|
|
|
def login(self, username, password):
|
|
self.calls.append(("login", username, password))
|
|
|
|
def send_message(self, message):
|
|
self.calls.append(("send", message))
|
|
|
|
def quit(self):
|
|
self.calls.append(("quit",))
|
|
|
|
def close(self):
|
|
self.calls.append(("close",))
|
|
|
|
monkeypatch.setattr(email_adapter.smtplib, "SMTP", FakeSmtp)
|
|
config = {
|
|
"host": "smtp.example.com",
|
|
"port": 587,
|
|
"security": "starttls",
|
|
"username": "bot@example.com",
|
|
"from_address": "bot@example.com",
|
|
"to_addresses": ["one@example.com", "two@example.com"],
|
|
}
|
|
|
|
assert email_adapter.send_email(config, "smtp-password", "监控告警", "正文")
|
|
smtp = instances[0]
|
|
assert smtp.calls[:4] == [
|
|
("ehlo",),
|
|
("starttls",),
|
|
("ehlo",),
|
|
("login", "bot@example.com", "smtp-password"),
|
|
]
|
|
message = next(call[1] for call in smtp.calls if call[0] == "send")
|
|
assert message["To"] == "one@example.com, two@example.com"
|
|
assert message["Subject"] == "监控告警"
|
|
assert smtp.calls[-1] == ("quit",)
|
|
|
|
|
|
def _unreachable(*_args, **_kwargs):
|
|
raise ConnectionError("unreachable")
|
|
|
|
|
|
def test_feishu_single_attempt_returns_without_backoff_and_logs_the_real_count(monkeypatch, caplog):
|
|
# 设置页「发送测试消息」传 max_attempts=1: 失败即返回, 不等退避, 日志计数如实。
|
|
sleeps: list[float] = []
|
|
monkeypatch.setattr(webhook_adapter.time, "sleep", lambda seconds: sleeps.append(seconds))
|
|
monkeypatch.setattr("httpx.post", _unreachable)
|
|
with caplog.at_level(logging.WARNING, logger=webhook_adapter.__name__):
|
|
ok = webhook_adapter.send_feishu(
|
|
"https://open.feishu.cn/open-apis/bot/v2/hook/abc", "标题", "正文", max_attempts=1
|
|
)
|
|
assert ok is False
|
|
assert sleeps == []
|
|
assert "已重试 1 次" in caplog.text
|
|
|
|
|
|
def test_feishu_production_retries_keep_backoff(monkeypatch, caplog):
|
|
# 生产路径 (默认 3 次) 的退避与日志不变: 1s、2s 两次退避, 日志写 3 次。
|
|
sleeps: list[float] = []
|
|
calls = {"n": 0}
|
|
|
|
def unreachable(*_args, **_kwargs):
|
|
calls["n"] += 1
|
|
raise ConnectionError("unreachable")
|
|
|
|
monkeypatch.setattr(webhook_adapter.time, "sleep", lambda seconds: sleeps.append(seconds))
|
|
monkeypatch.setattr("httpx.post", unreachable)
|
|
with caplog.at_level(logging.WARNING, logger=webhook_adapter.__name__):
|
|
ok = webhook_adapter.send_feishu(
|
|
"https://open.feishu.cn/open-apis/bot/v2/hook/abc", "标题", "正文"
|
|
)
|
|
assert ok is False
|
|
assert calls["n"] == 3
|
|
assert sleeps == [1, 2]
|
|
assert "已重试 3 次" in caplog.text
|