feat: 添加飞书用户模块和订阅功能支持 - 新增feishu_users模块用于处理飞书用户身份验证和权限管理 - 新增subscriptions模块用于处理订阅相关功能 - 新增personalization模块用于个性化服务 - 在alembic迁移配置中注册新的模型模块 - 在API路由器中添加feishu_users和subscriptions路由 - 实现事件调度服务的改进,包括错误处理和状态更新优化 - 添加飞书命令处理的权限检查机制 - 实现飞书应用票据事件处理 - 改进审计日志记录功能 ```
158 lines
4.4 KiB
Python
158 lines
4.4 KiB
Python
from collections.abc import Iterator
|
|
from typing import Any
|
|
|
|
import pytest
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.application.feishu.commands import FeishuCommandService
|
|
from app.core.config import get_settings
|
|
from app.core.database import Base
|
|
from app.modules.audit.models import AuditLog
|
|
from app.modules.feishu.service import FeishuService
|
|
from app.modules.feishu_users.constants import FeishuUserRole, FeishuUserStatus
|
|
from app.modules.feishu_users.principal import FeishuPrincipal
|
|
|
|
|
|
class RecordingFeishuClient:
|
|
def __init__(self) -> None:
|
|
self.calls: list[dict[str, Any]] = []
|
|
|
|
def send_text(
|
|
self,
|
|
text: str,
|
|
receive_id: str | None,
|
|
receive_id_type: str,
|
|
uuid: str | None,
|
|
tenant_key: str | None = None,
|
|
) -> dict[str, Any]:
|
|
self.calls.append(
|
|
{
|
|
"operation": "text",
|
|
"receive_id": receive_id,
|
|
"tenant_key": tenant_key,
|
|
"uuid": uuid,
|
|
}
|
|
)
|
|
return {"code": 0, "data": {"message_id": "om-text"}}
|
|
|
|
def send_card(
|
|
self,
|
|
card: dict[str, Any],
|
|
receive_id: str | None,
|
|
receive_id_type: str,
|
|
uuid: str | None,
|
|
tenant_key: str | None = None,
|
|
) -> dict[str, Any]:
|
|
self.calls.append(
|
|
{
|
|
"operation": "card",
|
|
"receive_id": receive_id,
|
|
"tenant_key": tenant_key,
|
|
"uuid": uuid,
|
|
}
|
|
)
|
|
return {"code": 0, "data": {"message_id": "om-card"}}
|
|
|
|
def upload_image(
|
|
self,
|
|
image: bytes,
|
|
filename: str = "lifecycle-report.png",
|
|
tenant_key: str | None = None,
|
|
) -> dict[str, Any]:
|
|
self.calls.append(
|
|
{
|
|
"operation": "image",
|
|
"filename": filename,
|
|
"tenant_key": tenant_key,
|
|
}
|
|
)
|
|
return {"code": 0, "data": {"image_key": "img-key"}}
|
|
|
|
|
|
@pytest.fixture
|
|
def database(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> Iterator[Session]:
|
|
monkeypatch.setenv("FEISHU_APP_ID", "cli-routing")
|
|
monkeypatch.setenv("FEISHU_APP_SECRET", "routing-secret")
|
|
monkeypatch.setenv("FEISHU_DEFAULT_TENANT_KEY", "tenant-default")
|
|
monkeypatch.setenv("FEISHU_USER_FEATURES_ENABLED", "true")
|
|
get_settings.cache_clear()
|
|
engine = create_engine("sqlite://")
|
|
Base.metadata.create_all(engine, tables=[AuditLog.__table__])
|
|
try:
|
|
with Session(engine) as db:
|
|
yield db
|
|
finally:
|
|
engine.dispose()
|
|
get_settings.cache_clear()
|
|
|
|
|
|
def test_service_routes_text_card_and_image_through_selected_tenant(
|
|
database: Session,
|
|
) -> None:
|
|
service = FeishuService(database)
|
|
client = RecordingFeishuClient()
|
|
service.client = client
|
|
|
|
service.send_text("默认消息", receive_id="oc-default")
|
|
service.set_tenant_key("tenant-a")
|
|
service.send_card({"elements": []}, receive_id="oc-a")
|
|
service.upload_image(b"png")
|
|
service.send_text(
|
|
"显式覆盖",
|
|
receive_id="oc-b",
|
|
tenant_key="tenant-b",
|
|
)
|
|
|
|
assert [
|
|
(item["operation"], item["tenant_key"])
|
|
for item in client.calls
|
|
] == [
|
|
("text", "tenant-default"),
|
|
("card", "tenant-a"),
|
|
("image", "tenant-a"),
|
|
("text", "tenant-b"),
|
|
]
|
|
|
|
|
|
def test_command_reply_uses_verified_principal_tenant(
|
|
database: Session,
|
|
) -> None:
|
|
commands = FeishuCommandService(database)
|
|
client = RecordingFeishuClient()
|
|
commands.feishu.client = client
|
|
principal = FeishuPrincipal(
|
|
owner_id=1,
|
|
user_code="FSU-routing",
|
|
tenant_key="tenant-principal",
|
|
open_id="ou-routing",
|
|
union_id=None,
|
|
feishu_user_id=None,
|
|
role=FeishuUserRole.USER,
|
|
status=FeishuUserStatus.ACTIVE,
|
|
timezone="Asia/Shanghai",
|
|
quiet_hours_start=None,
|
|
quiet_hours_end=None,
|
|
chat_id="oc-routing",
|
|
chat_type="p2p",
|
|
)
|
|
|
|
result = commands.handle_text(
|
|
"帮助",
|
|
auto_reply=True,
|
|
principal=principal,
|
|
)
|
|
|
|
assert result["command"] == "help"
|
|
assert commands.feishu.tenant_key == "tenant-principal"
|
|
assert client.calls == [
|
|
{
|
|
"operation": "text",
|
|
"receive_id": "oc-routing",
|
|
"tenant_key": "tenant-principal",
|
|
"uuid": None,
|
|
}
|
|
]
|