diff --git a/Makefile b/Makefile index 9629771..daae7e6 100644 --- a/Makefile +++ b/Makefile @@ -50,10 +50,35 @@ run: ## Dev server on :8778 with reload (8089 is the container, not this) ./wakeup.sh .PHONY: test -test: ## Run the test suite +test: ## Run the test suite (no network needed — live tests are marked and self-skip) @test -x $(VENV)/bin/python || { echo "FAIL — no venv; run: make setup"; exit 69; } $(VENV)/bin/python -m pytest tests/ +# The `integration` marker and its RUN_INTEGRATION_TESTS/TEST_TENANT gate already +# lived in tests/conftest.py before T-55 (see test_integration.py, +# test_tenant_isolation_live.py, test_quality_report_live.py, and the +# TestWikiChangeListenerIntegration class) — `make test` never ran them because +# `pytest_collection_modifyitems` skips anything carrying the marker unless +# RUN_INTEGRATION_TESTS=1. That gate made them silent under `make test`, but +# nothing ran them WITH the flag set either, so "runnable" had never been +# reasserted. This target is that home (D-26): it sets the flag, selects the +# marker, and — the part that matters — fails loudly if selection ever drops to +# zero, since a target that passes by collecting nothing is worse than one that +# needs a network (T-55). +.PHONY: test-integration +test-integration: ## Live tests against neo4j/qdrant/wikijs/searxng/ollama (needs network + services) + @test -x $(VENV)/bin/python || { echo "FAIL — no venv; run: make setup"; exit 69; } + @$(VENV)/bin/python -m pytest tests/ -m integration --collect-only -q >/dev/null 2>&1; \ + rc=$$?; \ + if [ "$$rc" = "5" ]; then \ + echo "FAIL test-integration — selected 0 tests (marker renamed, moved, or lost — this is a defect, not a pass)"; \ + exit 1; \ + elif [ "$$rc" != "0" ]; then \ + echo "FAIL test-integration — collection errored (rc=$$rc)"; \ + exit $$rc; \ + fi + RUN_INTEGRATION_TESTS=1 $(VENV)/bin/python -m pytest tests/ -m integration -v + .PHONY: lint lint: ## ruff check over the sources @test -x $(VENV)/bin/ruff || { echo "FAIL — ruff not installed; run: make setup"; exit 69; } diff --git a/tests/test_entity_linking.py b/tests/test_entity_linking.py index 406c646..21ad40c 100644 --- a/tests/test_entity_linking.py +++ b/tests/test_entity_linking.py @@ -285,6 +285,7 @@ class TestAddEntityLinksToContent: # Integration Tests - Full Entity Linking Flow # ============================================================================ +@pytest.mark.integration class TestEntityLinkingIntegration: """Test full entity linking flow.""" @@ -421,6 +422,7 @@ class TestEntityLinkingIntegration: # Multi-Tenancy Tests # ============================================================================ +@pytest.mark.integration class TestEntityLinkingMultiTenancy: """Test multi-tenancy isolation in entity linking.""" @@ -464,6 +466,7 @@ class TestEntityLinkingMultiTenancy: # Cleanup # ============================================================================ +@pytest.mark.integration @pytest.mark.asyncio async def test_cleanup_entity_linking_test_data(neo4j_client): """Clean up all test data created by entity linking tests.""" diff --git a/tests/test_hybrid_rag.py b/tests/test_hybrid_rag.py index 5f6eb5f..3fa76d7 100644 --- a/tests/test_hybrid_rag.py +++ b/tests/test_hybrid_rag.py @@ -32,6 +32,13 @@ from src.services.graph_service import GraphService from src.models.hybrid_rag import HybridRAGConfig from src.config import get_settings +# Every test in this module goes through hybrid_rag_service -> graph_service -> +# neo4j_client, which opens a real Bolt connection at fixture setup (T-55/D-26). +# There is no unit/integration split within the file: even the fusion/formatting +# classes that look like pure logic still resolve the full fixture chain, so the +# whole module is marked rather than picking classes apart from underneath. +pytestmark = pytest.mark.integration + # Test user to isolate test data TEST_USER = "llm-tester"