import uuid from collections.abc import AsyncIterator, Iterator import pytest import pytest_asyncio from qdrant_client import AsyncQdrantClient from testcontainers.community.qdrant import QdrantContainer from src.config import QdrantSettings from src.infrastructure.qdrant.client import create_client # Pinned to match the `qdrant-client` major/minor in pyproject.toml. The # testcontainers default image trails it far enough that the client emits an # incompatibility warning, and testing against a version we do not deploy is # the wrong signal anyway. _QDRANT_IMAGE = "qdrant/qdrant:v1.19.0" @pytest.fixture(scope="session") def qdrant_container() -> Iterator[QdrantContainer]: with QdrantContainer(image=_QDRANT_IMAGE) as container: yield container @pytest.fixture(scope="session") def qdrant_url(qdrant_container: QdrantContainer) -> str: """The container's REST URL, pinned to IPv4. Same gotcha as postgres_url/minio_settings (see their conftests): testcontainers reports the host as `localhost`, which resolves to `::1` first, but Docker publishes the mapped port on IPv4 only. The IPv6 SYN is dropped rather than refused, so the client hangs until its timeout instead of falling back to the second address -- the connection does not fail, it hangs. """ host = qdrant_container.get_container_host_ip().replace("localhost", "127.0.0.1") return f"http://{host}:{qdrant_container.get_exposed_port(6333)}" @pytest.fixture def qdrant_settings(qdrant_url: str) -> QdrantSettings: """Settings naming a collection unique to this test (ADR-0016 isolation).""" return QdrantSettings(url=qdrant_url, collection=f"chunks_{uuid.uuid4().hex}") @pytest_asyncio.fixture(loop_scope="session") async def qdrant_client(qdrant_settings: QdrantSettings) -> AsyncIterator[AsyncQdrantClient]: client = create_client(qdrant_settings) try: yield client finally: await client.close()