@@ -73,11 +73,12 @@ def test_layer0_default_path():
7373def _mock_chromadb_for_layer (docs , metas , monkeypatch = None ):
7474 """Return a mock collection whose get() returns docs/metas.
7575
76- Layer1._fetch_drawers() has two phases: a fast-path (importance pre-filter)
77- and a fallback full-scan. For small test datasets (< 500 items), each phase
78- makes exactly one col.get() call before breaking (len < _BATCH). We
79- provide two identical responses: one consumed by the fast path and one by
80- the fallback.
76+ Layer1._fetch_drawers() has two phases: a fast-path (importance >= 3
77+ pre-filter) and a fallback full-scan. For small test datasets (< 500
78+ items), each phase makes exactly one col.get() call before breaking
79+ (len < _BATCH). We provide two identical responses: the first is consumed
80+ by the fast path, and the second is only consumed when the fast path
81+ doesn't return enough results (< MAX_DRAWERS) and the fallback executes.
8182 """
8283 mock_col = MagicMock ()
8384 mock_col .get .side_effect = [
@@ -153,6 +154,7 @@ def test_layer1_with_wing_filter():
153154 where = call_kwargs .get ("where" , {})
154155 assert "$and" in where
155156 assert {"wing" : "project_x" } in where ["$and" ]
157+ assert {"importance" : {"$gte" : 3 }} in where ["$and" ]
156158
157159
158160def test_layer1_truncates_long_snippets ():
@@ -210,6 +212,61 @@ def test_layer1_importance_from_various_keys():
210212 assert "ESSENTIAL STORY" in result
211213
212214
215+ def test_layer1_pagination_stops_at_max_scan ():
216+ """_fetch_drawers() paginates in _BATCH (500) chunks and stops at MAX_SCAN."""
217+ _BATCH = 500
218+ # Build a full batch of 500 docs (first page) and a partial second page
219+ batch_docs = [f"doc{ i } " for i in range (_BATCH )]
220+ batch_metas = [{"room" : "r" , "importance" : 5 } for _ in range (_BATCH )]
221+ partial_docs = [f"doc{ i } " for i in range (100 )]
222+ partial_metas = [{"room" : "r" , "importance" : 5 } for _ in range (100 )]
223+
224+ mock_col = MagicMock ()
225+ # Fast path: page 1 (full batch) -> page 2 (partial, triggers break)
226+ mock_col .get .side_effect = [
227+ {"documents" : batch_docs , "metadatas" : batch_metas },
228+ {"documents" : partial_docs , "metadatas" : partial_metas },
229+ ]
230+
231+ with (
232+ patch ("mempalace.layers.MempalaceConfig" ) as mock_cfg ,
233+ patch ("mempalace.layers._get_collection" , return_value = mock_col ),
234+ ):
235+ mock_cfg .return_value .palace_path = "/fake"
236+ layer = Layer1 (palace_path = "/fake" )
237+ result = layer .generate ()
238+
239+ # Fast path got 600 results (>= MAX_DRAWERS=15), so no fallback needed.
240+ # Two get() calls: first batch of 500, second batch of 100 (< _BATCH -> break).
241+ assert mock_col .get .call_count == 2
242+ assert "ESSENTIAL STORY" in result
243+
244+
245+ def test_layer1_pagination_caps_at_max_scan ():
246+ """_fetch_drawers() stops reading once MAX_SCAN is reached, even mid-pagination."""
247+ _BATCH = 500
248+ batch_docs = [f"doc{ i } " for i in range (_BATCH )]
249+ batch_metas = [{"room" : "r" , "importance" : 5 } for _ in range (_BATCH )]
250+
251+ mock_col = MagicMock ()
252+ # Return full batches every time — the loop should stop after hitting MAX_SCAN
253+ mock_col .get .return_value = {"documents" : batch_docs , "metadatas" : batch_metas }
254+
255+ with (
256+ patch ("mempalace.layers.MempalaceConfig" ) as mock_cfg ,
257+ patch ("mempalace.layers._get_collection" , return_value = mock_col ),
258+ ):
259+ mock_cfg .return_value .palace_path = "/fake"
260+ layer = Layer1 (palace_path = "/fake" )
261+ layer .MAX_SCAN = 1200 # Should stop after 3 pages (500+500+500 >= 1200)
262+ result = layer .generate ()
263+
264+ # Fast path: 3 pages to reach >= MAX_SCAN, then stops.
265+ # No fallback since 1500 >= MAX_DRAWERS.
266+ assert mock_col .get .call_count == 3
267+ assert "ESSENTIAL STORY" in result
268+
269+
213270def test_layer1_batch_exception_breaks ():
214271 """If the fast-path raises, fallback scan still returns results gracefully."""
215272 mock_col = MagicMock ()
0 commit comments