fix: harden qdrant response parsing and integration markers
This commit is contained in:
@@ -698,3 +698,197 @@ def test_scroll_based_operations_paginate_until_next_page_offset_is_absent():
|
||||
if call[0] == "POST" and call[1].endswith("/points/scroll")
|
||||
]
|
||||
assert offsets[:2] == [None, "page-2"]
|
||||
|
||||
|
||||
_MISSING = object()
|
||||
|
||||
|
||||
def _set_response_path(payload, path, value):
|
||||
if value is _MISSING:
|
||||
parent = payload
|
||||
for key in path[:-1]:
|
||||
parent = parent[key]
|
||||
parent.pop(path[-1], None)
|
||||
return
|
||||
parent = payload
|
||||
for key in path[:-1]:
|
||||
parent = parent[key]
|
||||
parent[path[-1]] = value
|
||||
|
||||
|
||||
def _collection_response_with_shape(fake, path, value):
|
||||
original = fake.request
|
||||
|
||||
def request(method, url, **kwargs):
|
||||
response = original(method, url, **kwargs)
|
||||
if method == "GET" and url.endswith("/collections/workspace-semantic") and response.ok:
|
||||
payload = response.json()
|
||||
_set_response_path(payload, path, value)
|
||||
return FakeResponse(200, payload)
|
||||
return response
|
||||
|
||||
return request
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("path", "value"),
|
||||
[
|
||||
(("result",), None),
|
||||
(("result",), []),
|
||||
(("result",), "result"),
|
||||
(("result",), _MISSING),
|
||||
(("result", "config"), None),
|
||||
(("result", "config"), []),
|
||||
(("result", "config"), "config"),
|
||||
(("result", "config"), _MISSING),
|
||||
(("result", "config", "params"), None),
|
||||
(("result", "config", "params"), []),
|
||||
(("result", "config", "params"), "params"),
|
||||
(("result", "config", "params"), _MISSING),
|
||||
(("result", "config", "params", "vectors"), None),
|
||||
(("result", "config", "params", "vectors"), []),
|
||||
(("result", "config", "params", "vectors"), "vectors"),
|
||||
(("result", "config", "params", "vectors"), _MISSING),
|
||||
(("result", "config", "params", "vectors", "size"), None),
|
||||
(("result", "config", "params", "vectors", "size"), []),
|
||||
(("result", "config", "params", "vectors", "size"), "1024"),
|
||||
(("result", "config", "params", "vectors", "size"), _MISSING),
|
||||
(("result", "config", "params", "vectors", "distance"), None),
|
||||
(("result", "config", "params", "vectors", "distance"), []),
|
||||
(("result", "config", "params", "vectors", "distance"), 1),
|
||||
(("result", "config", "params", "vectors", "distance"), _MISSING),
|
||||
(("result", "payload_schema"), None),
|
||||
(("result", "payload_schema"), []),
|
||||
(("result", "payload_schema"), "schema"),
|
||||
(("result", "payload_schema"), _MISSING),
|
||||
(("result", "payload_schema", "kind"), None),
|
||||
(("result", "payload_schema", "kind"), []),
|
||||
(("result", "payload_schema", "kind"), "keyword"),
|
||||
(("result", "payload_schema", "kind", "data_type"), None),
|
||||
(("result", "payload_schema", "kind", "data_type"), []),
|
||||
(("result", "payload_schema", "kind", "data_type"), _MISSING),
|
||||
],
|
||||
)
|
||||
def test_collection_success_response_shapes_are_typed_errors(path, value):
|
||||
fake = FakeQdrantHttp()
|
||||
fake.collection = {"vectors": {"size": 1024, "distance": "Cosine"}}
|
||||
fake.payload_indexes = set(_REQUIRED_INDEXES)
|
||||
request = _collection_response_with_shape(fake, path, value)
|
||||
store = QdrantVectorStore(
|
||||
base_url="http://qdrant:6333", collection="workspace-semantic", workspace_id="demo",
|
||||
expected_dimension=1024, request=request,
|
||||
)
|
||||
|
||||
with pytest.raises(VectorResponseError):
|
||||
store.upsert("memory", [_write_record("memory:1", "memory")])
|
||||
|
||||
health = store.health()
|
||||
assert health.ok is False
|
||||
assert health.read_reachable is False
|
||||
assert health.write_reachable is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize("payload_value", [None, [], {}])
|
||||
def test_query_success_response_payload_leaf_shapes_are_typed_errors(payload_value):
|
||||
fake = FakeQdrantHttp()
|
||||
fake.collection = {"vectors": {"size": 1024, "distance": "Cosine"}}
|
||||
fake.payload_indexes = set(_REQUIRED_INDEXES)
|
||||
original = fake.request
|
||||
|
||||
def request(method, url, **kwargs):
|
||||
response = original(method, url, **kwargs)
|
||||
if method == "POST" and url.endswith("/points/query") and response.ok:
|
||||
payload = response.json()
|
||||
payload["result"]["points"] = [{
|
||||
"id": "p1", "score": 0.9,
|
||||
"payload": {"record_key": payload_value},
|
||||
}]
|
||||
return FakeResponse(200, payload)
|
||||
return response
|
||||
|
||||
store = _store(fake)
|
||||
store._request = request
|
||||
with pytest.raises(VectorResponseError):
|
||||
store.search(["memory"], [0.2] * 1024, limit=1, kinds=["memory"])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("point", [None, [], "point"])
|
||||
def test_query_success_response_point_shapes_are_typed_errors(point):
|
||||
fake = FakeQdrantHttp()
|
||||
fake.collection = {"vectors": {"size": 1024, "distance": "Cosine"}}
|
||||
fake.payload_indexes = set(_REQUIRED_INDEXES)
|
||||
original = fake.request
|
||||
|
||||
def request(method, url, **kwargs):
|
||||
response = original(method, url, **kwargs)
|
||||
if method == "POST" and url.endswith("/points/query") and response.ok:
|
||||
payload = response.json()
|
||||
payload["result"]["points"] = [point]
|
||||
return FakeResponse(200, payload)
|
||||
return response
|
||||
|
||||
store = _store(fake)
|
||||
store._request = request
|
||||
with pytest.raises(VectorResponseError):
|
||||
store.search(["memory"], [0.2] * 1024, limit=1, kinds=["memory"])
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("path", "value"),
|
||||
[
|
||||
(("result",), None),
|
||||
(("result",), []),
|
||||
(("result",), "result"),
|
||||
(("result",), _MISSING),
|
||||
(("result", "points"), None),
|
||||
(("result", "points"), {}),
|
||||
(("result", "points"), "points"),
|
||||
(("result", "points"), _MISSING),
|
||||
(("result", "next_page_offset"), []),
|
||||
(("result", "next_page_offset"), {}),
|
||||
(("result", "next_page_offset"), 1.5),
|
||||
(("result", "next_page_offset"), True),
|
||||
],
|
||||
)
|
||||
def test_scroll_success_response_shapes_are_typed_errors(path, value):
|
||||
fake = FakeQdrantHttp()
|
||||
fake.collection = {"vectors": {"size": 1024, "distance": "Cosine"}}
|
||||
fake.payload_indexes = set(_REQUIRED_INDEXES)
|
||||
original = fake.request
|
||||
|
||||
def request(method, url, **kwargs):
|
||||
response = original(method, url, **kwargs)
|
||||
if method == "POST" and url.endswith("/points/scroll") and response.ok:
|
||||
payload = response.json()
|
||||
_set_response_path(payload, path, value)
|
||||
return FakeResponse(200, payload)
|
||||
return response
|
||||
|
||||
store = QdrantVectorStore(
|
||||
base_url="http://qdrant:6333", collection="workspace-semantic", workspace_id="demo",
|
||||
expected_dimension=1024, request=request,
|
||||
)
|
||||
with pytest.raises(VectorResponseError):
|
||||
store.existing_hashes("memory", ["memory"])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("next_page_offset", [None, _MISSING])
|
||||
def test_scroll_accepts_null_or_missing_terminal_offset(next_page_offset):
|
||||
fake = FakeQdrantHttp()
|
||||
fake.collection = {"vectors": {"size": 1024, "distance": "Cosine"}}
|
||||
fake.payload_indexes = set(_REQUIRED_INDEXES)
|
||||
original = fake.request
|
||||
|
||||
def request(method, url, **kwargs):
|
||||
response = original(method, url, **kwargs)
|
||||
if method == "POST" and url.endswith("/points/scroll") and response.ok:
|
||||
payload = response.json()
|
||||
_set_response_path(payload, ("result", "next_page_offset"), next_page_offset)
|
||||
return FakeResponse(200, payload)
|
||||
return response
|
||||
|
||||
store = QdrantVectorStore(
|
||||
base_url="http://qdrant:6333", collection="workspace-semantic", workspace_id="demo",
|
||||
expected_dimension=1024, request=request,
|
||||
)
|
||||
assert store.existing_hashes("memory", ["memory"]) == {}
|
||||
|
||||
@@ -477,6 +477,7 @@ def test_registry_render_chain_produces_require_existing_qdrant_configs(
|
||||
assert not [call for call in fake.calls if call[0] == "PUT"]
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
def test_registry_rendered_session_and_maintenance_configs_bind_equally(tmp_path):
|
||||
session_path, maintenance_path = _render_registry_runtime_configs(tmp_path)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user