dp-zp-agent/test/test_question_delivery.py

110 lines
4.6 KiB
Python

"""Question/engine binding across actual HTTP/MCP boundaries, without an LLM."""
import asyncio
import json
import os
import sys
import pytest
from test_claude_packages import backend, ROOT
from packages.claude import client
QUESTIONS = [
"Čomu sa venuje diplomová práca Jána Holpa? Stručne ju predstav a uveď zdroj.",
"Porovnaj zadanie a výsledky Ondreja Megelu a Jána Holpa.\nPlán nie je výsledok; uveď zdroje.",
'Ktorí študenti majú tag "RAG" a zároveň diplomový projekt z roku 2026?',
]
@pytest.mark.parametrize('engine',['rag','graphrag'])
def test_context_token_crosses_real_http_without_model_rewriting(backend,engine):
result=client.query_backend('Daj obe.',engine,context_token='opaque-context')
assert result['accepted_for_delivery'] and result['query']=='Daj obe.'
body=backend['requests'][-1][1]
assert body['query']=='Daj obe.' and body['context_token']=='opaque-context'
@pytest.mark.parametrize("engine", ["rag", "graphrag"])
def test_latest_question_and_all_conditions_cross_http_unchanged(backend, engine):
for question in QUESTIONS:
r = client.query_backend(question, engine, limit=7)
assert r["accepted_for_delivery"] and r["query"] == question and r["engine"] == engine
path, body, _ = backend["requests"][-1]
assert (
path == "/answer"
and body["query"] == question
and body["engine"] == engine
and body["limit"] == 7
)
assert len(backend["requests"]) == len(QUESTIONS)
@pytest.mark.parametrize("engine", ["rag", "graphrag"])
def test_only_a_matching_leading_command_is_removed(backend, engine):
question = 'Aké práce má Ján Holp v roku 2020? Zachovaj výraz "RAG".'
r = client.query_backend(" /" + engine.upper() + " " + question + " ", engine)
assert r["accepted_for_delivery"] and r["query"] == question
assert backend["requests"][-1][1]["query"] == question
other = "graphrag" if engine == "rag" else "rag"
r = client.query_backend("/" + other + " " + question, engine)
assert r["reason"] == "engine_mismatch" and not r["accepted_for_delivery"]
assert len(backend["requests"]) == 1
@pytest.mark.parametrize("question", ["/rag", "/rag ", "/graphrag"])
def test_command_without_a_question_never_reaches_backend(backend, question):
engine = "graphrag" if question.startswith("/graphrag") else "rag"
assert client.query_backend(question, engine)["reason"] == "invalid_query"
assert not backend["requests"]
@pytest.mark.parametrize(
"change",
[
{"query": "A previous unrelated question", "engine": "rag"},
{"query": QUESTIONS[0], "engine": "graphrag"},
],
)
def test_stale_question_or_wrong_engine_cannot_deliver_another_answer(backend, change):
backend["raw"] = json.dumps(
{**backend["checked"], "answer": "OTHER_ANSWER_SHOULD_NOT_ESCAPE", **change}
).encode()
r = client.query_backend(QUESTIONS[0], "rag")
assert r["reason"] == "question_mismatch" and not r["accepted_for_delivery"]
assert "OTHER_ANSWER" not in json.dumps(r)
def test_missing_question_echo_is_not_accepted(backend):
backend["raw"] = json.dumps({**backend["checked"], "engine": "rag"}).encode()
r = client.query_backend(QUESTIONS[0], "rag")
assert r["reason"] == "invalid_backend_response" and not r["accepted_for_delivery"]
def test_consecutive_mcp_calls_do_not_reuse_previous_queries(backend):
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from packages.claude.mcp_server import QUESTION_POLICY
async def run():
params = StdioServerParameters(
command=sys.executable,
args=[str(ROOT / "packages/claude/mcp_server.py")],
env={**os.environ, "PYTHONDONTWRITEBYTECODE": "1"},
)
async with stdio_client(params) as (read, write):
async with ClientSession(read, write) as session:
initialized = await session.initialize()
assert QUESTION_POLICY in initialized.instructions
for engine, question in [
("rag", QUESTIONS[0]),
("graphrag", QUESTIONS[2]),
("rag", QUESTIONS[1]),
]:
result = await session.call_tool("zpwiki_" + engine, {"query": question})
assert not result.isError and result.structuredContent["query"] == question
assert backend["requests"][-1][1]["query"] == question
assert backend["requests"][-1][1]["engine"] == engine
asyncio.run(run())