Files
2026Technology-Competition/tests/test_phase5_writer_agent.py
T
lhl 2e11720a2d feat(impact): Impact Agent MVP —— 变更点定位 + 影响调查书(追加改修场景)
- 门控:用户提供 existing_system 路径 → 进入影响调查;未提供 → 原流程不变
- CodeParser 解析 Java(@RestController/@Service/@Entity/@TableName)+ ExistingSystemExplorer 组装
- ImpactAgent 变更点定位(变更区分×既存対応 确定性比对,无 LLM)→ ImpactReport(JSON 可下载)
- 影响调查结果作为 Writer 生成概要设计书的主上下文({{impact}},无专用影响章)
- source_aggregator 解除 existing_system=None 硬编码
- 既有系统样本 sunOnly/stock-trade-system(无 LICENSE,仅测试输入,保留来源标注)
- 新造股票交易域追加改修样本 要件定義_追加改修_股票.xlsx(对齐 sunOnly 真实类名)
- 全量 351 passed / 99.27% 覆盖;门禁 PASS(16 要素:新规5/変更8/削除3/未受影响50)
2026-08-23 17:59:08 +08:00

126 lines
4.0 KiB
Python

import pytest
from types import SimpleNamespace
from genesis.writer.writer_agent import (
WriterAgent,
WRITER_PROMPT_TEMPLATE,
CHAPTER_OUTPUT_SCHEMA,
)
from genesis.writer.models import GenerationContext, ChapterSpec
from genesis.writer.writer_state import WriterState
from genesis.writer.exceptions import WriterGenerationError
class FakeEngine:
def __init__(self):
self.calls = 0
def chat_structured(self, *, session_id, prompt, variables, schema, retry_count=2):
self.calls += 1
return SimpleNamespace(
data={"title": variables["title"], "blocks": [{"type": "paragraph", "text": "ok"}]},
status="ok",
)
class FakePromptRegistry:
@staticmethod
def get_or_create(name, template):
return SimpleNamespace(name=name, version="1", template=template)
def _ctx(cid, title):
return GenerationContext(
chapter_id=cid,
title=title,
template_marker=ChapterSpec(chapter_id=cid, title=title, section_placeholder=None),
structured_source="source text",
write_rules=["W1"],
design_rules=["D1"],
template_styles={"Heading1"},
prior_state=None,
)
def test_generate_chapter_success():
agent = WriterAgent(
session_id="s",
engine=FakeEngine(),
prompt_registry=FakePromptRegistry(),
state=WriterState(["db_design"]),
)
content = agent.generate_chapter(_ctx("db_design", "DB 設計"))
assert content.chapter_id == "db_design"
assert content.blocks and content.blocks[0].type == "paragraph"
def test_prompt_template_enforces_title_language():
# 语言约束:正文需与章节标题语言一致(模板为日文时输出日文)
assert "语言约束" in WRITER_PROMPT_TEMPLATE
assert "{{title}}" in WRITER_PROMPT_TEMPLATE
def test_prompt_template_has_impact_context_var():
# 影响调查结果作为生成主上下文:模板必须包含 impact 变量(无影响书时渲染为空串)
assert "影响调查上下文" in WRITER_PROMPT_TEMPLATE
assert "{{impact}}" in WRITER_PROMPT_TEMPLATE
def test_generate_chapter_retries():
class Boom(FakeEngine):
def chat_structured(self, *, session_id, prompt, variables, schema, retry_count=2):
raise RuntimeError("boom")
agent = WriterAgent(
session_id="s",
engine=Boom(),
prompt_registry=FakePromptRegistry(),
state=WriterState(["db_design"]),
max_retries=2,
)
with pytest.raises(WriterGenerationError):
agent.generate_chapter(_ctx("db_design", "DB 設計"))
class AsyncFakeEngine:
async def chat_structured(self, *, session_id, prompt, variables, schema, retry_count=2):
return SimpleNamespace(
data={"title": variables["title"], "blocks": [{"type": "paragraph", "text": "ok"}]},
status="ok",
)
def test_generate_chapter_with_async_engine():
agent = WriterAgent(
session_id="s",
engine=AsyncFakeEngine(),
prompt_registry=FakePromptRegistry(),
state=WriterState(["db_design"]),
)
content = agent.generate_chapter(_ctx("db_design", "DB 設計"))
assert content.chapter_id == "db_design"
assert content.blocks and content.blocks[0].type == "paragraph"
def test_generate_chapter_propagates_engine_error_detail():
class FailingEngine:
def chat_structured(self, *, session_id, prompt, variables, schema, retry_count=2):
return SimpleNamespace(
data={},
status="failed",
error="LLM HTTP 401: invalid key",
error_code="LLM_NETWORK_ERROR",
)
agent = WriterAgent(
session_id="s",
engine=FailingEngine(),
prompt_registry=FakePromptRegistry(),
state=WriterState(["db_design"]),
max_retries=1,
)
with pytest.raises(WriterGenerationError) as exc:
agent.generate_chapter(_ctx("db_design", "DB 設計"))
assert "LLM HTTP 401" in str(exc.value)
assert "LLM_NETWORK_ERROR" in str(exc.value)