fix(tests): Fix integration test failures in qdrant, sampling, and rag tests

- test_qdrant_collection_creation.py:
  - Add get_vector_params() helper to handle named vectors format
  - Collections use {"dense": VectorParams(...)} instead of direct VectorParams
  - Fix otel_service_name setting in test_collection_name_generation

- test_sampling.py:
  - Fix MCP response parsing: use json.loads(result.content[0].text)
    instead of result.structuredContent (which is None)
  - Add require_vector_sync_tools() helper for graceful skipping
  - Add helper call to all 5 test functions

- test_rag.py:
  - Add require_vector_sync_tools() helper for graceful skipping
  - Fix MCP response parsing (same as sampling tests)
  - Prevents 600s timeout when VECTOR_SYNC_ENABLED is not set

Tests now pass/skip cleanly when run independently. The anyio.WouldBlock
errors in full test suite runs are fixture isolation issues, not code bugs.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Chris Coutinho
2025-12-25 09:59:44 -06:00
parent 894bf5f916
commit 779d474aaa
3 changed files with 58 additions and 22 deletions
+29 -10
View File
@@ -13,6 +13,7 @@ Note: These tests require VECTOR_SYNC_ENABLED=true and a configured
vector database with indexed test data.
"""
import json
from unittest.mock import MagicMock
import pytest
@@ -21,6 +22,14 @@ from mcp.types import CreateMessageResult, TextContent
pytestmark = pytest.mark.integration
async def require_vector_sync_tools(nc_mcp_client):
"""Skip test if vector sync tools are not available."""
tools = await nc_mcp_client.list_tools()
tool_names = [t.name for t in tools.tools]
if "nc_get_vector_sync_status" not in tool_names:
pytest.skip("Vector sync tools not available (VECTOR_SYNC_ENABLED not set)")
@pytest.fixture
def mock_sampling_result():
"""Mock successful sampling result from MCP client."""
@@ -55,13 +64,15 @@ async def test_semantic_search_answer_successful_sampling(
4. Mock ctx.session.create_message to return answer
5. Verify response contains generated answer and sources
"""
await require_vector_sync_tools(nc_mcp_client)
# Get initial indexed count before creating note
import asyncio
initial_sync = await nc_mcp_client.call_tool(
"nc_get_vector_sync_status", arguments={}
)
initial_indexed_count = initial_sync.structuredContent["indexed_count"]
initial_indexed_count = json.loads(initial_sync.content[0].text)["indexed_count"]
print(f"Initial indexed count: {initial_indexed_count}")
# Create a note with content about Python async
@@ -90,7 +101,7 @@ Avoid blocking operations in async code.""",
sync_status = await nc_mcp_client.call_tool(
"nc_get_vector_sync_status", arguments={}
)
status_data = sync_status.structuredContent
status_data = json.loads(sync_status.content[0].text)
print(
f"Sync status at {waited}s: indexed={status_data['indexed_count']}, pending={status_data['pending_count']}, status={status_data['status']}"
@@ -135,7 +146,7 @@ Avoid blocking operations in async code.""",
assert call_result.isError is False, (
f"Tool call failed: {call_result.content[0].text if call_result.isError else ''}"
)
result = call_result.structuredContent
result = json.loads(call_result.content[0].text)
# Verify response structure
assert result is not None
@@ -179,6 +190,8 @@ async def test_semantic_search_answer_no_results(nc_mcp_client):
2. Verify response indicates no documents found
3. Verify no sampling call was made (no sources to base answer on)
"""
await require_vector_sync_tools(nc_mcp_client)
call_result = await nc_mcp_client.call_tool(
"nc_semantic_search_answer",
arguments={
@@ -192,7 +205,7 @@ async def test_semantic_search_answer_no_results(nc_mcp_client):
assert call_result.isError is False, (
f"Tool call failed: {call_result.content[0].text if call_result.isError else ''}"
)
result = call_result.structuredContent
result = json.loads(call_result.content[0].text)
# Should get "no documents found" message
assert result is not None
@@ -214,6 +227,8 @@ async def test_semantic_search_answer_with_limit(nc_mcp_client, temporary_note_f
3. Query with limit=2
4. Verify at most 2 sources in response
"""
await require_vector_sync_tools(nc_mcp_client)
# Create multiple related notes
_note1 = await temporary_note_factory(
title="Python Async Part 1",
@@ -242,7 +257,7 @@ async def test_semantic_search_answer_with_limit(nc_mcp_client, temporary_note_f
sync_status = await nc_mcp_client.call_tool(
"nc_get_vector_sync_status", arguments={}
)
status_data = sync_status.structuredContent
status_data = json.loads(sync_status.content[0].text)
if status_data["status"] == "idle" and status_data["pending_count"] == 0:
break
@@ -265,7 +280,7 @@ async def test_semantic_search_answer_with_limit(nc_mcp_client, temporary_note_f
assert call_result.isError is False, (
f"Tool call failed: {call_result.content[0].text if call_result.isError else ''}"
)
result = call_result.structuredContent
result = json.loads(call_result.content[0].text)
# Should respect limit
assert len(result["sources"]) <= 2
@@ -282,6 +297,8 @@ async def test_semantic_search_answer_score_threshold(
3. Query with high threshold (0.9)
4. Verify only high-scoring results returned
"""
await require_vector_sync_tools(nc_mcp_client)
_note = await temporary_note_factory(
title="Exact Match Test",
content="This is a very specific test document about widget manufacturing",
@@ -299,7 +316,7 @@ async def test_semantic_search_answer_score_threshold(
sync_status = await nc_mcp_client.call_tool(
"nc_get_vector_sync_status", arguments={}
)
status_data = sync_status.structuredContent
status_data = json.loads(sync_status.content[0].text)
if status_data["status"] == "idle" and status_data["pending_count"] == 0:
break
@@ -323,7 +340,7 @@ async def test_semantic_search_answer_score_threshold(
assert call_result.isError is False, (
f"Tool call failed: {call_result.content[0].text if call_result.isError else ''}"
)
result = call_result.structuredContent
result = json.loads(call_result.content[0].text)
# Note: Semantic search scores depend on embedding model
# We just verify the tool accepts the parameter
@@ -345,6 +362,8 @@ async def test_semantic_search_answer_max_tokens(nc_mcp_client, temporary_note_f
Note: Token limiting is enforced by the MCP client's LLM, not the server.
This test just verifies the parameter is correctly passed.
"""
await require_vector_sync_tools(nc_mcp_client)
_note = await temporary_note_factory(
title="Long Document",
content="This is a document with lots of content. " * 50,
@@ -362,7 +381,7 @@ async def test_semantic_search_answer_max_tokens(nc_mcp_client, temporary_note_f
sync_status = await nc_mcp_client.call_tool(
"nc_get_vector_sync_status", arguments={}
)
status_data = sync_status.structuredContent
status_data = json.loads(sync_status.content[0].text)
if status_data["status"] == "idle" and status_data["pending_count"] == 0:
break
@@ -386,7 +405,7 @@ async def test_semantic_search_answer_max_tokens(nc_mcp_client, temporary_note_f
assert call_result.isError is False, (
f"Tool call failed: {call_result.content[0].text if call_result.isError else ''}"
)
result = call_result.structuredContent
result = json.loads(call_result.content[0].text)
# Should not error, even if sampling fails
assert result is not None