feat: 添加飞书用户模块和订阅功能支持 - 新增feishu_users模块用于处理飞书用户身份验证和权限管理 - 新增subscriptions模块用于处理订阅相关功能 - 新增personalization模块用于个性化服务 - 在alembic迁移配置中注册新的模型模块 - 在API路由器中添加feishu_users和subscriptions路由 - 实现事件调度服务的改进,包括错误处理和状态更新优化 - 添加飞书命令处理的权限检查机制 - 实现飞书应用票据事件处理 - 改进审计日志记录功能 ```
96 lines
2.9 KiB
Python
96 lines
2.9 KiB
Python
from datetime import timedelta
|
|
|
|
from sqlalchemy import create_engine, select
|
|
from sqlalchemy.orm import sessionmaker
|
|
|
|
from app.core.database import Base
|
|
from app.core.security import ApiPrincipal
|
|
from app.core.utils.time import utc_now
|
|
from app.modules.ai_memory.constants import AIMemoryResponseKey, AIMemoryStatus
|
|
from app.modules.ai_memory.models import AIMemoryEntry
|
|
from app.modules.ai_memory.routes import list_memory
|
|
from app.modules.dashboard.routes import dashboard_summary
|
|
from app.modules.observability.routes import metrics as observability_metrics
|
|
|
|
|
|
def _session_factory():
|
|
engine = create_engine("sqlite://")
|
|
Base.metadata.create_all(engine)
|
|
return engine, sessionmaker(bind=engine, expire_on_commit=False)
|
|
|
|
|
|
def _seed_expired_memory(factory: sessionmaker, code: str, subject: str) -> None:
|
|
with factory() as db:
|
|
db.add(
|
|
AIMemoryEntry(
|
|
code=code,
|
|
scope="project",
|
|
subject=subject,
|
|
content="expired",
|
|
status=AIMemoryStatus.ACTIVE,
|
|
expires_at=utc_now() - timedelta(seconds=1),
|
|
)
|
|
)
|
|
db.commit()
|
|
|
|
|
|
def _assert_archived(factory: sessionmaker, code: str) -> None:
|
|
with factory() as db:
|
|
stored = db.execute(
|
|
select(AIMemoryEntry).where(AIMemoryEntry.code == code)
|
|
).scalar_one()
|
|
assert stored.status == AIMemoryStatus.ARCHIVED
|
|
|
|
|
|
def test_memory_list_route_persists_expiration_at_request_boundary() -> None:
|
|
engine, factory = _session_factory()
|
|
try:
|
|
code = "MEM-LIST-EXPIRED"
|
|
_seed_expired_memory(factory, code, "P-LIST")
|
|
|
|
with factory() as db:
|
|
result = list_memory(
|
|
scope="project",
|
|
subject="P-LIST",
|
|
status=AIMemoryStatus.ACTIVE,
|
|
limit=100,
|
|
db=db,
|
|
)
|
|
assert result[AIMemoryResponseKey.ITEMS] == []
|
|
|
|
_assert_archived(factory, code)
|
|
finally:
|
|
engine.dispose()
|
|
|
|
|
|
def test_observability_metrics_route_persists_memory_expiration() -> None:
|
|
engine, factory = _session_factory()
|
|
try:
|
|
code = "MEM-METRICS-EXPIRED"
|
|
_seed_expired_memory(factory, code, "P-METRICS")
|
|
|
|
with factory() as db:
|
|
observability_metrics(db=db)
|
|
|
|
_assert_archived(factory, code)
|
|
finally:
|
|
engine.dispose()
|
|
|
|
|
|
def test_dashboard_route_excludes_and_archives_expired_memory() -> None:
|
|
engine, factory = _session_factory()
|
|
try:
|
|
code = "MEM-DASHBOARD-EXPIRED"
|
|
_seed_expired_memory(factory, code, "P-DASHBOARD")
|
|
|
|
with factory() as db:
|
|
result = dashboard_summary(
|
|
db=db,
|
|
principal=ApiPrincipal(actor="pytest"),
|
|
)
|
|
assert result["metrics"]["active_ai_memory"] == 0
|
|
|
|
_assert_archived(factory, code)
|
|
finally:
|
|
engine.dispose()
|