chore: initialize EverOS 1.0.0
md-first memory extraction framework for AI agents. Markdown is the single source of truth; SQLite holds state and LanceDB provides the rebuildable vector + BM25 + scalar index. The codebase follows a single-direction DDD layering (entrypoints -> service -> memory -> infra, with component / core / config cross-cutting) enforced by import-linter. Engineering surface: - Coding conventions in .claude/rules/ (path-scoped) and workflows in .claude/skills/ (/commit, /new-branch, /pr). - GitHub Actions CI runs make lint + test + integration; pre-commit mirrors the gates locally (ruff, hygiene hooks, gitlint commit-msg). - Commit messages follow Conventional Commits, enforced by gitlint. - make lint also enforces datetime two-zone discipline and OpenAPI drift.
This commit is contained in:
215
tests/unit/test_infra/test_ome/test_dispatcher.py
Normal file
215
tests/unit/test_infra/test_ome/test_dispatcher.py
Normal file
@ -0,0 +1,215 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from everos.infra.ome._dispatch.dispatcher import EventDispatcher
|
||||
from everos.infra.ome._dispatch.registry import StrategyRegistry
|
||||
from everos.infra.ome._stores.counter import CounterStore
|
||||
from everos.infra.ome._stores.storage import OMEStorage
|
||||
from everos.infra.ome.context import StrategyContext
|
||||
from everos.infra.ome.decorator import offline_strategy
|
||||
from everos.infra.ome.events import BaseEvent, CronTick
|
||||
from everos.infra.ome.gates import Counter
|
||||
from everos.infra.ome.triggers import Cron, Immediate
|
||||
|
||||
|
||||
class _E(BaseEvent):
|
||||
user_id: str
|
||||
|
||||
|
||||
def _make_strategy(name: str, **kw):
|
||||
@offline_strategy(name=name, trigger=Immediate(on=[_E]), emits=[], **kw)
|
||||
async def _f(event: Any, ctx: StrategyContext) -> None:
|
||||
return None
|
||||
|
||||
return _f
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def dispatcher(tmp_path: Path) -> EventDispatcher:
|
||||
storage = OMEStorage(db_path=tmp_path / "ome.db")
|
||||
await storage.init()
|
||||
registry = StrategyRegistry()
|
||||
counter = CounterStore(storage=storage)
|
||||
return EventDispatcher(registry=registry, counter_store=counter)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_dispatch_passes_when_no_gate(
|
||||
dispatcher: EventDispatcher,
|
||||
) -> None:
|
||||
dispatcher._registry.register(_make_strategy("s_pass"))
|
||||
routes = await dispatcher.dispatch(_E(user_id="u1"))
|
||||
assert [m.name for m, _ in routes] == ["s_pass"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_dispatch_skips_disabled(dispatcher: EventDispatcher) -> None:
|
||||
dispatcher._registry.register(_make_strategy("s_off", enabled=False))
|
||||
routes = await dispatcher.dispatch(_E(user_id="u1"))
|
||||
assert routes == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_dispatch_applies_to_string(
|
||||
dispatcher: EventDispatcher,
|
||||
) -> None:
|
||||
dispatcher._registry.register(
|
||||
_make_strategy("s", applies_to="user_id"),
|
||||
)
|
||||
routes_empty = await dispatcher.dispatch(_E(user_id=""))
|
||||
routes_set = await dispatcher.dispatch(_E(user_id="u1"))
|
||||
assert routes_empty == []
|
||||
assert len(routes_set) == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_dispatch_applies_to_callable(
|
||||
dispatcher: EventDispatcher,
|
||||
) -> None:
|
||||
def is_paid(e: _E) -> bool:
|
||||
return e.user_id.startswith("paid_")
|
||||
|
||||
dispatcher._registry.register(_make_strategy("s", applies_to=is_paid))
|
||||
assert await dispatcher.dispatch(_E(user_id="free_a")) == []
|
||||
assert len(await dispatcher.dispatch(_E(user_id="paid_a"))) == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_dispatch_counter_gate(dispatcher: EventDispatcher) -> None:
|
||||
dispatcher._registry.register(
|
||||
_make_strategy("s", gate=Counter(threshold=3, event_field="user_id"))
|
||||
)
|
||||
for _ in range(2):
|
||||
routes = await dispatcher.dispatch(_E(user_id="u1"))
|
||||
assert routes == []
|
||||
routes = await dispatcher.dispatch(_E(user_id="u1"))
|
||||
assert len(routes) == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_inspect_returns_route_info(
|
||||
dispatcher: EventDispatcher,
|
||||
) -> None:
|
||||
dispatcher._registry.register(
|
||||
_make_strategy("s", gate=Counter(threshold=3, event_field="user_id"))
|
||||
)
|
||||
infos = await dispatcher.inspect(_E(user_id="u1"))
|
||||
assert len(infos) == 1
|
||||
assert infos[0].counter_progress == (1, 3)
|
||||
assert infos[0].will_run is False
|
||||
|
||||
|
||||
def _make_cron_strategy(name: str):
|
||||
@offline_strategy(name=name, trigger=Cron(expr="0 * * * *"), emits=[])
|
||||
async def _f(event: Any, ctx: StrategyContext) -> None:
|
||||
return None
|
||||
|
||||
return _f
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_dispatch_routes_engine_tick_to_named_strategy_only(
|
||||
dispatcher: EventDispatcher,
|
||||
) -> None:
|
||||
dispatcher._registry.register(_make_cron_strategy("cron_a"))
|
||||
dispatcher._registry.register(_make_cron_strategy("cron_b"))
|
||||
routes = await dispatcher.dispatch(CronTick(strategy_name="cron_a"))
|
||||
assert [m.name for m, _ in routes] == ["cron_a"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_inspect_engine_tick_skips_non_target_strategy(
|
||||
dispatcher: EventDispatcher,
|
||||
) -> None:
|
||||
dispatcher._registry.register(_make_cron_strategy("cron_a"))
|
||||
dispatcher._registry.register(_make_cron_strategy("cron_b"))
|
||||
infos = await dispatcher.inspect(CronTick(strategy_name="cron_b"))
|
||||
assert [i.strategy_name for i in infos] == ["cron_b"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_dispatch_force_enabled_bypasses_enabled_gate(
|
||||
dispatcher: EventDispatcher,
|
||||
) -> None:
|
||||
dispatcher._registry.register(_make_strategy("s_off", enabled=False))
|
||||
assert await dispatcher.dispatch(_E(user_id="u1")) == []
|
||||
routes = await dispatcher.dispatch(_E(user_id="u1"), force_enabled=True)
|
||||
assert [m.name for m, _ in routes] == ["s_off"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_dispatch_force_enabled_still_applies_applies_to_and_counter(
|
||||
dispatcher: EventDispatcher,
|
||||
) -> None:
|
||||
dispatcher._registry.register(
|
||||
_make_strategy(
|
||||
"s",
|
||||
enabled=False,
|
||||
applies_to="user_id",
|
||||
gate=Counter(threshold=2, event_field="user_id"),
|
||||
),
|
||||
)
|
||||
assert await dispatcher.dispatch(_E(user_id=""), force_enabled=True) == []
|
||||
assert await dispatcher.dispatch(_E(user_id="u1"), force_enabled=True) == []
|
||||
routes = await dispatcher.dispatch(_E(user_id="u1"), force_enabled=True)
|
||||
assert len(routes) == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_dispatch_strategy_filter_scopes_to_single_strategy(
|
||||
dispatcher: EventDispatcher,
|
||||
) -> None:
|
||||
dispatcher._registry.register(_make_strategy("s_a"))
|
||||
dispatcher._registry.register(_make_strategy("s_b"))
|
||||
routes = await dispatcher.dispatch(_E(user_id="u1"), strategy_filter="s_a")
|
||||
assert [m.name for m, _ in routes] == ["s_a"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_dispatch_strategy_filter_unknown_raises(
|
||||
dispatcher: EventDispatcher,
|
||||
) -> None:
|
||||
dispatcher._registry.register(_make_strategy("s_a"))
|
||||
with pytest.raises(KeyError):
|
||||
await dispatcher.dispatch(_E(user_id="u1"), strategy_filter="missing")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_dispatch_isolates_faulty_applies_to_callable(
|
||||
dispatcher: EventDispatcher,
|
||||
) -> None:
|
||||
"""A single strategy's buggy ``applies_to`` callable must not tank
|
||||
the fan-out for siblings subscribed to the same event class.
|
||||
"""
|
||||
|
||||
def _boom(_e: _E) -> bool:
|
||||
raise RuntimeError("applies_to is buggy")
|
||||
|
||||
dispatcher._registry.register(_make_strategy("s_buggy", applies_to=_boom))
|
||||
dispatcher._registry.register(_make_strategy("s_healthy"))
|
||||
|
||||
routes = await dispatcher.dispatch(_E(user_id="u1"))
|
||||
|
||||
# s_buggy is treated as not-applies; s_healthy still routes.
|
||||
assert [m.name for m, _ in routes] == ["s_healthy"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_inspect_isolates_faulty_applies_to_callable(
|
||||
dispatcher: EventDispatcher,
|
||||
) -> None:
|
||||
def _boom(_e: _E) -> bool:
|
||||
raise RuntimeError("applies_to is buggy")
|
||||
|
||||
dispatcher._registry.register(_make_strategy("s_buggy", applies_to=_boom))
|
||||
dispatcher._registry.register(_make_strategy("s_healthy"))
|
||||
|
||||
infos = await dispatcher.inspect(_E(user_id="u1"))
|
||||
|
||||
by_name = {i.strategy_name: i for i in infos}
|
||||
assert by_name["s_buggy"].applies_to_pass is False
|
||||
assert by_name["s_healthy"].applies_to_pass is True
|
||||
Reference in New Issue
Block a user