初始化提交
This commit is contained in:
commit
887c1f449e
963 files changed
+166869
No files matched your search
@@ -0,0 +1,110 @@
|
||||
"""DbEventStore 集成测试:sqlite 建表 → 写入 → 检索,验证持久化与实时推送。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import AsyncIterator
|
||||
|
||||
import pytest
|
||||
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
||||
|
||||
from app.db.base import Base
|
||||
from app.modules.runtime.events import Event
|
||||
from app.services.event_store import DbEventStore
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def store() -> AsyncIterator[DbEventStore]:
|
||||
"""内存 sqlite + 全表建表(FK 不强制,故无需 characters/runs 先验行)。"""
|
||||
engine = create_async_engine("sqlite+aiosqlite:///:memory:")
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
factory = async_sessionmaker(engine, expire_on_commit=False)
|
||||
yield DbEventStore(factory)
|
||||
await engine.dispose()
|
||||
|
||||
|
||||
async def test_append_assigns_monotonic_sequence(store: DbEventStore) -> None:
|
||||
await store.append(Event(type="tool.started", source="tool", run_id="run_1"))
|
||||
await store.append(Event(type="tool.completed", source="tool", run_id="run_1"))
|
||||
|
||||
events = await store.list_by_run("run_1")
|
||||
assert [e.type for e in events] == ["tool.started", "tool.completed"]
|
||||
assert [e.sequence for e in events] == [1, 2]
|
||||
|
||||
|
||||
async def test_payload_and_evidence_ids_roundtrip(store: DbEventStore) -> None:
|
||||
await store.append(
|
||||
Event(
|
||||
type="memory.candidate.created",
|
||||
source="memory",
|
||||
run_id="run_1",
|
||||
character_id="char_1",
|
||||
payload={"summary": "测试", "score": 0.8},
|
||||
evidence_ids=["evidence_a", "evidence_b"],
|
||||
sensitivity="medium",
|
||||
)
|
||||
)
|
||||
events = await store.list_by_run("run_1")
|
||||
assert events[0].payload == {"summary": "测试", "score": 0.8}
|
||||
assert events[0].evidence_ids == ["evidence_a", "evidence_b"]
|
||||
assert events[0].character_id == "char_1"
|
||||
assert events[0].sensitivity == "medium"
|
||||
|
||||
|
||||
async def test_run_without_run_id_persists(store: DbEventStore) -> None:
|
||||
"""运行外事件(run_id=None)可持久化,不撞 NOT NULL / 唯一约束。"""
|
||||
await store.append(Event(type="drive.need.detected", source="drive"))
|
||||
# 用 run_id 查不到,但写入不应报错;这里仅验证无异常 + run 内事件仍正常
|
||||
await store.append(Event(type="tool.started", source="tool", run_id="run_x"))
|
||||
events = await store.list_by_run("run_x")
|
||||
assert len(events) == 1
|
||||
|
||||
|
||||
async def test_subscribe_receives_realtime(store: DbEventStore) -> None:
|
||||
queue = store.subscribe("run_1")
|
||||
await store.append(Event(type="tool.started", source="tool", run_id="run_1"))
|
||||
await store.append(Event(type="tool.completed", source="tool", run_id="run_1"))
|
||||
|
||||
first = queue.get_nowait()
|
||||
second = queue.get_nowait()
|
||||
assert first.type == "tool.started"
|
||||
assert second.type == "tool.completed"
|
||||
store.unsubscribe("run_1", queue)
|
||||
|
||||
|
||||
async def test_persistence_across_sessions(tmp_path) -> None:
|
||||
"""重启后(新 engine/session)事件仍在——验证真正落盘。"""
|
||||
db_path = tmp_path / "events.sqlite3"
|
||||
url = f"sqlite+aiosqlite:///{db_path}"
|
||||
|
||||
engine = create_async_engine(url)
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
factory = async_sessionmaker(engine, expire_on_commit=False)
|
||||
store1 = DbEventStore(factory)
|
||||
await store1.append(
|
||||
Event(type="run.started", source="run", run_id="run_persist", character_id="char_1")
|
||||
)
|
||||
await engine.dispose()
|
||||
|
||||
# 新 engine 同一文件
|
||||
engine2 = create_async_engine(url)
|
||||
factory2 = async_sessionmaker(engine2, expire_on_commit=False)
|
||||
store2 = DbEventStore(factory2)
|
||||
events = await store2.list_by_run("run_persist")
|
||||
await engine2.dispose()
|
||||
|
||||
assert len(events) == 1
|
||||
assert events[0].type == "run.started"
|
||||
assert events[0].character_id == "char_1"
|
||||
|
||||
|
||||
async def test_subscribe_by_pattern_glob_match(store: DbEventStore) -> None:
|
||||
"""DbEventStore 的 subscribe_by_pattern 与 InMemoryEventStore 语义一致。"""
|
||||
queue = store.subscribe_by_pattern("time.*")
|
||||
await store.append(Event(type="time.reunion", source="time_sense", run_id="run_1"))
|
||||
await store.append(Event(type="run.started", source="run", run_id="run_1"))
|
||||
received = queue.get_nowait()
|
||||
assert received.type == "time.reunion"
|
||||
assert queue.empty()
|
||||
store.unsubscribe_by_pattern("time.*", queue)
|
||||
Reference in new issue
Block a user