- Fix 71 invalid-syntax files (class-body newline-broken assignments) - Add from/None chain to 307 B904 raise-without-from sites - Add B008 ignore to ruff.toml (already in pyproject.toml) - Noqa F401 on __init__.py re-exports (137 sites) - Noqa E402 on deferred imports (63 sites) - Bulk-add stdlib/FastAPI/project imports for F821 (127 sites) - Replace ×→x, –→-, …→... in docstrings (4093 chars) - Manual refactor of 5 SIM103/SIM116 patterns Tests: 791 passed (66 deselected due to pre-existing Redis issues in test_rag.py) Co-authored-by: opencode <opencode@rugmunch.io>
93 lines
3.3 KiB
Python
93 lines
3.3 KiB
Python
"""T15 - Qdrant test_col_* cleanup guard.
|
|
|
|
CI runs this after integration tests to verify no test_col_* collections
|
|
are left behind. Test fixtures SHOULD use unique names (uuid-based) and
|
|
clean up in teardown - this test catches regressions where they don't.
|
|
|
|
Also includes a pytest fixture that creates a unique test collection,
|
|
yields its name, and drops it on teardown - even on test failure.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import uuid
|
|
|
|
import httpx
|
|
import pytest
|
|
|
|
QDRANT_URL = os.getenv("QDRANT_URL", "http://localhost:6333")
|
|
|
|
|
|
def _list_collections() -> list[str]:
|
|
"""Fetch all Qdrant collection names."""
|
|
r = httpx.get(f"{QDRANT_URL}/collections", timeout=5.0)
|
|
r.raise_for_status()
|
|
return [c["name"] for c in r.json().get("result", {}).get("collections", [])]
|
|
|
|
|
|
def test_no_test_col_artifacts_after_test_run() -> None:
|
|
"""No test_col_* collections may exist after integration tests run.
|
|
|
|
A test_col_* artifact means a test forgot to clean up. With many
|
|
integration tests over time these accumulate (memory waste, query
|
|
slowdown, MCP agent confusion). This test catches any regression
|
|
where new tests don't clean up after themselves.
|
|
"""
|
|
names = _list_collections()
|
|
leaked = [n for n in names if n.startswith("test_col")]
|
|
assert not leaked, (
|
|
f"Found {len(leaked)} test_col_* collection artifacts left behind "
|
|
f"by integration tests: {leaked}. "
|
|
f"Fix: use the `clean_test_collection` fixture below, or add "
|
|
f"explicit teardown in your test that drops the collection."
|
|
)
|
|
|
|
|
|
def test_no_legacy_test_col_collection() -> None:
|
|
"""The bare 'test_col' collection (no suffix) is a known test artifact
|
|
from before fixtures used uuid suffixes. Catch any regression.
|
|
"""
|
|
names = _list_collections()
|
|
assert "test_col" not in names, (
|
|
"Bare 'test_col' collection found - this is a test artifact that "
|
|
"should never be present in production. Use the "
|
|
"`clean_test_collection` fixture for all Qdrant tests."
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def clean_test_collection() -> str:
|
|
"""Yield a unique Qdrant collection name and auto-drop on teardown.
|
|
|
|
Usage:
|
|
def test_my_qdrant_feature(clean_test_collection):
|
|
# collection is auto-created with a unique uuid name
|
|
qdrant.upsert(clean_test_collection, points=[...])
|
|
assert qdrant.search(clean_test_collection, vector=[...])
|
|
|
|
The fixture handles:
|
|
- Creates a fresh collection per test (no shared state)
|
|
- UUID-based name so parallel test runs don't collide
|
|
- Drops the collection on teardown, even if the test fails
|
|
- Skips the test if Qdrant is unreachable (graceful)
|
|
"""
|
|
name = f"test_col_{uuid.uuid4().hex[:12]}"
|
|
try:
|
|
httpx.put(
|
|
f"{QDRANT_URL}/collections/{name}",
|
|
json={"vectors": {"size": 4, "distance": "Cosine"}},
|
|
timeout=10.0,
|
|
).raise_for_status()
|
|
except (httpx.HTTPError, httpx.ConnectError) as e:
|
|
pytest.skip(f"Qdrant unreachable at {QDRANT_URL}: {e}")
|
|
return name # unreachable, for type checker
|
|
|
|
try:
|
|
yield name
|
|
finally:
|
|
# Always drop, even on test failure
|
|
try: # noqa: SIM105
|
|
httpx.delete(f"{QDRANT_URL}/collections/{name}", timeout=5.0)
|
|
except Exception:
|
|
pass # best-effort cleanup
|