129 lines
4.8 KiB
Python
129 lines
4.8 KiB
Python
import pytest
|
|
from fastapi import HTTPException
|
|
|
|
from app.schemas.ai import SituationalAnalysisRequest
|
|
from app.services.ai_tools import web_search as web_search_module
|
|
from app.services.ai_tools.schemas import SearchEvidence, WebSearchConfig, WebSearchProviderConfig
|
|
from app.services.ai_tools.web_search import WebSearchClient
|
|
from app.services import ai_client as ai_client_module
|
|
from app.services.ai_client import AIProviderClient
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ai_client_analyze_logs_summary_without_prompt(monkeypatch):
|
|
events = []
|
|
|
|
async def fake_emit_business_log(_logger, **payload):
|
|
events.append(payload)
|
|
|
|
async def fake_request(self, method, path, json=None, request_id=None, operation="request", payload_summary=None):
|
|
return {
|
|
"provider": "test-provider",
|
|
"model": "test-model",
|
|
"content": "ok",
|
|
"content_blocks": [],
|
|
"text_blocks": ["ok"],
|
|
"thinking_blocks": [],
|
|
"raw_response": {},
|
|
}
|
|
|
|
monkeypatch.setattr(ai_client_module, "emit_business_log", fake_emit_business_log)
|
|
monkeypatch.setattr(AIProviderClient, "_request", fake_request)
|
|
|
|
client = AIProviderClient(
|
|
service_url="http://provider.test",
|
|
llm_config={"provider": "openai", "provider_api": "openai-completions", "model": "gpt-test", "api_key": "sk-secret"},
|
|
)
|
|
result = await client.analyze(
|
|
SituationalAnalysisRequest(
|
|
title="Sensitive title",
|
|
objective="Do not store this full prompt",
|
|
observations=["secret observation"],
|
|
constraints=["secret constraint"],
|
|
context={"source": "test", "private": "value"},
|
|
),
|
|
request_id="req-ai-test",
|
|
)
|
|
|
|
assert result.model == "test-model"
|
|
assert [event["event"] for event in events] == [
|
|
"ai.provider.analyze.start",
|
|
"ai.provider.analyze.success",
|
|
]
|
|
serialized = str(events)
|
|
assert "Do not store this full prompt" not in serialized
|
|
assert "secret observation" not in serialized
|
|
assert "sk-secret" not in serialized
|
|
start_context = events[0]["context"]
|
|
assert start_context["model"] == "gpt-test"
|
|
assert start_context["input_summary"]["objective_length"] == len("Do not store this full prompt")
|
|
assert start_context["input_summary"]["observation_count"] == 1
|
|
assert start_context["input_summary"]["context_keys"] == ["private", "source"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ai_client_analyze_logs_failure(monkeypatch):
|
|
events = []
|
|
|
|
async def fake_emit_business_log(_logger, **payload):
|
|
events.append(payload)
|
|
|
|
async def fake_request(self, method, path, json=None, request_id=None, operation="request", payload_summary=None):
|
|
raise HTTPException(status_code=502, detail="provider failed")
|
|
|
|
monkeypatch.setattr(ai_client_module, "emit_business_log", fake_emit_business_log)
|
|
monkeypatch.setattr(AIProviderClient, "_request", fake_request)
|
|
|
|
client = AIProviderClient(service_url="http://provider.test", llm_config={"provider": "openai", "model": "gpt-test"})
|
|
|
|
with pytest.raises(HTTPException):
|
|
await client.analyze(
|
|
SituationalAnalysisRequest(title="T", objective="O", observations=["one"]),
|
|
request_id="req-ai-fail",
|
|
)
|
|
|
|
assert events[-1]["event"] == "ai.provider.analyze.failed"
|
|
assert events[-1]["level"] == "error"
|
|
assert events[-1]["context"]["error_type"] == "HTTPException"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_web_search_logs_query_hash_without_query(monkeypatch):
|
|
events = []
|
|
|
|
async def fake_emit_business_log(_logger, **payload):
|
|
events.append(payload)
|
|
|
|
async def fake_search_tavily(self, config, query, max_results, domains, freshness_days):
|
|
return [
|
|
SearchEvidence(
|
|
title="Example",
|
|
url="https://example.test",
|
|
snippet="result",
|
|
source_provider="tavily",
|
|
)
|
|
]
|
|
|
|
monkeypatch.setattr(web_search_module, "emit_business_log", fake_emit_business_log)
|
|
monkeypatch.setattr(WebSearchClient, "_search_tavily", fake_search_tavily)
|
|
|
|
client = WebSearchClient(
|
|
WebSearchConfig(
|
|
enabled=True,
|
|
default_provider="tavily",
|
|
providers={"tavily": WebSearchProviderConfig(provider="tavily", api_key="secret-key")},
|
|
)
|
|
)
|
|
results = await client.search("secret query text", max_results=1)
|
|
|
|
assert len(results) == 1
|
|
assert [event["event"] for event in events] == [
|
|
"ai_tool.web_search.start",
|
|
"ai_tool.web_search.success",
|
|
]
|
|
serialized = str(events)
|
|
assert "secret query text" not in serialized
|
|
assert "secret-key" not in serialized
|
|
assert events[0]["context"]["query_length"] == len("secret query text")
|
|
assert events[1]["context"]["result_count"] == 1
|