Files
2026Technology-Competition/tests/test_resolver.py
T
lhl 69aec7716c feat(provenance): URI 统一 + resolver + 强验证(T12 架构审查整改)
- T12 (OV3, P1): 新建 src/genesis/parsers/resolver.py
  - parse_source_uri: 解析 file.xlsx#Sheet!CellRef → SourceRef(格式非法 raise URIError)
  - provenance_to_uri: Provenance 还原 URI(与 build_source_uri 互逆)
  - resolve_source_uri: StructuredSource 内定位真实 CellValue
  - validate_source_uris: 批量强验证 → ValidationResult(resolved/unresolved)
    格式错误或源中不存在一律 unresolved(防 QA#8 编造 URI 作弊)
- URI 唯一生成入口 build_source_uri(provenance.py),无散落不一致
- 新增 test_resolver.py(13 用例);同步 design.md §9.2 + §6.8 第五步
- TDD: RED(模块缺失)→ GREEN(聚焦 10 passed)→ 全量 231 passed / 100.00%(1191 stmts/298 br)
2026-08-12 22:35:56 +08:00

159 lines
4.9 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.
"""URI resolver + 强验证测试(T12OV3)。
OV3 裁定:可追溯性标准未操作性定义(URI 格式不一致、无 resolver、QA#8 可作弊)
→ 统一 URI + resolver + 强验证。
本文件测试:URI 解析、provenance 往返、结构化源内定位、批量强验证(防 QA 作弊)。
"""
from __future__ import annotations
import pytest
from genesis.data_models import (
CellComment,
CellValue,
ExcelTable,
ParsedTemplate,
Provenance,
RuleDocument,
SheetType,
StructuredSource,
)
from genesis.parsers.provenance import build_source_uri
from genesis.parsers.resolver import (
SourceRef,
URIError,
ValidationResult,
parse_source_uri,
provenance_to_uri,
resolve_source_uri,
validate_source_uris,
)
# ---------- 构造小源 ----------
def _cell(file_name: str, sheet: str, row: int, col: str, value: str) -> CellValue:
return CellValue(
value=value,
provenance=Provenance(
file_name=file_name, sheet_name=sheet, row=row, column=col, column_header="x"
),
)
def _source_with(cells: list[CellValue]) -> StructuredSource:
table = ExcelTable(
name="機能一覧",
detected_type=SheetType.FUNCTION,
extraction_method="structured",
headers=["v"],
rows=[{"v": c} for c in cells],
)
return StructuredSource(
tables=[table],
template=ParsedTemplate(file_name="t.docx", sections=[], placeholders={}, styles={}),
rule_docs=[],
image_analyses=[],
existing_system=None,
comments=[],
)
# ---------- URI 解析 ----------
def test_parse_source_uri():
ref = parse_source_uri("要求.xlsx#機能一覧!C3")
assert ref.file_name == "要求.xlsx"
assert ref.sheet_name == "機能一覧"
assert ref.cell_ref == "C3"
def test_parse_source_uri_rejects_missing_hash():
with pytest.raises(URIError):
parse_source_uri("要求.xlsx機能一覧!C3")
def test_parse_source_uri_rejects_missing_bang():
with pytest.raises(URIError):
parse_source_uri("要求.xlsx#機能一覧")
def test_parse_source_uri_rejects_empty_file():
with pytest.raises(URIError):
parse_source_uri("#機能一覧!C3")
def test_parse_source_uri_rejects_empty_sheet_or_cell():
with pytest.raises(URIError):
parse_source_uri("f.xlsx#!C3")
with pytest.raises(URIError):
parse_source_uri("f.xlsx#Sheet!")
def test_resolve_skips_cell_without_provenance():
"""CellValue.provenance 为 None 时不匹配任何 URI_matches 防御分支)。"""
orphan = CellValue(value="x", provenance=None)
source = _source_with([orphan])
assert resolve_source_uri("f.xlsx#機能一覧!C3", source) is None
# ---------- provenance 往返 ----------
def test_provenance_to_uri_roundtrip():
prov = Provenance(file_name="f.xlsx", sheet_name="S", row=3, column="C", column_header="h")
uri = provenance_to_uri(prov)
assert uri == "f.xlsx#S!C3"
ref = parse_source_uri(uri)
assert ref == SourceRef(file_name="f.xlsx", sheet_name="S", cell_ref="C3")
def test_build_and_parse_roundtrip():
uri = build_source_uri("要求.xlsx", "機能一覧", "A5")
ref = parse_source_uri(uri)
assert ref == SourceRef(file_name="要求.xlsx", sheet_name="機能一覧", cell_ref="A5")
# ---------- 结构化源内定位 ----------
def test_resolve_finds_existing_cell():
cell = _cell("f.xlsx", "機能一覧", 3, "C", "登録")
source = _source_with([cell])
found = resolve_source_uri("f.xlsx#機能一覧!C3", source)
assert found is not None
assert found.value == "登録"
def test_resolve_returns_none_for_missing():
cell = _cell("f.xlsx", "機能一覧", 3, "C", "登録")
source = _source_with([cell])
assert resolve_source_uri("f.xlsx#機能一覧!D9", source) is None
def test_resolve_distinguishes_sheet():
cell = _cell("f.xlsx", "機能一覧", 3, "C", "登録")
source = _source_with([cell])
# 不同 Sheet 的同 cell_ref 不应命中
assert resolve_source_uri("f.xlsx#画面一覧!C3", source) is None
# ---------- 批量强验证(防 QA 作弊) ----------
def test_validate_source_uris_split():
cell = _cell("f.xlsx", "機能一覧", 3, "C", "登録")
source = _source_with([cell])
uris = ["f.xlsx#機能一覧!C3", "f.xlsx#機能一覧!D9", "invalid-uri"]
result = validate_source_uris(uris, source)
assert isinstance(result, ValidationResult)
assert result.resolved == ["f.xlsx#機能一覧!C3"]
assert result.unresolved == ["f.xlsx#機能一覧!D9", "invalid-uri"]
def test_validate_rejects_malformed_separately():
"""格式错误的 URI 应归入 unresolvedQA#8 不可靠 URI 无效,防作弊)。"""
cell = _cell("f.xlsx", "機能一覧", 3, "C", "登録")
source = _source_with([cell])
result = validate_source_uris(["garbage-without-hash"], source)
assert result.resolved == []
assert result.unresolved == ["garbage-without-hash"]