fix: harden qdrant payload and paging

This commit is contained in:
2026-08-08 17:48:56 +02:00
parent 7109ee15c4
commit f61648fb69
4 changed files with 206 additions and 11 deletions
+107 -1
View File
@@ -38,6 +38,7 @@ class FakeQdrantHttp:
self.fail_request: Exception | None = None
self.malformed_query = False
self.malformed_scroll = False
self.scroll_pages: list[dict] | None = None
def request(self, method, url, *, json=None, timeout=None):
self.calls.append((method, url, json))
@@ -98,11 +99,23 @@ class FakeQdrantHttp:
if method == "POST" and path == "/collections/workspace-semantic/points/scroll":
if self.malformed_scroll:
return FakeResponse(200, {"result": {"points": "bad"}})
if self.scroll_pages is not None:
offset = json.get("offset")
for page in self.scroll_pages:
if page["offset"] == offset:
filtered = _match_points(page["points"], json["filter"])
return FakeResponse(200, {
"result": {
"points": filtered,
"next_page_offset": page["next_page_offset"],
}
})
raise AssertionError(("unexpected offset", offset, self.scroll_pages))
wanted = sorted(
_match_points(self.points.values(), json["filter"]),
key=lambda point: point["payload"]["record_key"],
)
return FakeResponse(200, {"result": {"points": wanted}})
return FakeResponse(200, {"result": {"points": wanted, "next_page_offset": None}})
if method == "POST" and path == "/collections/workspace-semantic/points/delete":
doomed = [point["id"] for point in _match_points(self.points.values(), json["filter"])]
@@ -310,3 +323,96 @@ def test_sanitizes_timeout_and_malformed_responses():
fake.malformed_scroll = True
with pytest.raises(VectorStoreError, match="Qdrant returned malformed scroll response"):
store.existing_hashes("memory", ["memory"])
def test_upsert_payload_keeps_canonical_identity_when_metadata_collides():
fake = FakeQdrantHttp()
store = _store(fake)
record = _write_record(
"memory:1",
"memory",
metadata={
"workspace_id": "evil",
"kind": "evil",
"record_kind": "evil",
"record_key": "evil",
"content_hash": "evil",
},
)
store.upsert("memory", [record])
payload = next(iter(fake.points.values()))["payload"]
assert payload["workspace_id"] == "demo"
assert payload["kind"] == "memory"
assert payload["record_kind"] == "memory"
assert payload["record_key"] == "memory:1"
assert payload["content_hash"] == "sha256:" + "a" * 64
def test_scroll_based_operations_paginate_until_next_page_offset_is_absent():
fake = FakeQdrantHttp()
generation_a = "gen:" + "1" * 32
generation_b = "gen:" + "2" * 32
fake.scroll_pages = [
{
"offset": None,
"points": [
{
"id": "p1",
"payload": {
"workspace_id": "demo",
"kind": "evidence",
"record_kind": "evidence",
"record_key": f"demo:{generation_a}:chunk:1",
"content_hash": "sha256:" + "a" * 64,
"vector_generation": generation_a,
},
}
],
"next_page_offset": "page-2",
},
{
"offset": "page-2",
"points": [
{
"id": "p2",
"payload": {
"workspace_id": "demo",
"kind": "evidence",
"record_kind": "evidence",
"record_key": f"demo:{generation_a}:chunk:2",
"content_hash": "sha256:" + "b" * 64,
"vector_generation": generation_a,
},
},
{
"id": "p3",
"payload": {
"workspace_id": "demo",
"kind": "evidence",
"record_kind": "evidence",
"record_key": f"demo:{generation_b}:chunk:3",
"content_hash": "sha256:" + "c" * 64,
"vector_generation": generation_b,
},
},
],
"next_page_offset": None,
},
]
store = _store(fake)
assert store.existing_hashes("evidence", ["evidence"]) == {
f"demo:{generation_a}:chunk:1": "sha256:" + "a" * 64,
f"demo:{generation_a}:chunk:2": "sha256:" + "b" * 64,
f"demo:{generation_b}:chunk:3": "sha256:" + "c" * 64,
}
assert store.list_evidence_generations("evidence", "demo") == [generation_a, generation_b]
assert store.delete_generation("evidence", generation_a, "demo") == 2
offsets = [
call[2].get("offset")
for call in fake.calls
if call[0] == "POST" and call[1].endswith("/points/scroll")
]
assert offsets[:2] == [None, "page-2"]