test(repair): unit coverage for empty-metadata sanitization in _extract_drawers

Addresses @Copilot's review feedback on #1459. Five tests:

  - test_extract_drawers_preserves_valid_metadata: non-empty dict
    passes through unchanged (regression guard against breaking happy
    path).
  - test_extract_drawers_sanitizes_none_metadata: None entries
    coerce to {"_repaired_empty_meta": True} (the core fix).
  - test_extract_drawers_sanitizes_empty_dict_metadata: empty dict
    {} entries also coerce to the sentinel (chromadb 1.5.x rejects
    both shapes equally).
  - test_extract_drawers_sanitization_preserves_alignment: critical
    invariant — ids[i] / documents[i] / metadatas[i] stay in
    lockstep through the sanitizer; mis-pairing would silently
    corrupt rebuilds.
  - test_extract_drawers_multiple_batches: pagination boundary
    correctness (sanitizer applied per-batch, no drops/duplicates).

Verified passing locally against mempalace fork main + chromadb
1.5.8 in the palace-daemon venv (5 passed, 67 deselected in 1.88s).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
jp 2026-05-11 14:17:07 -07:00
parent 5d95656b6b
commit 0a1adf9962
1 changed files with 88 additions and 0 deletions

View File

@ -76,6 +76,94 @@ def test_paginate_ids_offset_exception_fallback():
assert "id1" in ids
# ── _extract_drawers ──────────────────────────────────────────────────
def test_extract_drawers_preserves_valid_metadata():
"""Non-empty dict metadata passes through unchanged."""
col = MagicMock()
col.get.return_value = {
"ids": ["id1", "id2"],
"documents": ["doc1", "doc2"],
"metadatas": [{"wing": "a", "room": "1"}, {"wing": "b", "room": "2"}],
}
all_ids, all_docs, all_metas = repair._extract_drawers(col, total=2, batch_size=2)
assert all_ids == ["id1", "id2"]
assert all_docs == ["doc1", "doc2"]
assert all_metas == [{"wing": "a", "room": "1"}, {"wing": "b", "room": "2"}]
def test_extract_drawers_sanitizes_none_metadata():
"""None entries in metadatas are coerced to the sentinel dict.
chromadb 1.5.x's `validate_metadata` raises `ValueError: Expected metadata
to be a non-empty dict, got 0 metadata attributes in add.` if it sees a
None entry; the sanitizer keeps the rebuild upsert from crashing.
"""
col = MagicMock()
col.get.return_value = {
"ids": ["id1", "id2", "id3"],
"documents": ["doc1", "doc2", "doc3"],
"metadatas": [{"wing": "a"}, None, {"wing": "c"}],
}
_, _, all_metas = repair._extract_drawers(col, total=3, batch_size=3)
assert all_metas[0] == {"wing": "a"}
assert all_metas[1] == {"_repaired_empty_meta": True}
assert all_metas[2] == {"wing": "c"}
def test_extract_drawers_sanitizes_empty_dict_metadata():
"""Empty dict {} entries are coerced to the sentinel dict.
chromadb 1.5.x rejects `{}` the same way it rejects `None`. The comment
in the previous code path mistakenly assumed otherwise.
"""
col = MagicMock()
col.get.return_value = {
"ids": ["id1", "id2"],
"documents": ["doc1", "doc2"],
"metadatas": [{}, {"wing": "b"}],
}
_, _, all_metas = repair._extract_drawers(col, total=2, batch_size=2)
assert all_metas[0] == {"_repaired_empty_meta": True}
assert all_metas[1] == {"wing": "b"}
def test_extract_drawers_sanitization_preserves_alignment():
"""Sanitized output keeps the same length and ordering as input.
Critical invariant: ids[i] / documents[i] / metadatas[i] must stay in
lockstep through the sanitizer; otherwise the rebuild upsert mis-pairs
documents with metadata.
"""
col = MagicMock()
col.get.return_value = {
"ids": ["id1", "id2", "id3", "id4"],
"documents": ["d1", "d2", "d3", "d4"],
"metadatas": [None, {"k": "v"}, {}, None],
}
all_ids, all_docs, all_metas = repair._extract_drawers(col, total=4, batch_size=4)
assert len(all_ids) == len(all_docs) == len(all_metas) == 4
assert all_ids == ["id1", "id2", "id3", "id4"]
assert all_metas[0] == {"_repaired_empty_meta": True}
assert all_metas[1] == {"k": "v"}
assert all_metas[2] == {"_repaired_empty_meta": True}
assert all_metas[3] == {"_repaired_empty_meta": True}
def test_extract_drawers_multiple_batches():
"""Pagination handles batch boundaries without losing/duplicating rows."""
col = MagicMock()
col.get.side_effect = [
{"ids": ["id1", "id2"], "documents": ["d1", "d2"], "metadatas": [{"a": 1}, None]},
{"ids": ["id3"], "documents": ["d3"], "metadatas": [{}]},
{"ids": [], "documents": [], "metadatas": []},
]
all_ids, all_docs, all_metas = repair._extract_drawers(col, total=3, batch_size=2)
assert all_ids == ["id1", "id2", "id3"]
assert all_metas == [{"a": 1}, {"_repaired_empty_meta": True}, {"_repaired_empty_meta": True}]
# ── scan_palace ───────────────────────────────────────────────────────