feat: add feishu api notification services
This commit is contained in:
200
tests/test_feishu_api_services.py
Normal file
200
tests/test_feishu_api_services.py
Normal file
@@ -0,0 +1,200 @@
|
||||
import json
|
||||
|
||||
from django.utils import timezone
|
||||
import pytest
|
||||
|
||||
from review_agent.models import FeishuAccessTokenCache
|
||||
from review_agent.notifications.context import NotificationContext
|
||||
from review_agent.notifications.feishu_message_api import send_personal_message
|
||||
from review_agent.notifications.feishu_token import app_id_hash, get_tenant_access_token
|
||||
from review_agent.notifications.message_builder import build_feishu_post_message
|
||||
from review_agent.notifications.recipient import resolve_configured_personal_recipient
|
||||
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
class FakeResponse:
|
||||
def __init__(self, payload, status_code=200):
|
||||
self.payload = payload
|
||||
self.status_code = status_code
|
||||
self.text = json.dumps(payload, ensure_ascii=False)
|
||||
|
||||
def json(self):
|
||||
return self.payload
|
||||
|
||||
|
||||
def test_token_service_fetches_and_caches(monkeypatch, settings):
|
||||
settings.FEISHU_APP_ID = "cli_a"
|
||||
settings.FEISHU_APP_SECRET = "secret"
|
||||
calls = []
|
||||
|
||||
def fake_post(*args, **kwargs):
|
||||
calls.append(kwargs)
|
||||
return FakeResponse({"code": 0, "tenant_access_token": "tenant-token", "expire": 7200})
|
||||
|
||||
monkeypatch.setattr("review_agent.notifications.feishu_token.httpx.post", fake_post)
|
||||
|
||||
first = get_tenant_access_token()
|
||||
second = get_tenant_access_token()
|
||||
|
||||
assert first.ok
|
||||
assert second.tenant_access_token == "tenant-token"
|
||||
assert len(calls) == 1
|
||||
assert FeishuAccessTokenCache.objects.get(app_id_hash=app_id_hash("cli_a")).is_valid()
|
||||
|
||||
|
||||
def test_token_service_refreshes_expired_cache(monkeypatch, settings):
|
||||
settings.FEISHU_APP_ID = "cli_a"
|
||||
settings.FEISHU_APP_SECRET = "secret"
|
||||
FeishuAccessTokenCache.objects.create(
|
||||
app_id_hash=app_id_hash("cli_a"),
|
||||
tenant_access_token="old",
|
||||
expires_at=timezone.now() - timezone.timedelta(minutes=1),
|
||||
)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"review_agent.notifications.feishu_token.httpx.post",
|
||||
lambda *args, **kwargs: FakeResponse({"code": 0, "tenant_access_token": "new", "expire": 7200}),
|
||||
)
|
||||
|
||||
assert get_tenant_access_token().tenant_access_token == "new"
|
||||
|
||||
|
||||
def test_token_service_returns_error_for_api_failure(monkeypatch, settings):
|
||||
settings.FEISHU_APP_ID = "cli_a"
|
||||
settings.FEISHU_APP_SECRET = "secret"
|
||||
monkeypatch.setattr(
|
||||
"review_agent.notifications.feishu_token.httpx.post",
|
||||
lambda *args, **kwargs: FakeResponse({"code": 1, "msg": "bad secret"}),
|
||||
)
|
||||
|
||||
result = get_tenant_access_token()
|
||||
|
||||
assert not result.ok
|
||||
assert result.error_message == "bad secret"
|
||||
|
||||
|
||||
def test_recipient_prefers_open_id(settings):
|
||||
settings.FEISHU_DEFAULT_USER_OPEN_ID = "ou_xxx"
|
||||
settings.FEISHU_DEFAULT_USER_ID = "user_xxx"
|
||||
settings.FEISHU_DEFAULT_TARGET_NAME = "负责人"
|
||||
|
||||
target = resolve_configured_personal_recipient()
|
||||
|
||||
assert target.ok
|
||||
assert target.identifier_type == "open_id"
|
||||
assert target.identifier_value == "ou_xxx"
|
||||
|
||||
|
||||
def test_recipient_uses_user_id_when_open_id_missing(settings):
|
||||
settings.FEISHU_DEFAULT_USER_OPEN_ID = ""
|
||||
settings.FEISHU_DEFAULT_USER_ID = "user_xxx"
|
||||
|
||||
target = resolve_configured_personal_recipient()
|
||||
|
||||
assert target.ok
|
||||
assert target.identifier_type == "user_id"
|
||||
|
||||
|
||||
def test_recipient_missing(settings):
|
||||
settings.FEISHU_DEFAULT_USER_OPEN_ID = ""
|
||||
settings.FEISHU_DEFAULT_USER_ID = ""
|
||||
|
||||
target = resolve_configured_personal_recipient()
|
||||
|
||||
assert not target.ok
|
||||
assert target.error_code == "recipient_missing"
|
||||
|
||||
|
||||
def test_build_feishu_post_message_contains_summary(settings):
|
||||
settings.PUBLIC_BASE_URL = "http://example.test"
|
||||
settings.FEISHU_DEFAULT_USER_OPEN_ID = "ou_xxx"
|
||||
target = resolve_configured_personal_recipient()
|
||||
context = NotificationContext(
|
||||
workflow_type="file_summary",
|
||||
workflow_name="自动汇总",
|
||||
workflow_batch_id=1,
|
||||
workflow_batch_no="FS-001",
|
||||
workflow_status="success",
|
||||
trigger_user_id=1,
|
||||
trigger_username="owner",
|
||||
title="自动汇总完成",
|
||||
summary_lines=("文件 3 个", "异常 0 个"),
|
||||
next_step="查看汇总结果",
|
||||
result_path="/summary/1/",
|
||||
)
|
||||
|
||||
payload = build_feishu_post_message(context, target)
|
||||
|
||||
assert payload["receive_id"] == "ou_xxx"
|
||||
content = json.loads(payload["content"])
|
||||
assert content["zh_cn"]["title"] == "自动汇总完成"
|
||||
assert "http://example.test/summary/1/" in payload["content"]
|
||||
|
||||
|
||||
def test_send_personal_message_success(monkeypatch, settings):
|
||||
settings.FEISHU_MESSAGE_API_URL = "http://feishu/messages"
|
||||
requests = []
|
||||
|
||||
def fake_post(*args, **kwargs):
|
||||
requests.append(kwargs)
|
||||
return FakeResponse({"code": 0, "data": {"message_id": "om_xxx"}})
|
||||
|
||||
monkeypatch.setattr("review_agent.notifications.feishu_message_api.httpx.post", fake_post)
|
||||
|
||||
result = send_personal_message(
|
||||
tenant_access_token="token",
|
||||
receive_id_type="open_id",
|
||||
payload={"receive_id": "ou_xxx"},
|
||||
)
|
||||
|
||||
assert result.ok
|
||||
assert result.external_message_id == "om_xxx"
|
||||
assert requests[0]["headers"]["Authorization"] == "Bearer token"
|
||||
|
||||
|
||||
def test_send_personal_message_api_error(monkeypatch, settings):
|
||||
settings.FEISHU_MESSAGE_API_URL = "http://feishu/messages"
|
||||
monkeypatch.setattr(
|
||||
"review_agent.notifications.feishu_message_api.httpx.post",
|
||||
lambda *args, **kwargs: FakeResponse({"code": 230001, "msg": "bad receive_id"}),
|
||||
)
|
||||
|
||||
result = send_personal_message(
|
||||
tenant_access_token="token",
|
||||
receive_id_type="open_id",
|
||||
payload={"receive_id": "bad"},
|
||||
)
|
||||
|
||||
assert not result.ok
|
||||
assert result.error_code == "230001"
|
||||
|
||||
|
||||
def test_send_personal_message_refreshes_token_once(monkeypatch, settings):
|
||||
settings.FEISHU_MESSAGE_API_URL = "http://feishu/messages"
|
||||
settings.FEISHU_APP_ID = "cli_a"
|
||||
settings.FEISHU_APP_SECRET = "secret"
|
||||
calls = {"message": 0}
|
||||
|
||||
def fake_message_post(*args, **kwargs):
|
||||
calls["message"] += 1
|
||||
if calls["message"] == 1:
|
||||
return FakeResponse({"code": 99991663, "msg": "token expired"})
|
||||
return FakeResponse({"code": 0, "data": {"message_id": "om_retry"}})
|
||||
|
||||
monkeypatch.setattr("review_agent.notifications.feishu_message_api.httpx.post", fake_message_post)
|
||||
monkeypatch.setattr(
|
||||
"review_agent.notifications.feishu_token.httpx.post",
|
||||
lambda *args, **kwargs: FakeResponse({"code": 0, "tenant_access_token": "fresh", "expire": 7200}),
|
||||
)
|
||||
|
||||
result = send_personal_message(
|
||||
tenant_access_token="stale",
|
||||
receive_id_type="open_id",
|
||||
payload={"receive_id": "ou_xxx"},
|
||||
)
|
||||
|
||||
assert result.ok
|
||||
assert result.refreshed_token
|
||||
assert calls["message"] == 2
|
||||
Reference in New Issue
Block a user