From 5affe70eae24782fdb506d89928e079f8b9b13a5 Mon Sep 17 00:00:00 2001 From: Yiorgis Gozadinos Date: Mon, 25 May 2026 16:29:31 +0300 Subject: [PATCH] Cover unparseable-metadata path --- .../haiku/rag/store/upgrades/v0_50_0.py | 2 +- tests/store/test_v0_50_0_migration.py | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/haiku_rag_slim/haiku/rag/store/upgrades/v0_50_0.py b/haiku_rag_slim/haiku/rag/store/upgrades/v0_50_0.py index 4d0c4e85..9c574c44 100644 --- a/haiku_rag_slim/haiku/rag/store/upgrades/v0_50_0.py +++ b/haiku_rag_slim/haiku/rag/store/upgrades/v0_50_0.py @@ -62,7 +62,7 @@ async def _apply_canonical_metadata_keys(store: Store) -> None: raw = row.get("metadata") or "{}" try: meta = json.loads(raw) - except Exception: # pragma: no cover + except Exception: logger.warning( "Could not parse metadata JSON for document %s; skipping", doc_id ) diff --git a/tests/store/test_v0_50_0_migration.py b/tests/store/test_v0_50_0_migration.py index 3284f151..d446f691 100644 --- a/tests/store/test_v0_50_0_migration.py +++ b/tests/store/test_v0_50_0_migration.py @@ -139,6 +139,42 @@ class TestV0_50_0Migration: "source_revision": "v2", } + async def test_unparseable_metadata_skipped_without_crashing(self, temp_db_path): + """A row with malformed JSON in `metadata` must not abort the whole + migration: it's logged and skipped, and well-formed rows alongside it + still get rewritten. The bad row's metadata is left exactly as-is.""" + async with Store(temp_db_path, create=True, skip_migration_check=True) as store: + await store.set_haiku_version("0.48.1") + await store.documents_table.add( + [ + # Contains the substring `"etag"` so the WHERE LIKE pulls + # it in, but it's not valid JSON — json.loads fails. + DocumentRecord( + id="bad", + content="x", + uri="u1", + metadata='{"etag": broken', + ), + DocumentRecord( + id="good", + content="x", + uri="u2", + metadata=json.dumps({"etag": "abc"}), + ), + ] + ) + + async with Store(temp_db_path, skip_migration_check=True) as store: + # Must not raise. + applied = await store.migrate() + assert any("0.50.0" in d for d in applied) + + rows = await store.documents_table.query().to_list() + by_id = {r["id"]: r["metadata"] for r in rows} + + assert by_id["bad"] == '{"etag": broken' + assert json.loads(by_id["good"]) == {"source_revision": "abc"} + async def test_preserves_existing_canonical_keys_on_collision(self, temp_db_path): """If both legacy and canonical keys are present, the canonical wins and the legacy is dropped — defends against partial-migration states."""