Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions tests/services/test_search_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,41 @@ async def test_delete_entity_without_permalink(search_service, sample_entity):
await search_service.handle_delete(sample_entity)


@pytest.mark.asyncio
async def test_handle_delete_clears_entity_vectors(
search_service, sample_entity, monkeypatch
):
"""Regression guard for #764: handle_delete must drive vector-row cleanup
so deleting an entity doesn't leave orphaned rows in `search_vector_chunks`
or `search_vector_embeddings`.

Verified by spying on the repository's `delete_entity_vector_rows`. The
short-circuit path inside `_clear_entity_vectors` (semantic disabled) is
bypassed by forcing `_semantic_enabled=True` so we exercise the real
delegation, not the no-op branch.
"""
calls: list[int] = []

async def spy_delete_entity_vector_rows(entity_id: int) -> None:
calls.append(entity_id)

# Force the cleanup path even if the test repo is configured without
# semantic enabled — we're asserting the wiring, not embedding behavior.
monkeypatch.setattr(search_service.repository, "_semantic_enabled", True)
monkeypatch.setattr(
search_service.repository,
"delete_entity_vector_rows",
spy_delete_entity_vector_rows,
)

await search_service.handle_delete(sample_entity)

assert calls == [sample_entity.id], (
f"handle_delete must call delete_entity_vector_rows({sample_entity.id}); "
f"got calls={calls}"
)


@pytest.mark.asyncio
async def test_no_criteria(search_service, test_graph):
"""Test search with no criteria returns empty list."""
Expand Down
Loading