fix: close task3 qdrant and config quality gaps

This commit is contained in:
2026-08-11 08:06:18 +02:00
parent 88d1790728
commit b7ea5443b3
8 changed files with 290 additions and 47 deletions
+87 -34
View File
@@ -5,7 +5,17 @@ import requests
from qdrant_test_helpers import FakeQdrantHttp, FakeResponse, _write_record
from tht.adapters.vector.qdrant import QdrantVectorStore, point_id
from tht.ports.vector import VectorStoreError
from tht.ports.vector import (
SemanticIndexIncompatibleError,
VectorResponseError,
VectorStoreError,
VectorTransportError,
)
_REQUIRED_INDEXES = {
"content_hash", "document_id", "kind", "record_key", "record_kind",
"vector_generation", "workspace_id", "workspace_revision",
}
def _store(fake: FakeQdrantHttp, *, collection_lifecycle="create_if_missing") -> QdrantVectorStore:
@@ -51,29 +61,7 @@ def test_require_existing_refuses_missing_collection_without_mutations():
assert [call for call in fake.calls if call[0] == "PUT"] == []
def test_require_existing_preflights_before_existing_hash_scroll():
fake = FakeQdrantHttp()
original_request = fake.request
def request(method, url, **kwargs):
if method == "POST" and url.endswith("/points/scroll"):
return FakeResponse(404, {"status": "error"})
return original_request(method, url, **kwargs)
store = QdrantVectorStore(
base_url="http://qdrant:6333", collection="workspace-semantic", workspace_id="demo",
workspace_revision="a" * 40, expected_dimension=1024,
collection_lifecycle="require_existing", request=request,
)
with pytest.raises(VectorStoreError, match="semantic_index_incompatible"):
store.existing_hashes("memory", ["memory"])
assert [call for call in fake.calls if call[1].endswith("/points/scroll")] == []
assert [call for call in fake.calls if call[0] == "PUT"] == []
def test_require_existing_delete_maps_collection_404_after_preflight():
def test_require_existing_maps_scroll_404_after_compatible_preflight():
fake = FakeQdrantHttp()
fake.collection = {"vectors": {"size": 1024, "distance": "Cosine"}}
fake.payload_indexes = {
@@ -81,16 +69,9 @@ def test_require_existing_delete_maps_collection_404_after_preflight():
"vector_generation", "workspace_id", "workspace_revision",
}
original_request = fake.request
deleted = False
def request(method, url, **kwargs):
nonlocal deleted
if method == "GET" and url.endswith("/collections/workspace-semantic") and not deleted:
response = original_request(method, url, **kwargs)
deleted = True
fake.collection = None
return response
if method == "POST" and url.endswith("/points/delete?wait=true"):
if method == "POST" and url.endswith("/points/scroll"):
original_request(method, url, **kwargs)
return FakeResponse(404, {"status": "error"})
return original_request(method, url, **kwargs)
@@ -101,8 +82,79 @@ def test_require_existing_delete_maps_collection_404_after_preflight():
collection_lifecycle="require_existing", request=request,
)
with pytest.raises(VectorStoreError, match="semantic_index_incompatible"):
store.delete_kinds("memory", ["memory"])
with pytest.raises(SemanticIndexIncompatibleError):
store.existing_hashes("memory", ["memory"])
assert [call for call in fake.calls if call[1].endswith("/points/scroll")]
assert [call for call in fake.calls if call[0] == "PUT"] == []
@pytest.mark.parametrize(
("operation", "generation"),
[("delete_kinds", None), ("delete_generation", "gen:" + "a" * 32)],
)
def test_require_existing_maps_delete_scroll_404_after_compatible_preflight(operation, generation):
fake = FakeQdrantHttp()
fake.collection = {"vectors": {"size": 1024, "distance": "Cosine"}}
fake.payload_indexes = {
"content_hash", "document_id", "kind", "record_key", "record_kind",
"vector_generation", "workspace_id", "workspace_revision",
}
original_request = fake.request
def request(method, url, **kwargs):
if method == "POST" and url.endswith("/points/scroll"):
original_request(method, url, **kwargs)
return FakeResponse(404, {"status": "error"})
return original_request(method, url, **kwargs)
store = QdrantVectorStore(
base_url="http://qdrant:6333", collection="workspace-semantic", workspace_id="demo",
workspace_revision="a" * 40, expected_dimension=1024,
collection_lifecycle="require_existing", request=request,
)
with pytest.raises(SemanticIndexIncompatibleError):
if operation == "delete_kinds":
store.delete_kinds("memory", ["memory"])
else:
store.delete_generation("evidence", generation, "demo")
assert [call for call in fake.calls if call[1].endswith("/points/scroll")]
assert [call for call in fake.calls if call[0] == "POST" and "delete" in call[1]] == []
def test_require_existing_scroll_non_404_remains_transport_error():
fake = FakeQdrantHttp()
fake.collection = {"vectors": {"size": 1024, "distance": "Cosine"}}
fake.payload_indexes = set(_REQUIRED_INDEXES)
original_request = fake.request
def request(method, url, **kwargs):
if method == "POST" and url.endswith("/points/scroll"):
original_request(method, url, **kwargs)
return FakeResponse(503, {"status": "error"})
return original_request(method, url, **kwargs)
store = QdrantVectorStore(
base_url="http://qdrant:6333", collection="workspace-semantic", workspace_id="demo",
workspace_revision="a" * 40, expected_dimension=1024,
collection_lifecycle="require_existing", request=request,
)
with pytest.raises(VectorTransportError) as caught:
store.existing_hashes("memory", ["memory"])
assert caught.value.status_code == 503
def test_require_existing_scroll_malformed_remains_response_error():
fake = FakeQdrantHttp()
fake.collection = {"vectors": {"size": 1024, "distance": "Cosine"}}
fake.payload_indexes = set(_REQUIRED_INDEXES)
fake.malformed_scroll = True
store = _store(fake, collection_lifecycle="require_existing")
with pytest.raises(VectorResponseError):
store.existing_hashes("memory", ["memory"])
@pytest.mark.parametrize(
("dimension", "distance", "indexes", "index_types"),
@@ -581,6 +633,7 @@ def test_upsert_payload_keeps_canonical_identity_when_metadata_collides():
def test_scroll_based_operations_paginate_until_next_page_offset_is_absent():
fake = FakeQdrantHttp()
fake.collection = {"vectors": {"size": 1024, "distance": "Cosine"}}
generation_a = "gen:" + "1" * 32
generation_b = "gen:" + "2" * 32
fake.scroll_pages = [