From d595f2a698fbcd571417b3e2ff7fa4ff0de8d452 Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Sat, 15 Aug 2026 01:30:08 +0100 Subject: [PATCH] fix(store): preserve non-mapping native layer entries (#508) * fix(store): preserve non-mapping native layer entries * test(store): cover mixed native injection cleanup --- backend/services/ai_intel_store.py | 12 ++++- .../tests/test_ai_intel_store_mixed_items.py | 46 +++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 backend/tests/test_ai_intel_store_mixed_items.py diff --git a/backend/services/ai_intel_store.py b/backend/services/ai_intel_store.py index c7fda01..1006c05 100644 --- a/backend/services/ai_intel_store.py +++ b/backend/services/ai_intel_store.py @@ -141,7 +141,11 @@ def inject_layer_data( existing = list(current) if isinstance(current, list) else [] if mode == "replace": - existing = [e for e in existing if not e.get("_injected")] + existing = [ + entry + for entry in existing + if not (isinstance(entry, dict) and entry.get("_injected")) + ] # Readers can hold references to published layer lists after releasing # _data_lock. Build a fresh list and swap it atomically rather than @@ -170,7 +174,11 @@ def clear_injected_data(layer: str = "") -> dict[str, Any]: if not isinstance(existing, list): continue before = len(existing) - latest_data[lyr] = [e for e in existing if not e.get("_injected")] + latest_data[lyr] = [ + entry + for entry in existing + if not (isinstance(entry, dict) and entry.get("_injected")) + ] removed += before - len(latest_data[lyr]) if removed: diff --git a/backend/tests/test_ai_intel_store_mixed_items.py b/backend/tests/test_ai_intel_store_mixed_items.py new file mode 100644 index 0000000..4dc61c8 --- /dev/null +++ b/backend/tests/test_ai_intel_store_mixed_items.py @@ -0,0 +1,46 @@ +"""Regression coverage for mixed native entries during injected-data cleanup.""" + +from services import ai_intel_store +from services.fetchers import _store + + +def _publish_mixed_layer(monkeypatch): + published = [ + "native-sentinel", + {"id": "native"}, + {"id": "old-injected", "_injected": True}, + ] + monkeypatch.setitem(_store.latest_data, "air_quality", published) + monkeypatch.setattr(_store, "bump_data_version", lambda: None) + return published + + +def test_replace_preserves_non_mapping_native_entries(monkeypatch): + before = _publish_mixed_layer(monkeypatch) + + result = ai_intel_store.inject_layer_data( + "air_quality", + [{"id": "new-injected"}], + mode="replace", + ) + + after = _store.latest_data["air_quality"] + assert result["ok"] is True + assert after is not before + assert after[0] == "native-sentinel" + assert [item.get("id") for item in after if isinstance(item, dict)] == [ + "native", + "new-injected", + ] + + +def test_clear_preserves_non_mapping_native_entries(monkeypatch): + _publish_mixed_layer(monkeypatch) + + result = ai_intel_store.clear_injected_data("air_quality") + + assert result == {"ok": True, "removed": 1} + assert _store.latest_data["air_quality"] == [ + "native-sentinel", + {"id": "native"}, + ]