```
feat(feishu): 添加飞书入站事件inbox和混合数据库协调功能 - 实现飞书入站事件持久化inbox机制,支持状态管理、租约锁定和重试退避 - 添加混合数据库基线协调工具,确保平台PostgreSQL结构安全对齐 - 增加运行组件心跳检测和readiness就绪检查机制 - 实现app_ticket事件的安全轮换和验证处理 - 添加生产环境运行编排和fail-closed安全机制 - 支持webhook快速确认和长连接独立进程处理 - 完善个人数据擦除时的待处理事件清理功能 ```
This commit is contained in:
@@ -10,7 +10,7 @@ from sqlalchemy import create_engine, func, select
|
||||
from sqlalchemy.orm import Session, sessionmaker
|
||||
from sqlalchemy.pool import StaticPool
|
||||
|
||||
from app.application.feishu.events import FeishuEventService
|
||||
from app.application.feishu.events import FeishuEventService, _identifier_digest
|
||||
from app.core.config import get_settings
|
||||
from app.core.database import Base
|
||||
from app.modules.audit.models import AuditLog
|
||||
@@ -18,6 +18,7 @@ from app.modules.feishu import long_connection
|
||||
from app.modules.feishu.app_tickets import FeishuAppTicketService
|
||||
from app.modules.feishu.constants import FeishuEventSource
|
||||
from app.modules.feishu.models import FeishuAppTicket, FeishuEventReceipt
|
||||
from app.modules.feishu.services import FeishuInboundService
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -26,6 +27,7 @@ def session_factory(
|
||||
) -> Iterator[sessionmaker[Session]]:
|
||||
monkeypatch.setenv("FEISHU_APP_ID", "cli-ticket-app")
|
||||
monkeypatch.setenv("FEISHU_APP_SECRET", "ticket-app-secret")
|
||||
monkeypatch.setenv("FEISHU_EVENT_TRANSPORT", "long_connection")
|
||||
monkeypatch.setenv("FEISHU_VERIFICATION_TOKEN", "ticket-token")
|
||||
monkeypatch.setenv("FEISHU_USER_FEATURES_ENABLED", "false")
|
||||
get_settings.cache_clear()
|
||||
@@ -135,6 +137,9 @@ def test_verified_ticket_is_deduplicated_rotated_and_never_leaked(
|
||||
assert current.received_at >= first_received_at
|
||||
assert db.scalar(select(func.count()).select_from(FeishuAppTicket)) == 1
|
||||
assert db.scalar(select(func.count()).select_from(FeishuEventReceipt)) == 2
|
||||
receipts = list(db.execute(select(FeishuEventReceipt)).scalars())
|
||||
assert all(item.status == "succeeded" for item in receipts)
|
||||
assert all(item.payload is None for item in receipts)
|
||||
assert rotated_ticket not in json.dumps(rotated, ensure_ascii=False)
|
||||
|
||||
audits = list(db.execute(select(AuditLog)).scalars())
|
||||
@@ -196,6 +201,59 @@ def test_only_verified_matching_app_ticket_events_can_write(
|
||||
assert db.scalar(select(FeishuAppTicket)) is None
|
||||
|
||||
|
||||
def test_app_ticket_write_and_receipt_success_are_atomic(
|
||||
session_factory: sessionmaker[Session],
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
payload = _v2_ticket_event("atomic-app-ticket", "atomic-ticket-secret")
|
||||
original_mark_success = FeishuInboundService._mark_success
|
||||
fail_before_commit = True
|
||||
|
||||
def mark_success(
|
||||
atomic_db: Session,
|
||||
event_key: str,
|
||||
lock_owner: str,
|
||||
current: Any,
|
||||
) -> None:
|
||||
nonlocal fail_before_commit
|
||||
if fail_before_commit:
|
||||
fail_before_commit = False
|
||||
raise RuntimeError("simulated crash before atomic app-ticket commit")
|
||||
original_mark_success(atomic_db, event_key, lock_owner, current)
|
||||
|
||||
monkeypatch.setattr(
|
||||
FeishuInboundService,
|
||||
"_mark_success",
|
||||
staticmethod(mark_success),
|
||||
)
|
||||
with session_factory() as db:
|
||||
service = FeishuEventService(db)
|
||||
with pytest.raises(HTTPException) as first_error:
|
||||
service._handle_verified_event(
|
||||
payload,
|
||||
source=FeishuEventSource.WEBHOOK,
|
||||
)
|
||||
|
||||
assert first_error.value.status_code == 503
|
||||
assert db.scalar(select(FeishuAppTicket)) is None
|
||||
receipt = db.scalar(select(FeishuEventReceipt))
|
||||
assert receipt is not None
|
||||
assert receipt.status == "retry"
|
||||
|
||||
result = service._handle_verified_event(
|
||||
payload,
|
||||
source=FeishuEventSource.WEBHOOK,
|
||||
)
|
||||
|
||||
assert result == {"ok": True, "handled": True}
|
||||
assert FeishuAppTicketService(db).get_ticket("cli-ticket-app") == (
|
||||
"atomic-ticket-secret"
|
||||
)
|
||||
db.refresh(receipt)
|
||||
assert receipt.status == "succeeded"
|
||||
assert receipt.attempt_count == 2
|
||||
|
||||
|
||||
def test_v1_app_ticket_payload_uses_uuid_receipt(
|
||||
session_factory: sessionmaker[Session],
|
||||
) -> None:
|
||||
@@ -209,8 +267,18 @@ def test_v1_app_ticket_payload_uses_uuid_receipt(
|
||||
receipt = db.scalar(select(FeishuEventReceipt))
|
||||
assert result == {"ok": True, "handled": True}
|
||||
assert receipt is not None
|
||||
assert receipt.event_id == "v1-ticket-uuid"
|
||||
assert receipt.event_key == "cli-ticket-app:app_ticket:v1-ticket-uuid"
|
||||
assert receipt.event_id == _identifier_digest(
|
||||
"v1-ticket-uuid",
|
||||
"event-id",
|
||||
)
|
||||
assert receipt.event_key == _identifier_digest(
|
||||
"cli-ticket-app:app_ticket:v1-ticket-uuid",
|
||||
"event-key",
|
||||
)
|
||||
assert "v1-ticket-uuid" not in receipt.event_id
|
||||
assert "cli-ticket-app" not in receipt.event_key
|
||||
assert receipt.status == "succeeded"
|
||||
assert receipt.payload is None
|
||||
assert FeishuAppTicketService(db).get_ticket("cli-ticket-app") == ticket
|
||||
assert ticket not in json.dumps(result, ensure_ascii=False)
|
||||
|
||||
@@ -257,6 +325,7 @@ def test_long_connection_registers_custom_app_ticket_handler(
|
||||
monkeypatch.setitem(sys.modules, "lark_oapi", fake_lark)
|
||||
monkeypatch.setenv("FEISHU_APP_ID", "cli-ticket-app")
|
||||
monkeypatch.setenv("FEISHU_APP_SECRET", "ticket-app-secret")
|
||||
monkeypatch.setenv("FEISHU_EVENT_TRANSPORT", "long_connection")
|
||||
monkeypatch.setenv("FEISHU_VERIFICATION_TOKEN", "ticket-token")
|
||||
get_settings.cache_clear()
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user