headroom/tests/test_memory/test_local_backend_search.py
Chester aa4515cf7a
fix(memory): filter inactive graph-expanded results (#2210)
## DescriptionKeep graph-expanded local-memory results consistent with
the current-only contract already applied by vector search.Closes
#2209## Type of Change- [x] Bug fix (non-breaking change that fixes an
issue)- [ ] New feature (non-breaking change that adds functionality)- [
] Breaking change (fix or feature that would cause existing
functionality to change)- [ ] Documentation update- [ ] Performance
improvement- [ ] Code refactoring (no functional changes)## Changes
Made- Reject graph-expanded memories whose `valid_until` is set.- Reject
graph-expanded memories whose `superseded_by` is set.- Add focused
coverage for active, expired, and superseded related memories.##
Testing- [x] Unit tests pass (`pytest`)- [x] Linting passes (`ruff check
.`)- [ ] Type checking passes (`mypy headroom`)- [x] New tests added for
new functionality- [ ] Manual testing performed### Test Output```text$
uv run --with pytest --with pytest-asyncio --with numpy pytest
tests/test_memory/test_local_backend_search.py -q3 passed$ uv run --with
ruff ruff check headroom/memory/backends/local.py
tests/test_memory/test_local_backend_search.pyAll checks passed!$ uv run
--with ruff ruff format --check headroom/memory/backends/local.py
tests/test_memory/test_local_backend_search.py2 files already
formatted```## Real Behavior Proof- Environment: Python 3.13, synthetic
in-memory test doubles- Exact command / steps: run the focused test file
above- Observed result: active graph-linked memory is returned; records
with `valid_until` or `superseded_by` are excluded- Not tested: full
repository suite, external vector/graph implementations## Review
Readiness- [x] I have performed a self-review- [x] This PR is ready for
human review## Checklist- [x] My code follows the project's style
guidelines- [x] I have performed a self-review of my code- [ ] I have
commented my code, particularly in hard-to-understand areas- [ ] I have
made corresponding changes to the documentation- [ ] My changes generate
no new warnings- [x] I have added tests that prove my fix is effective
or that my feature works- [x] New and existing unit tests pass locally
with my changes- [ ] I have updated the CHANGELOG.md if applicable##
Screenshots (if applicable)N/A## Additional NotesDocumentation and
changelog changes are not needed for this narrow internal behavior fix.
The existing temporal-history APIs remain unchanged.

Co-authored-by: JerrettDavis <mxjerrett@gmail.com>
2026-07-15 19:58:13 +00:00

76 lines
2.4 KiB
Python

"""Focused tests for LocalBackend search result filtering."""
from __future__ import annotations
from datetime import datetime, timezone
from types import SimpleNamespace
from unittest.mock import AsyncMock
import pytest
from headroom.memory.backends.local import LocalBackend
from headroom.memory.models import Memory
def _backend_with_related_memory(related_memory: Memory) -> LocalBackend:
seed = Memory(
id="seed-memory",
content="Alice manages Project X",
user_id="alice",
entity_refs=["Project X"],
)
vector_result = SimpleNamespace(memory=seed, similarity=0.9)
backend = LocalBackend()
backend._initialized = True
backend._hierarchical_memory = SimpleNamespace(
search=AsyncMock(return_value=[vector_result]),
get=AsyncMock(return_value=related_memory),
)
backend._graph = SimpleNamespace(
get_entity_by_name=AsyncMock(return_value=SimpleNamespace(id="project-x")),
query_subgraph=AsyncMock(
return_value=SimpleNamespace(
entities=[SimpleNamespace(metadata={"source_memory_id": related_memory.id})],
relationships=[],
)
),
)
return backend
@pytest.mark.asyncio
async def test_graph_expansion_includes_active_related_memory() -> None:
related = Memory(
id="related-memory",
content="Project X uses Python",
user_id="alice",
entity_refs=["Project X", "Python"],
)
backend = _backend_with_related_memory(related)
results = await backend.search_memories("Alice's work", "alice", include_related=True)
assert [result.memory.id for result in results] == ["seed-memory", "related-memory"]
@pytest.mark.asyncio
@pytest.mark.parametrize("inactive_field", ["valid_until", "superseded_by"])
async def test_graph_expansion_excludes_inactive_related_memory(
inactive_field: str,
) -> None:
related = Memory(
id="related-memory",
content="Outdated Project X detail",
user_id="alice",
entity_refs=["Project X"],
)
if inactive_field == "valid_until":
related.valid_until = datetime.now(timezone.utc).replace(tzinfo=None)
else:
related.superseded_by = "replacement-memory"
backend = _backend_with_related_memory(related)
results = await backend.search_memories("Alice's work", "alice", include_related=True)
assert [result.memory.id for result in results] == ["seed-memory"]