Files
local-deep-research/tests/api_tests/test_research_creation.py
LearningCircuit 7d8e02a7e2 test: gate test_research_creation.py with @pytest.mark.requires_llm (#4288)
PUNCHLIST Tier 4 FLAKY_SLEEP flagged all 6 tests in TestResearchCreation
as flaky in CI:
- test_research_creation_endpoint (gpt-3.5-turbo + searxng required)
- test_research_creation_with_minimal_params (Ollama llama2 required)
- test_research_status_check (Ollama llama2, time.sleep(0.5))
- test_research_termination (time.sleep(0.5), race condition)
- test_research_modes (Ollama llama2, modes may not exist)
- test_research_with_custom_model (Ollama mistral required)

All tests POST to /api/start_research and need a live Ollama / searxng
stack to succeed. They have no skip markers, so CI was running them
unconditionally and flaking on infrastructure availability.

Added @pytest.mark.requires_llm at the class level (the project's
existing convention for tests needing a real LLM — see pyproject.toml
markers + tests/api_tests/test_rest_api.py for sibling usage). With
the marker:
- CI runs with `-m "not requires_llm"` will skip the entire class
- Manual runs with `-m requires_llm` can invoke them against a live
  Ollama + searxng setup

This is more conservative than deletion — preserves the tests for
manual integration runs but removes their CI flakiness.
2026-05-25 00:29:55 +02:00

220 lines
7.1 KiB
Python

#!/usr/bin/env python3
"""
Test research creation endpoint specifically
"""
import json
import time
import pytest
@pytest.mark.requires_llm
class TestResearchCreation:
"""Test research creation functionality.
All tests in this class POST to /api/start_research with real
Ollama / searxng / model dependencies (PUNCHLIST Tier 4: FLAKY_SLEEP).
The class-level @pytest.mark.requires_llm gate makes them auto-
skip in environments without a live LLM stack, so CI doesn't
flake on the timing-sensitive sleep(0.5) and model-availability
assertions. Manual runs can invoke them with -m requires_llm.
"""
def test_research_creation_endpoint(self, authenticated_client):
"""Test the research creation endpoint."""
print("\nTesting /api/start_research endpoint...")
research_data = {
"query": "Test research query from Python",
"mode": "quick",
"model": "gpt-3.5-turbo",
"search_engines": ["searxng"],
"local_context": 2000,
"web_context": 2000,
"temperature": 0.7,
}
response = authenticated_client.post(
"/api/start_research",
json=research_data,
content_type="application/json",
)
print(f"Response status: {response.status_code}")
assert response.status_code == 200
data = json.loads(response.data)
if data.get("status") == "success":
assert "research_id" in data
print(f"\n✅ SUCCESS! Research ID: {data.get('research_id')}")
return data["research_id"]
# Alternative response format
assert "research_id" in data
print(f"\n✅ SUCCESS! Research ID: {data['research_id']}")
return data["research_id"]
def test_research_creation_with_minimal_params(self, authenticated_client):
"""Test research creation with minimal parameters."""
research_data = {
"query": "Minimal test query",
"mode": "quick",
"model": "llama2",
"model_provider": "OLLAMA",
}
print(f"\n[DEBUG] Sending minimal research request: {research_data}")
response = authenticated_client.post(
"/api/start_research",
json=research_data,
content_type="application/json",
)
print(f"[DEBUG] Response status: {response.status_code}")
if response.status_code != 200:
print(f"[DEBUG] Response data: {response.data.decode()[:500]}")
assert response.status_code == 200
data = json.loads(response.data)
assert "research_id" in data or (
data.get("status") == "success" and "research_id" in data
)
def test_research_creation_validation(self, authenticated_client):
"""Test research creation validation."""
# Missing query
response = authenticated_client.post(
"/api/start_research",
json={"mode": "quick"},
content_type="application/json",
)
assert response.status_code == 400
data = json.loads(response.data)
assert "error" in data or "message" in data
def test_research_status_check(self, authenticated_client):
"""Test checking research status."""
# First create a research
research_data = {
"query": "Status check test",
"mode": "quick",
"model": "llama2",
"model_provider": "OLLAMA",
}
response = authenticated_client.post(
"/api/start_research",
json=research_data,
content_type="application/json",
)
assert response.status_code == 200
data = json.loads(response.data)
# Extract research_id
if "research_id" in data:
research_id = data["research_id"]
else:
research_id = data.get("data", {}).get("research_id")
assert research_id is not None
# Give it a moment to start
time.sleep(0.5) # allow: unmarked-sleep
# Check status
response = authenticated_client.get(
f"/api/research/{research_id}/status"
)
assert response.status_code == 200
status_data = json.loads(response.data)
assert "status" in status_data or "research_status" in status_data
def test_research_termination(self, authenticated_client):
"""Test terminating a research."""
# First create a research
research_data = {
"query": "Termination test",
"mode": "quick",
"model": "llama2",
"model_provider": "OLLAMA",
}
response = authenticated_client.post(
"/api/start_research",
json=research_data,
content_type="application/json",
)
assert response.status_code == 200
data = json.loads(response.data)
research_id = data.get("research_id") or data.get("data", {}).get(
"research_id"
)
assert research_id is not None
# Give it a moment to start
time.sleep(0.5) # allow: unmarked-sleep
# Terminate it
response = authenticated_client.post(f"/api/terminate/{research_id}")
assert response.status_code in [200, 404] # 404 if already finished
def test_research_modes(self, authenticated_client):
"""Test different research modes."""
modes = ["quick", "normal", "comprehensive"]
for mode in modes:
research_data = {
"query": f"Test {mode} mode",
"mode": mode,
"model": "llama2",
"model_provider": "OLLAMA",
}
response = authenticated_client.post(
"/api/start_research",
json=research_data,
content_type="application/json",
)
assert response.status_code == 200
data = json.loads(response.data)
assert "research_id" in data or (
data.get("status") == "success" and "research_id" in data
)
print(f"{mode} mode research created successfully")
# Terminate to clean up
research_id = data.get("research_id") or data.get("data", {}).get(
"research_id"
)
if research_id:
authenticated_client.post(f"/api/terminate/{research_id}")
def test_research_with_custom_model(self, authenticated_client):
"""Test research with custom model settings."""
research_data = {
"query": "Custom model test",
"mode": "quick",
"model_provider": "OLLAMA",
"model": "mistral",
"temperature": 0.5,
"max_tokens": 1000,
}
response = authenticated_client.post(
"/api/start_research",
json=research_data,
content_type="application/json",
)
assert response.status_code == 200
data = json.loads(response.data)
assert "research_id" in data or (
data.get("status") == "success" and "research_id" in data
)