Files
EverOS/tests/unit/test_memory/test_get/test_dto.py
Elliot Chen 518b8eca85 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.
2026-06-06 07:33:17 +08:00

178 lines
5.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Tests for ``memory.get.dto``.
Pydantic-side guarantees the manager / route can rely on:
* ``GetRequest`` defaults match the wiki spec (``page=1`` /
``page_size=20`` / ``sort_by="timestamp"`` / ``sort_order="desc"``)
* ``page_size`` upper bound (1100)
* ``owner_type`` × ``memory_type`` strict pairing
* Unknown fields on the request are rejected (``extra="forbid"``)
Filter DSL coverage lives in ``test_memory/test_search/test_filters.py``
since ``/get`` shares :class:`everos.memory.search.FilterNode`.
"""
from __future__ import annotations
import pytest
from pydantic import ValidationError
from everos.memory.get.dto import (
GetMemoryType,
GetRequest,
)
# ── GetRequest defaults / shape ──────────────────────────────────────────
def test_get_request_defaults_match_wiki() -> None:
"""``page`` / ``page_size`` / ``sort_by`` / ``sort_order`` come from the wiki."""
req = GetRequest(
user_id="u1",
memory_type=GetMemoryType.EPISODE,
)
assert req.page == 1
assert req.page_size == 20
assert req.sort_by == "timestamp"
assert req.sort_order == "desc"
assert req.filters is None
def test_get_request_page_size_upper_bound() -> None:
"""101 → ValidationError (wiki cap is 100)."""
with pytest.raises(ValidationError):
GetRequest(
user_id="u1",
memory_type=GetMemoryType.EPISODE,
page_size=101,
)
def test_get_request_page_size_lower_bound() -> None:
"""0 → ValidationError (page_size ≥ 1)."""
with pytest.raises(ValidationError):
GetRequest(
user_id="u1",
memory_type=GetMemoryType.EPISODE,
page_size=0,
)
def test_get_request_page_lower_bound() -> None:
"""0 → ValidationError (page ≥ 1; 1-indexed)."""
with pytest.raises(ValidationError):
GetRequest(
user_id="u1",
memory_type=GetMemoryType.EPISODE,
page=0,
)
def test_get_request_rejects_unknown_field() -> None:
"""``extra='forbid'`` — typos surface as a 422, not silent drops."""
with pytest.raises(ValidationError):
GetRequest(
user_id="u1",
memory_type=GetMemoryType.EPISODE,
unknown_extra=True, # type: ignore[call-arg]
)
def test_get_request_rejects_empty_user_id() -> None:
"""``user_id`` carries ``min_length=1`` — empty string is 422."""
with pytest.raises(ValidationError):
GetRequest(
user_id="",
memory_type=GetMemoryType.EPISODE,
)
def test_get_request_rejects_missing_memory_type() -> None:
"""``memory_type`` is required — omission is 422."""
with pytest.raises(ValidationError):
GetRequest( # type: ignore[call-arg]
user_id="u1",
)
def test_get_request_rejects_missing_owner_identity() -> None:
"""Neither ``user_id`` nor ``agent_id`` → xor validator rejects."""
with pytest.raises(ValidationError, match="exactly one of"):
GetRequest( # type: ignore[call-arg]
memory_type=GetMemoryType.EPISODE,
)
def test_get_request_rejects_both_user_and_agent_id() -> None:
"""Both ``user_id`` and ``agent_id`` set → xor validator rejects."""
with pytest.raises(ValidationError, match="exactly one of"):
GetRequest(
user_id="u1",
agent_id="agent_x",
memory_type=GetMemoryType.EPISODE,
)
def test_get_request_rejects_invalid_memory_type_value() -> None:
"""A value outside the four-kind enum is 422."""
with pytest.raises(ValidationError):
GetRequest.model_validate(
{
"user_id": "u1",
"memory_type": "atomic_fact", # not a top-level kind
}
)
def test_get_request_rejects_invalid_sort_order() -> None:
"""``sort_order`` is a tight Literal — typos / casing variants are 422."""
with pytest.raises(ValidationError):
GetRequest.model_validate(
{
"user_id": "u1",
"memory_type": "episode",
"sort_order": "DESC", # must be lowercase
}
)
# ── owner_type × memory_type pairing ─────────────────────────────────────
@pytest.mark.parametrize(
"id_field, memory_type",
[
("user_id", GetMemoryType.EPISODE),
("user_id", GetMemoryType.PROFILE),
("agent_id", GetMemoryType.AGENT_CASE),
("agent_id", GetMemoryType.AGENT_SKILL),
],
)
def test_get_request_allows_valid_owner_memory_pair(
id_field: str,
memory_type: GetMemoryType,
) -> None:
"""The four valid (owner-kind, memory_type) combinations."""
req = GetRequest(**{id_field: "u1"}, memory_type=memory_type)
assert req.memory_type is memory_type
expected_owner_type = "user" if id_field == "user_id" else "agent"
assert req.owner_type == expected_owner_type
@pytest.mark.parametrize(
"id_field, memory_type",
[
("user_id", GetMemoryType.AGENT_CASE),
("user_id", GetMemoryType.AGENT_SKILL),
("agent_id", GetMemoryType.EPISODE),
("agent_id", GetMemoryType.PROFILE),
],
)
def test_get_request_rejects_cross_owner_memory_pair(
id_field: str,
memory_type: GetMemoryType,
) -> None:
"""Cross-pairs (user_id+agent_case etc.) are 422 at the DTO layer."""
with pytest.raises(ValidationError):
GetRequest(**{id_field: "u1"}, memory_type=memory_type)