test: 覆盖率提升至 100%(fail_under 90→99,13 个防御/边界用例)
This commit is contained in:
+11
-1
@@ -48,4 +48,14 @@ def test_env_placeholder_expansion(monkeypatch):
|
||||
def test_redacted_hides_secrets():
|
||||
s = Settings.from_dir(FIXTURES)
|
||||
red = s.get_redacted()
|
||||
assert red["rag"]["vector_store"]["qdrant"]["api_key"] == "***"
|
||||
assert red["rag"]["vector_store"]["qdrant"]["api_key"] == "***"
|
||||
|
||||
|
||||
def test_expand_env_list_branch(monkeypatch):
|
||||
# _expand_env 的 list 分支(L134):列表项递归展开环境占位
|
||||
from genesis.config import _expand_env
|
||||
monkeypatch.setenv("QDRANT_API_KEY", "sk-list-xyz")
|
||||
data = {"models": [{"name": "a", "key": "${QDRANT_API_KEY}"}, "plain"]}
|
||||
out = _expand_env(data)
|
||||
assert out["models"][0]["key"] == "sk-list-xyz"
|
||||
assert out["models"][1] == "plain"
|
||||
@@ -4,6 +4,43 @@ from genesis.parsers.excel_parser import ExcelParseResult, ExcelParser
|
||||
from tests.excel_helpers import new_workbook, save_workbook
|
||||
|
||||
|
||||
def test_parse_skips_empty_sheet(tmp_path):
|
||||
# 空 sheet → skipped 不进入(L skips 分支)
|
||||
wb = new_workbook({"空白": [[]]})
|
||||
path = save_workbook(tmp_path, wb)
|
||||
result = ExcelParser().parse(path)
|
||||
assert "空白" in result.skipped
|
||||
assert result.tables == []
|
||||
|
||||
|
||||
def test_parse_mixed_formatting_outside_table_segment(tmp_path):
|
||||
# 碎片段(free_text 段)存在取消线格式 → seg_fmt_map 构建时该行不在表格段 [s,e] 内,被跳过(66-65 分支)
|
||||
from copy import copy
|
||||
from openpyxl import Workbook
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = "混合"
|
||||
data = [
|
||||
["機能ID", "機能名"], ["F101", "社員登録"],
|
||||
[],
|
||||
["・改述ポイント"],
|
||||
]
|
||||
for r, row in enumerate(data, start=1):
|
||||
for c, v in enumerate(row, start=1):
|
||||
if v:
|
||||
ws.cell(row=r, column=c, value=v)
|
||||
font = copy(ws.cell(row=4, column=1).font) # 碎片段行(物理行 4)
|
||||
font.strike = True
|
||||
ws.cell(row=4, column=1).font = font
|
||||
path = save_workbook(tmp_path, wb)
|
||||
result = ExcelParser().parse(path)
|
||||
ms = result.mixed[0]
|
||||
tbl = [p.table for p in ms.paragraphs if p.kind == "table"][0]
|
||||
# 表格段数据行不应带上碎片段的取消线格式
|
||||
assert tbl.rows[0]["機能ID"].formatting is None
|
||||
assert len(tbl.rows) == 1
|
||||
|
||||
|
||||
def test_parse_table_sheets(tmp_path):
|
||||
wb = new_workbook({
|
||||
"機能一覧": [["機能ID", "機能名"], ["A001", "社員登録"]],
|
||||
|
||||
@@ -59,4 +59,58 @@ def test_cell_comment_returns_obj():
|
||||
|
||||
def test_collect_comments():
|
||||
comments = collect_comments(make_wb().active, "f.xlsx")
|
||||
assert len(comments) == 1
|
||||
assert len(comments) == 1
|
||||
|
||||
|
||||
def test_to_rgb_hex_accepts_6_digit_hex():
|
||||
from types import SimpleNamespace
|
||||
from genesis.parsers.formatting_detector import _to_rgb_hex
|
||||
assert _to_rgb_hex(SimpleNamespace(rgb="FF3333")) == "FF3333"
|
||||
assert _to_rgb_hex(SimpleNamespace(rgb="00FF00FF")) == "00FF00FF"
|
||||
|
||||
|
||||
def test_to_rgb_hex_rejects_black_and_theme():
|
||||
from types import SimpleNamespace
|
||||
from genesis.parsers.formatting_detector import _to_rgb_hex
|
||||
# 纯黑(默认色)与主题色(rgb 为空/异常/非 hex)→ None
|
||||
assert _to_rgb_hex(SimpleNamespace(rgb="00000000")) is None
|
||||
assert _to_rgb_hex(SimpleNamespace(rgb="FF000000")) is None
|
||||
assert _to_rgb_hex(SimpleNamespace(rgb="")) is None
|
||||
assert _to_rgb_hex(SimpleNamespace(rgb="ZZZZZZ")) is None
|
||||
assert _to_rgb_hex(SimpleNamespace(rgb="FFFFF")) is None # 长度不足
|
||||
|
||||
|
||||
class _ExplodingRgb:
|
||||
@property
|
||||
def rgb(self):
|
||||
raise Exception("boom")
|
||||
|
||||
|
||||
def test_to_rgb_hex_rgb_access_exception_returns_none():
|
||||
from genesis.parsers.formatting_detector import _to_rgb_hex
|
||||
# L15-16:color.rgb 访问抛异常 → 返回 None(防御分支)
|
||||
assert _to_rgb_hex(_ExplodingRgb()) is None
|
||||
|
||||
|
||||
def test_cell_formatting_fills_only_color():
|
||||
from copy import copy
|
||||
from openpyxl.styles import PatternFill
|
||||
from genesis.parsers.formatting_detector import cell_formatting
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws["A1"] = "x"
|
||||
# 仅背景色(无 strikethrough / 无字体色)→ 返回 CellFormatting 含 bg_color
|
||||
f = copy(ws["A1"].fill)
|
||||
f.fgColor = PatternFill(start_color="00CC00").fgColor
|
||||
ws["A1"].fill = f
|
||||
fmt = cell_formatting(ws["A1"])
|
||||
assert fmt is not None
|
||||
assert fmt.bg_color is not None
|
||||
|
||||
|
||||
def test_cell_formatting_fill_none_branch():
|
||||
# 覆盖 L31 防御分支:fill 为 None(无样式对象)时直接返回 None
|
||||
from types import SimpleNamespace
|
||||
from genesis.parsers.formatting_detector import cell_formatting
|
||||
fake = SimpleNamespace(font=SimpleNamespace(strike=False), fill=None)
|
||||
assert cell_formatting(fake) is None
|
||||
@@ -29,4 +29,21 @@ def test_classify_sheet_mixed_with_bullet_line():
|
||||
|
||||
def test_classify_sheet_mixed_with_square_line():
|
||||
m = [["ID", "名前"], ["1", "田中"], ["■備考行"]]
|
||||
assert classify_sheet(m) == SheetNature.MIXED
|
||||
assert classify_sheet(m) == SheetNature.MIXED
|
||||
|
||||
|
||||
def test_classify_empty_matrix_is_free_text():
|
||||
# 空矩阵:_free_text_like 对空矩阵返回 True(L26 分支)
|
||||
assert classify_sheet([]) == SheetNature.FREE_TEXT
|
||||
|
||||
|
||||
def test_classify_sparse_multicol_is_free_text():
|
||||
# 多列但非空行占比 < 0.7 → FREE_TEXT(L32 稀疏分支)
|
||||
m = [["ID", "名前"], [], [], [], [], ["1", "田中"]]
|
||||
assert classify_sheet(m) == SheetNature.FREE_TEXT
|
||||
|
||||
|
||||
def test_classify_header_no_bullet_is_table():
|
||||
# 表头有效且无 ・/■ 行 → 落入 TABLE(L40→44 无匹配循环路径)
|
||||
m = [["ID", "名前"], ["1", "田中"], ["2", "佐藤"]]
|
||||
assert classify_sheet(m) == SheetNature.TABLE
|
||||
@@ -49,4 +49,20 @@ def test_extract_table_uses_enum_value():
|
||||
def test_forward_fill_ignores_invalid_range():
|
||||
matrix = [["A"], ["B"]]
|
||||
assert forward_fill(matrix, [(0, 1, 2, 1)]) == matrix # min_row=0 非法 → 不崩溃不改数据
|
||||
assert forward_fill(matrix, [(9, 9, 9, 9)]) == matrix # 越界 → 不崩溃
|
||||
assert forward_fill(matrix, [(9, 9, 9, 9)]) == matrix # 越界 → 不崩溃
|
||||
|
||||
|
||||
def test_extract_table_empty_matrix():
|
||||
# 空矩阵 → 返回空表(headers/rows 均为空),不崩溃(L 空矩阵分支)
|
||||
table = extract_table("空シート", [], "f.xlsx", SheetType.FUNCTION)
|
||||
assert table.headers == []
|
||||
assert table.rows == []
|
||||
|
||||
|
||||
def test_extract_table_header_row_out_of_range():
|
||||
# header_row 越界/负值 → 回落 0(防御分支)
|
||||
mat = [["ID", "名前"], ["1", "田中"]]
|
||||
t_high = extract_table("社員", mat, "f.xlsx", SheetType.FUNCTION, header_row=9)
|
||||
t_neg = extract_table("社員", mat, "f.xlsx", SheetType.FUNCTION, header_row=-1)
|
||||
assert t_high.headers == ["ID", "名前"]
|
||||
assert t_neg.headers == ["ID", "名前"]
|
||||
Reference in New Issue
Block a user