finish app develop
This commit is contained in:
71
tests/test_analyzer.py
Normal file
71
tests/test_analyzer.py
Normal file
@@ -0,0 +1,71 @@
|
||||
from app.analyzer import LLMClient, build_analysis_prompt, select_relevant_skills
|
||||
from app.config import ProviderConfig
|
||||
from app.docx_parser import ParsedDocument
|
||||
from app.skill_loader import Skill
|
||||
|
||||
|
||||
class DummyResponse:
|
||||
def raise_for_status(self) -> None:
|
||||
return None
|
||||
|
||||
def json(self) -> dict:
|
||||
return {"choices": [{"message": {"content": "分析结果"}}]}
|
||||
|
||||
|
||||
class DummySession:
|
||||
def __init__(self) -> None:
|
||||
self.calls = []
|
||||
|
||||
def post(self, url: str, **kwargs):
|
||||
self.calls.append((url, kwargs))
|
||||
return DummyResponse()
|
||||
|
||||
|
||||
def test_select_relevant_skills_prefers_matching_text() -> None:
|
||||
parsed = ParsedDocument(
|
||||
filename="srs.docx",
|
||||
text="软件需求规格说明 CSCI 能力需求 接口需求 合格性规定",
|
||||
paragraphs=[],
|
||||
headings=[],
|
||||
tables=[],
|
||||
)
|
||||
skills = [
|
||||
Skill("requirements", "软件需求规格说明", "需求文档", "编写软件需求", "需求能力接口", None),
|
||||
Skill("deployment", "部署准备", "部署文档", "软件部署交付", "安装部署", None),
|
||||
]
|
||||
|
||||
selected = select_relevant_skills(parsed, skills, max_skills=1)
|
||||
|
||||
assert [skill.slug for skill in selected] == ["requirements"]
|
||||
|
||||
|
||||
def test_llm_client_posts_openai_compatible_payload() -> None:
|
||||
provider = ProviderConfig(
|
||||
api_key="secret",
|
||||
base_url="https://api.deepseek.com/v1/chat/completions",
|
||||
max_tokens=99,
|
||||
model="deepseek-chat",
|
||||
temperature=0.1,
|
||||
)
|
||||
session = DummySession()
|
||||
client = LLMClient(provider, session=session)
|
||||
|
||||
content = client.complete("检查文档")
|
||||
|
||||
assert content == "分析结果"
|
||||
url, kwargs = session.calls[0]
|
||||
assert url == "https://api.deepseek.com/v1/chat/completions"
|
||||
assert kwargs["headers"]["Authorization"] == "Bearer secret"
|
||||
assert kwargs["json"]["model"] == "deepseek-chat"
|
||||
assert kwargs["json"]["messages"][0]["content"] == "检查文档"
|
||||
|
||||
|
||||
def test_build_analysis_prompt_contains_document_and_skill() -> None:
|
||||
parsed = ParsedDocument("a.docx", "正文", [], [], [])
|
||||
skill = Skill("s1", "技能一", "描述", "使用条件", "规范内容", None)
|
||||
|
||||
prompt = build_analysis_prompt(parsed, [skill])
|
||||
|
||||
assert "a.docx" in prompt
|
||||
assert "技能一" in prompt
|
||||
assert "符合项" in prompt
|
||||
47
tests/test_config.py
Normal file
47
tests/test_config.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from pathlib import Path
|
||||
|
||||
from app.config import load_api_config
|
||||
|
||||
|
||||
def test_load_api_config_selects_default_provider(tmp_path: Path) -> None:
|
||||
config_path = tmp_path / "api_config.yaml"
|
||||
config_path.write_text(
|
||||
"""
|
||||
default_provider: intranet
|
||||
providers:
|
||||
intranet:
|
||||
api_key: EMPTY
|
||||
base_url: http://model.local/v1
|
||||
max_tokens: 1024
|
||||
model: qwen3-coder
|
||||
temperature: 0.2
|
||||
""".strip(),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
settings = load_api_config(config_path)
|
||||
|
||||
assert settings.provider_name == "intranet"
|
||||
assert settings.provider.model == "qwen3-coder"
|
||||
assert settings.provider.chat_completions_url == "http://model.local/v1/chat/completions"
|
||||
|
||||
|
||||
def test_load_api_config_accepts_full_chat_completions_url(tmp_path: Path) -> None:
|
||||
config_path = tmp_path / "api_config.yaml"
|
||||
config_path.write_text(
|
||||
"""
|
||||
default_provider: deepseek
|
||||
providers:
|
||||
deepseek:
|
||||
api_key: key
|
||||
base_url: https://api.deepseek.com/v1/chat/completions
|
||||
max_tokens: 4000
|
||||
model: deepseek-chat
|
||||
temperature: 0.7
|
||||
""".strip(),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
settings = load_api_config(config_path, provider_name="deepseek")
|
||||
|
||||
assert settings.provider.chat_completions_url == "https://api.deepseek.com/v1/chat/completions"
|
||||
24
tests/test_docx_parser.py
Normal file
24
tests/test_docx_parser.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from pathlib import Path
|
||||
|
||||
from docx import Document
|
||||
|
||||
from app.docx_parser import parse_docx
|
||||
|
||||
|
||||
def test_parse_docx_extracts_headings_paragraphs_and_tables(tmp_path: Path) -> None:
|
||||
docx_path = tmp_path / "sample.docx"
|
||||
document = Document()
|
||||
document.add_heading("软件需求规格说明", level=1)
|
||||
document.add_paragraph("本文档描述 CSCI 的能力需求和接口需求。")
|
||||
table = document.add_table(rows=1, cols=2)
|
||||
table.cell(0, 0).text = "需求编号"
|
||||
table.cell(0, 1).text = "REQ-001"
|
||||
document.save(docx_path)
|
||||
|
||||
parsed = parse_docx(docx_path)
|
||||
|
||||
assert parsed.filename == "sample.docx"
|
||||
assert "软件需求规格说明" in parsed.text
|
||||
assert "REQ-001" in parsed.text
|
||||
assert parsed.headings[0].text == "软件需求规格说明"
|
||||
assert parsed.tables[0][0] == ["需求编号", "REQ-001"]
|
||||
29
tests/test_report_generator.py
Normal file
29
tests/test_report_generator.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from pathlib import Path
|
||||
|
||||
from docx import Document
|
||||
|
||||
from app.report_generator import AnalysisReport, generate_docx_report, generate_markdown_report
|
||||
|
||||
|
||||
def test_generate_reports_create_markdown_and_docx(tmp_path: Path) -> None:
|
||||
report = AnalysisReport(
|
||||
source_filename="sample.docx",
|
||||
provider_name="deepseek",
|
||||
model_name="deepseek-chat",
|
||||
matched_skills=["gjb438c-software-requirements-spec-structure"],
|
||||
summary="部分通过",
|
||||
findings=[{"status": "需整改", "item": "缺少追溯性矩阵", "evidence": "未发现相关章节"}],
|
||||
recommendations=["补充需求双向追溯矩阵。"],
|
||||
raw_model_output="模型输出",
|
||||
)
|
||||
|
||||
md_path = generate_markdown_report(report, tmp_path)
|
||||
docx_path = generate_docx_report(report, tmp_path)
|
||||
|
||||
assert md_path.exists()
|
||||
assert "缺少追溯性矩阵" in md_path.read_text(encoding="utf-8")
|
||||
assert docx_path.exists()
|
||||
parsed = Document(docx_path)
|
||||
text = "\n".join(p.text for p in parsed.paragraphs)
|
||||
assert "DOCX 规范分析报告" in text
|
||||
assert "部分通过" in text
|
||||
14
tests/test_skill_loader.py
Normal file
14
tests/test_skill_loader.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from pathlib import Path
|
||||
|
||||
from app.skill_loader import load_skill_catalog
|
||||
|
||||
|
||||
def test_load_skill_catalog_reads_index_and_skill_files() -> None:
|
||||
skills = load_skill_catalog(Path("GJB438C-2021_prd_skills"))
|
||||
|
||||
assert len(skills) >= 30
|
||||
skill_names = {skill.slug for skill in skills}
|
||||
assert "gjb438c-software-requirements-spec-structure" in skill_names
|
||||
target = next(skill for skill in skills if skill.slug == "gjb438c-software-requirements-spec-structure")
|
||||
assert "软件需求规格说明" in target.content
|
||||
assert target.path.name == "SKILL.md"
|
||||
55
tests/test_web.py
Normal file
55
tests/test_web.py
Normal file
@@ -0,0 +1,55 @@
|
||||
from pathlib import Path
|
||||
|
||||
from docx import Document
|
||||
|
||||
from app.main import OUTPUT_DIR, ROOT_DIR, analyze_saved_docx
|
||||
|
||||
|
||||
def test_index_template_contains_upload_ui() -> None:
|
||||
html = (ROOT_DIR / "app" / "templates" / "index.html").read_text(encoding="utf-8")
|
||||
js = (ROOT_DIR / "app" / "static" / "app.js").read_text(encoding="utf-8")
|
||||
|
||||
assert "DOCX 规范分析" in html
|
||||
assert 'type="file"' in html
|
||||
assert "analysis-progress" in html
|
||||
assert "analysis-status" in html
|
||||
assert "下载 Markdown 报告" in html
|
||||
assert "<!-- <a id=\"download-docx\"" in html
|
||||
assert "download-md" in js
|
||||
assert "pollTask" in js
|
||||
|
||||
|
||||
def test_analyze_saved_docx_reports_progress(tmp_path: Path) -> None:
|
||||
updates: list[tuple[int, str]] = []
|
||||
docx_path = tmp_path / "progress.docx"
|
||||
document = Document()
|
||||
document.add_heading("软件需求规格说明", level=1)
|
||||
document.add_paragraph("能力需求、接口需求、合格性规定。")
|
||||
document.save(docx_path)
|
||||
|
||||
payload = analyze_saved_docx(
|
||||
docx_path,
|
||||
provider="deepseek",
|
||||
use_model=False,
|
||||
progress_callback=lambda progress, message: updates.append((progress, message)),
|
||||
)
|
||||
|
||||
assert updates[0][0] == 5
|
||||
assert updates[-1] == (100, "分析完成")
|
||||
assert any("技能" in message for _, message in updates)
|
||||
assert payload["downloads"]["markdown"].endswith(".md")
|
||||
|
||||
|
||||
def test_analyze_saved_docx_creates_downloadable_report(tmp_path: Path) -> None:
|
||||
docx_path = tmp_path / "upload.docx"
|
||||
document = Document()
|
||||
document.add_heading("软件需求规格说明", level=1)
|
||||
document.add_paragraph("能力需求、接口需求、合格性规定。")
|
||||
document.save(docx_path)
|
||||
|
||||
payload = analyze_saved_docx(docx_path, provider="deepseek", use_model=False)
|
||||
|
||||
assert payload["source_filename"] == "upload.docx"
|
||||
assert "docx" not in payload["downloads"]
|
||||
assert payload["downloads"]["markdown"].endswith(".md")
|
||||
assert (OUTPUT_DIR / Path(payload["downloads"]["markdown"]).name).exists()
|
||||
Reference in New Issue
Block a user