Files
agent_jrxml/tests/test_agent.py
T
panda 1210b926c3 fix: MAX_RETRY 5 + rolling continuation + namespace-aware JRXML extraction
- MAX_RETRY: 3→5 (graph.py:35, nodes.py:25) with env override
- Rolling continuation: _generate_with_continuation() auto-detects
  truncated JRXML and sends anchor-based continuation, max 3 rounds
- JRXML extraction: regex/end-tag now namespace-prefix aware
  (ns0:jasperReport, ns:jasperReport, etc.)
- All 5 generation nodes refactored to use continuation helper
- Tests updated: scenario1 accepts ns-prefixed root, max_retry
  verifies graph termination
- stop_reason capture + WARNING log on max_tokens truncation
- Correction prompt now injects OCR context + layout schema
2026-05-23 10:58:46 +08:00

145 lines
6.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""JRXML 代理集成测试 - 4 个验收场景。
这些测试模拟多轮对话并验证代理管道。
需要验证服务在 8001 端口上运行。
"""
import os
import sys
import pytest
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from dotenv import load_dotenv
load_dotenv()
from agent.graph import build_graph, create_initial_state
@pytest.fixture
def graph():
return build_graph()
def run_graph(graph, initial_state):
"""使用给定的初始状态运行图并返回最终状态。"""
final = None
for event in graph.stream(initial_state):
for node_name, node_state in event.items():
final = node_state
return final
class TestAcceptanceScenarios:
def test_scenario1_simple_report_generation(self, graph):
"""场景 1:生成简单的员工名册 - 应该通过验证。"""
state = create_initial_state()
state["user_input"] = (
"Generate an employee roster report with columns: employee_id (Integer), "
"full_name (String), department (String), and salary (BigDecimal). "
"Query from employees table. Include a title 'Employee Roster'."
)
state["stage"] = "initial_generation"
final = run_graph(graph, state)
assert final.get("current_jrxml"), "应该已生成 JRXML"
assert final.get("status") in ("pass", "fail"), f"意外状态: {final.get('status')}"
import re
assert re.search(r"<[\w:]*jasperReport", final["current_jrxml"]), \
"输出应包含合法 JRXML 根元素(支持命名空间前缀如 ns0:jasperReport"
def test_scenario2_auto_correction(self, graph):
"""场景 2:故意提出一个可能初次失败的需求。"""
state = create_initial_state()
state["user_input"] = (
"Create a sales summary report. Show customer_name and order_total. "
"Add a subtotal by customer group. Query from orders table."
)
state["stage"] = "initial_generation"
final = run_graph(graph, state)
assert final.get("retry_count", 0) <= 5, "不应超过最大重试次数"
assert "status" in final, "最终状态应包含 status 字段"
assert final.get("current_jrxml") or final.get("error_msg"), "应有输出或错误消息"
def test_scenario3_multi_turn_modification(self, graph):
"""场景 3:多轮对话 - 先生成,再修改两次。"""
# 第 1 轮:生成销售订单报表
state = create_initial_state()
state["user_input"] = (
"Create a sales order report with order_id (String), customer (String), "
"amount (BigDecimal), order_date (Date). Query from sales_orders."
)
state["stage"] = "initial_generation"
final = run_graph(graph, state)
assert final.get("current_jrxml"), "第 1 轮应该已生成 JRXML"
assert final.get("status") in ("pass", "fail")
# 第 2 轮:添加月度销售汇总
state2 = final.copy()
state2["user_input"] = "Add a monthly sales total summary in the summary band."
state2["user_modification_request"] = "Add a monthly sales total summary in the summary band."
state2["stage"] = "modification"
state2["retry_count"] = 0
final2 = run_graph(graph, state2)
assert final2.get("current_jrxml"), "第 2 轮应该已修改 JRXML"
assert final2.get("status") in ("pass", "fail")
# 第 3 轮:修改标题
state3 = final2.copy()
state3["user_input"] = "Change the title to '2024 Annual Sales Report' and make it bold."
state3["user_modification_request"] = "Change the title to '2024 Annual Sales Report' and make it bold."
state3["stage"] = "modification"
state3["retry_count"] = 0
final3 = run_graph(graph, state3)
jrxml = final3.get("current_jrxml", "")
assert "2024" in jrxml or "Annual" in jrxml, "标题修改应该体现在 JRXML 中"
assert final3.get("status") in ("pass", "fail")
def test_scenario4_context_aware_modification(self, graph):
"""场景 4:基于对话上下文的修改。"""
# 第 1 轮:生成按客户分组的报表
state = create_initial_state()
state["user_input"] = (
"Create a customer orders report grouped by customer_name with order_id, "
"order_total fields. Include a subtotal for each customer group. "
"Query from customer_orders."
)
state["stage"] = "initial_generation"
final = run_graph(graph, state)
assert final.get("current_jrxml"), "第 1 轮应该已生成 JRXML"
# 第 2 轮:上下文感知修改
state2 = final.copy()
state2["user_input"] = "Make the subtotal row font larger and bold."
state2["user_modification_request"] = "Make the subtotal row font larger and bold."
state2["stage"] = "modification"
state2["retry_count"] = 0
final2 = run_graph(graph, state2)
jrxml = final2.get("current_jrxml", "")
assert "isBold" in jrxml or "size=" in jrxml, "字体修改应该体现在结果中"
assert final2.get("status") in ("pass", "fail")
def test_max_retry_handling(self, graph):
"""测试在 MAX_RETRY 次失败后,图能否正常终止。
process_input 会重置 retry_count 为 0,因此不依赖初始值。
实际验证:图在多次修正后终止(不挂死),renry_count 至少为 1。
MAX_RETRY 配置为 5(环境变量),图在达到上限后路由到 finalize。
"""
state = create_initial_state()
state["current_jrxml"] = "<invalid>xml<<<"
state["user_input"] = "Fix this"
state["status"] = "fail"
final = run_graph(graph, state)
# 图应正常终止:status=passLLM修复成功)或 retry_count>=1(至少尝试了修正)
assert final.get("retry_count", 0) >= 1 or final.get("status") == "pass", \
f"图应在至少1次修正后终止,实际 retry_count={final.get('retry_count')} status={final.get('status')}"