deer-flow/backend/tests/test_thread_archive.py
Ryker_Feng 98b8e4657e
feat(chats): add archive and restore (#5236)
* feat(chats): add archive and restore

* test(chats): observe archive search requests in pagination e2e

* docs(gateway): move thread lifecycle details out of inherited guidance

* docs(chats): add concise archive and restore RFC

* docs(chats): move archive RFC discussion to issue 5237
2026-09-06 22:30:26 +08:00

60 lines
2.9 KiB
Python

"""Archive is an owner-scoped list filter, never deletion or run state."""
import pytest
from langgraph.store.memory import InMemoryStore
from deerflow.persistence.thread_meta.memory import MemoryThreadMetaStore
from deerflow.persistence.thread_meta.sql import ThreadMetaRepository
ARCHIVED = "deerflow_archived"
@pytest.fixture(params=["memory", "sqlite"])
async def archive_store(request, tmp_path):
if request.param == "memory":
yield MemoryThreadMetaStore(InMemoryStore())
return
from deerflow.persistence.engine import close_engine, get_session_factory, init_engine
await init_engine("sqlite", url=f"sqlite+aiosqlite:///{tmp_path / 'archive.db'}", sqlite_dir=str(tmp_path))
try:
yield ThreadMetaRepository(get_session_factory())
finally:
await close_engine()
@pytest.mark.anyio
async def test_archive_filter_precedes_pagination_and_includes_legacy(archive_store):
store = archive_store
for name, metadata in [("legacy", {}), ("false", {ARCHIVED: False}), ("null", {ARCHIVED: None}), ("string", {ARCHIVED: "true"}), ("integer", {ARCHIVED: 1})]:
await store.create(name, user_id="owner", metadata=metadata)
for index in range(4):
await store.create(f"archived-{index}", user_id="owner", metadata={ARCHIVED: True})
await store.create("other", user_id="other", metadata={ARCHIVED: True})
active = await store.search(archived=False, user_id="owner")
assert {row["thread_id"] for row in active} == {"legacy", "false", "null", "string", "integer"}
first = await store.search(archived=False, limit=2, user_id="owner")
second = await store.search(archived=False, limit=3, offset=2, user_id="owner")
assert first + second == active
archived = await store.search(archived=True, user_id="owner")
assert {row["thread_id"] for row in archived} == {f"archived-{index}" for index in range(4)}
assert len(await store.search(user_id="owner")) == 9
@pytest.mark.anyio
async def test_restore_preserves_thread_metadata_status_and_timestamps(archive_store):
store = archive_store
original = await store.create("chat", user_id="owner", display_name="Report", metadata={"deerflow_pinned": True})
await store.update_metadata("chat", {ARCHIVED: True}, touch=False, user_id="owner")
assert await store.search(archived=False, user_id="owner") == []
await store.update_metadata("chat", {ARCHIVED: False}, touch=False, user_id="other")
assert (await store.get("chat", user_id="owner"))["metadata"][ARCHIVED] is True
await store.update_metadata("chat", {ARCHIVED: False}, touch=False, user_id="owner")
restored = await store.get("chat", user_id="owner")
assert restored["updated_at"] == original["updated_at"]
assert restored["display_name"] == "Report"
assert restored["status"] == original["status"]
assert restored["metadata"] == {"deerflow_pinned": True, ARCHIVED: False}
assert len(await store.search(archived=False, user_id="owner")) == 1