refactor: isolate agent history queries

This commit is contained in:
jxxghp
2026-08-23 14:21:53 +08:00
parent d8b8583d49
commit 8fc9a021b0
11 changed files with 121 additions and 61 deletions
+3 -33
View File
@@ -1,43 +1,13 @@
{
"model_decorators": {
"by_kind": {
"async_db_query": 9,
"async_db_query": 7,
"async_db_update": 0,
"db_query": 9,
"db_query": 5,
"db_update": 0
},
"count": 18,
"count": 12,
"methods": [
{
"decorator": "async_db_query",
"file": "app/db/models/agentchat.py",
"method": "AgentChat.async_get_by_session"
},
{
"decorator": "async_db_query",
"file": "app/db/models/agentchat.py",
"method": "AgentChat.async_list_by_page"
},
{
"decorator": "db_query",
"file": "app/db/models/agentchat.py",
"method": "AgentChat.get_by_session"
},
{
"decorator": "db_query",
"file": "app/db/models/agentchat.py",
"method": "AgentChat.list_by_page"
},
{
"decorator": "db_query",
"file": "app/db/models/agenttaskrun.py",
"method": "AgentTaskRun.get_by_run_id"
},
{
"decorator": "db_query",
"file": "app/db/models/agenttaskrun.py",
"method": "AgentTaskRun.list_for_task"
},
{
"decorator": "async_db_query",
"file": "app/db/models/passkey.py",
+30
View File
@@ -14,6 +14,7 @@ from app.db.oper.agenttask import AgentTaskOper
from app.db.models.agenttask import AgentTask
from app.db.models.agenttaskrun import AgentTaskRun
from app.db.session import SessionFactory
from app.db import decorators
Engine = get_engine()
@@ -134,6 +135,35 @@ def test_begin_run_rejects_unknown_trigger_source() -> None:
assert AgentTaskOper().list_runs(task.id) == []
def test_agenttaskrun_oper_reuses_explicit_query_session(db, monkeypatch):
"""AgentTaskOper 的运行记录查询必须复用调用方同步会话。"""
task = _add_task("run-explicit-query")
run = AgentTaskOper().begin_run(task.id)
assert run
monkeypatch.setattr(
decorators,
"ScopedSession",
lambda: (_ for _ in ()).throw(AssertionError("不应创建额外同步会话")),
)
oper = AgentTaskOper(db.session)
assert oper.get_run(run.run_id) is not None
assert oper.list_runs(task.id)
def test_agenttaskrun_model_legacy_query_keeps_keyword_abi(monkeypatch):
"""旧插件以关键字直调 AgentTaskRun 时仍自动补入短会话。"""
opened = []
monkeypatch.setattr(
decorators,
"ScopedSession",
lambda: (opened.append(True) or SessionFactory()),
)
assert AgentTaskRun.get_by_run_id(run_id="missing-legacy") is None
assert opened == [True]
def test_begin_run_rolls_back_task_claim_when_run_insert_fails() -> None:
"""运行记录插入失败时,任务的 running 投影必须随事务回滚。"""
first_task = _add_task("run-rollback-first")
+2 -2
View File
@@ -126,8 +126,8 @@ def test_transaction_debt_baseline_is_a_model_and_oper_ratchet() -> None:
baseline = json.loads(baseline_path.read_text(encoding="utf-8"))
assert baseline["schema_version"] == 1
assert baseline["model_decorators"]["count"] == 18
assert sum(baseline["model_decorators"]["by_kind"].values()) == 18
assert baseline["model_decorators"]["count"] == 12
assert sum(baseline["model_decorators"]["by_kind"].values()) == 12
assert baseline["model_decorators"]["by_kind"]["db_update"] == 0
assert baseline["model_decorators"]["by_kind"]["async_db_update"] == 0
assert baseline["model_transaction_calls"] == {"count": 0, "calls": []}
@@ -8,12 +8,14 @@ import asyncio
import pytest
from app.db import decorators
from app.db.models.agentchat import AgentChat
from app.db.models.agenttask import AgentTask
from app.db.models.downloadfailure import DownloadFailure
from app.db.models.message import Message
from app.db.models.plugindata import PluginData
from app.db.oper.agenttask import AgentTaskOper
from app.db.session import SessionFactory
@pytest.fixture(autouse=True)
@@ -238,6 +240,48 @@ def test_agentchat_get_by_session_enforces_user_scope(db):
assert asyncio.run(AgentChat.async_get_by_session(session_id="s-owned", user_id="bob")) is None
def test_agentchat_oper_reuses_explicit_query_sessions(db, monkeypatch):
"""AgentChatOper 的同步与异步查询必须复用调用方会话。"""
db.add(_chat("s-explicit", user_id="explicit"))
monkeypatch.setattr(
decorators,
"ScopedSession",
lambda: (_ for _ in ()).throw(AssertionError("不应创建额外同步会话")),
)
from app.db.oper.agentchat import AgentChatOper
assert AgentChatOper(db.session).get("s-explicit", "explicit") is not None
async def check() -> None:
"""验证异步 Agent 会话查询复用显式 AsyncSession。"""
from app.db.session import async_session_scope
async with async_session_scope() as session:
monkeypatch.setattr(
decorators,
"async_session_scope",
lambda: (_ for _ in ()).throw(AssertionError("不应创建额外异步会话")),
)
assert await AgentChatOper(session).async_get("s-explicit", "explicit")
asyncio.run(check())
def test_agentchat_model_legacy_query_keeps_keyword_abi(db, monkeypatch):
"""旧插件以关键字直调 AgentChat 时仍自动补入短会话。"""
db.add(_chat("s-legacy"))
opened = []
monkeypatch.setattr(
decorators,
"ScopedSession",
lambda: (opened.append(True) or SessionFactory()),
)
assert AgentChat.get_by_session(session_id="s-legacy") is not None
assert opened == [True]
def test_agentchat_list_by_page_matches_either_user_or_username(db):
"""
同时给出用户 ID 与用户名时按「或」匹配。