Files
NotesAgentic/backend/tests/test_chat_versions.py
T
admin d703ab64e3
CI / docs-check (push) Canceled after 0s
CI / backend-test (push) Canceled after 0s
CI / service-test (push) Canceled after 0s
CI / frontend-test (push) Canceled after 0s
CI / rust-core (push) Canceled after 0s
CI / docs-check (pull_request) Canceled after 0s
CI / backend-test (pull_request) Canceled after 0s
CI / service-test (pull_request) Canceled after 0s
CI / frontend-test (pull_request) Canceled after 0s
CI / rust-core (pull_request) Canceled after 0s
docs: 将仓库代码注释统一为中文
2026-09-10 00:40:56 +08:00

90 lines
5.1 KiB
Python

from app.services import chat_history as history
def test_edits_regeneration_and_activity_survive_version_switch():
history.create('Versions', 'versions')
def append(id, role, content, parent=None, activity=None):
history.append_message('versions', message_id=id, role=role, content=content, parent_message_id=parent, activity=activity)
append('u1', 'user', 'original')
append('a1', 'assistant', 'original answer', 'u1')
append('u2', 'user', 'follow-up')
append('a2', 'assistant', 'follow-up answer', 'u2')
history.prepare_retry('versions', 'u1')
append('u1-edit', 'user', 'edited')
history.reserve_response('versions', 'a1-edit')
trace = [{'type': 'thinking', 'text': 'before'}, {'type': 'tool', 'tool_call_id': 'tool'}, {'type': 'thinking', 'text': 'after'}]
append('a1-edit', 'assistant', 'edited answer', 'u1-edit', trace)
items, _ = history.list_messages('versions', 500, 0)
assert [m.message_id for m in items] == ['u1-edit', 'a1-edit']
assert items[0].versions == ['u1', 'u1-edit']
assert items[1].activity == trace
history.select_version('versions', 'u1')
assert [m.message_id for m in history.list_messages('versions', 500, 0)[0]] == ['u1', 'a1', 'u2', 'a2']
history.prepare_retry('versions', 'a1')
history.reserve_response('versions', 'a1-new')
append('a1-new', 'assistant', 'regenerated', 'u1')
items, _ = history.list_messages('versions', 500, 0)
assert [m.message_id for m in items] == ['u1', 'a1-new']
assert items[-1].versions == ['a1', 'a1-new']
history.select_version('versions', 'a1')
assert history.list_messages('versions', 500, 0)[0][-1].message_id == 'a2'
def test_late_response_does_not_replace_new_generation():
history.create('Late', 'late')
history.append_message('late', message_id='u', role='user', content='question')
history.reserve_response('late', 'new')
history.append_message('late', message_id='old', role='assistant', content='old', parent_message_id='u')
assert history.list_messages('late', 500, 0)[0][-1].message_id == 'u'
history.append_message('late', message_id='new', role='assistant', content='new', parent_message_id='u')
assert history.list_messages('late', 500, 0)[0][-1].message_id == 'new'
def test_workspace_snapshots_and_agent_links_survive_history_reload():
history.create('Workspace', 'workspace')
snapshot = {'file_path': 'demo.md', 'content': '# unsaved draft'}
history.append_message('workspace', message_id='wu', role='user', content='explain', workspace_context=snapshot)
calls = [{'tool_call_id': 'ac', 'name': 'agent.create', 'result': '{"run_id":"run_example"}'}]
history.append_message('workspace', message_id='wa', role='assistant', content='started', tool_calls=calls)
messages, total = history.list_messages('workspace', 100, 0)
assert total == 2
assert messages[0].workspace_context.model_dump() == snapshot
assert messages[1].tool_calls == calls
def test_regeneration_persists_context_per_answer_without_rewriting_original(monkeypatch):
import asyncio
from types import SimpleNamespace
from app.contracts import ChatRequest, Message, ModelEvent, ModelEventType
from app.routes import chat, utc_now
received=[]
class Adapter:
async def stream(self, request):
received.append(request)
yield ModelEvent(event=ModelEventType.text_delta, sequence=0, data={'text':'answer'}, timestamp=utc_now())
yield ModelEvent(event=ModelEventType.done, sequence=1, data={}, timestamp=utc_now())
monkeypatch.setattr('app.routes.provider_or_404',lambda _:SimpleNamespace(adapter=Adapter()))
# 将附件解析排除在此持久性测试之外;路由必须保存原始 ID。
async def prepare(request, provider):
return request.model_copy(update={'attachments':[]})
monkeypatch.setattr('app.services.chat_attachments.prepare',prepare)
async def scenario():
history.create('Snapshots','snapshots')
for index,context in enumerate([{'file_path':'a.md','content':'A'},{'file_path':'b.md','content':'B'},None]):
req=ChatRequest(provider_id='test',model='test',use_rag=False,conversation_id='snapshots',
user_message_id='su',assistant_message_id=f'sa{index}',retry_message_id=f'sa{index-1}' if index else None,
messages=[Message(role='user',content='explain')],workspace_context=context,attachments=[f'file{index}.md'])
response=await chat(req)
_=[chunk async for chunk in response.body_iterator]
for index,path in enumerate(['a.md','b.md',None]):
history.select_version('snapshots',f'sa{index}')
messages,_=history.list_messages('snapshots',100,0)
assert messages[0].workspace_context.file_path=='a.md'
answer=messages[-1]
assert answer.context_captured
assert (answer.workspace_context.file_path if answer.workspace_context else None)==path
assert answer.attachments==[f'file{index}.md']
assert 'b.md' in received[1].system
assert received[2].system is None
asyncio.run(scenario())