@@ -58,8 +58,37 @@ def test_store_supports_listing(self, store: MemoryStore) -> None:
5858 assert store .supports_listing
5959
6060 async def test_list_prefix (self , store : MemoryStore ) -> None :
61+ """Stub – full regression test is below."""
6162 assert True
6263
64+ async def test_list_prefix_path_boundary (self , store : MemoryStore ) -> None :
65+ """list_prefix("0") must NOT return keys from a sibling path like "0_c/...".
66+
67+ Regression test for https://github.com/zarr-developers/zarr-python/issues/3773.
68+ Previously MemoryStore.list_prefix did a raw ``str.startswith`` check,
69+ so prefix "0" matched "0_c/zarr.json" as well as "0/zarr.json".
70+ LocalStore uses filesystem directory semantics and correctly returns only
71+ items under the "0/" directory; MemoryStore must behave consistently.
72+ """
73+ await self .set (store , "0/zarr.json" , self .buffer_cls .from_bytes (b"{}" ))
74+ await self .set (store , "0_c/zarr.json" , self .buffer_cls .from_bytes (b"{}" ))
75+ await self .set (store , "1/zarr.json" , self .buffer_cls .from_bytes (b"{}" ))
76+
77+ # list_prefix("0") must only return keys strictly under "0/"
78+ result = sorted ([k async for k in store .list_prefix ("0" )])
79+ assert result == ["0/zarr.json" ], (
80+ f"Expected ['0/zarr.json'], got { result !r} . "
81+ "list_prefix('0') must not match sibling paths starting with '0'."
82+ )
83+
84+ # Trailing slash should produce the same result
85+ result_slash = sorted ([k async for k in store .list_prefix ("0/" )])
86+ assert result_slash == ["0/zarr.json" ]
87+
88+ # list_prefix("") / list_prefix("/") should return all keys
89+ result_all = sorted ([k async for k in store .list_prefix ("" )])
90+ assert result_all == ["0/zarr.json" , "0_c/zarr.json" , "1/zarr.json" ]
91+
6392 @pytest .mark .parametrize ("dtype" , ["uint8" , "float32" , "int64" ])
6493 @pytest .mark .parametrize ("zarr_format" , [2 , 3 ])
6594 async def test_deterministic_size (
0 commit comments