签入版本

This commit is contained in:
2026-09-12 18:57:19 +08:00
parent d45b61d808
commit 2857e0f2da
295 changed files with 20713 additions and 13125 deletions
+145
View File
@@ -0,0 +1,145 @@
"""
共享 Excel 工具单元测试(excel_utils)
=============================================================
验证通用 Excel 导入/导出机制(供职级/用户等模块复用):
- parse_excel_rows:按列定义读取,必填/解析错误逐行收集,空行跳过
- parse_int / parse_status / format_status:单元格解析与状态展示格式化
- build_export_rows:按列 key 取值并应用 formatter
- save_export_file:写工作簿、生成访问 URL(路径通过 monkeypatch 指向临时目录)
不依赖具体业务模块,列定义在用例内自构造。
"""
import pytest
from openpyxl.reader.excel import load_workbook
from openpyxl.workbook import Workbook
from src.common.utils import excel_utils
# 用例自构造的列定义(结构与 level/user 模块一致)
COLUMNS = [
{"title": "名称", "index": 1, "key": "name", "required": True},
{"title": "排序", "index": 2, "key": "sort", "parser": excel_utils.parse_int},
{"title": "状态", "index": 3, "key": "status", "parser": excel_utils.parse_status},
]
class TestParseStatus:
"""状态单元格解析:中文 / 数字 -> 1/2,非法值报错"""
def test_normal_and_disabled(self):
assert excel_utils.parse_status("正常") == 1
assert excel_utils.parse_status("禁用") == 2
assert excel_utils.parse_status("1") == 1
assert excel_utils.parse_status(2) == 2
def test_invalid_raises(self):
with pytest.raises(ValueError):
excel_utils.parse_status("未知")
class TestParseInt:
"""整数单元格解析"""
def test_valid(self):
assert excel_utils.parse_int("10") == 10
assert excel_utils.parse_int(3) == 3
def test_invalid_raises(self):
with pytest.raises(ValueError):
excel_utils.parse_int("abc")
class TestFormatStatus:
"""状态展示格式化:1/2 -> 正常/禁用,未知值原样返回"""
def test_mapping(self):
assert excel_utils.format_status(1) == "正常"
assert excel_utils.format_status(2) == "禁用"
def test_unknown_passthrough(self):
assert excel_utils.format_status(0) == 0
class TestBuildExportRows:
"""导出行构建:按列 key 取值 + formatter 展示格式化"""
def test_formatter_applied_and_key_lookup(self):
records = [
{"name": "初级", "status": 1, "sort": 2},
{"name": "禁用级", "status": 2, "sort": 1},
]
columns = [
{"title": "名称", "key": "name"},
{"title": "排序", "key": "sort"},
{"title": "状态", "key": "status", "formatter": excel_utils.format_status},
]
assert excel_utils.build_export_rows(records, columns) == [
["初级", 2, "正常"],
["禁用级", 1, "禁用"],
]
def test_missing_key_becomes_none(self):
columns = [{"title": "缺失键", "key": "nope"}]
assert excel_utils.build_export_rows([{"name": "x"}], columns) == [[None]]
class TestParseExcelRows:
"""导入行解析:必填校验 / parser 错误 / 空行跳过 / 可选空单元格"""
def _make_wb(self, path):
wb = Workbook()
ws = wb.worksheets[0]
ws.append(["名称", "排序", "状态"]) # 表头(start_row=2 跳过)
ws.append(["初级", 1, "正常"]) # 合法
ws.append(["坏排序", "abc", "正常"]) # 排序解析失败
ws.append(["缺状态", 2, ""]) # 可选空单元格 -> None
ws.append([None, None, None]) # 整行为空 -> 跳过
ws.append(["", 3, "禁用"]) # 必填缺失
wb.save(path)
def test_rows_and_errors(self, tmp_path):
path = tmp_path / "in.xlsx"
self._make_wb(path)
rows, errors = excel_utils.parse_excel_rows(str(path), COLUMNS)
# 合法行 + 可选空单元格行保留;错误行 / 空行被剔除
assert rows == [
{"name": "初级", "sort": 1, "status": 1},
{"name": "缺状态", "sort": 2, "status": None},
]
# 行级错误带行号与原因
assert any("第3行 排序:" in e for e in errors)
assert any("第6行 名称不能为空" in e for e in errors)
class TestSaveExportFile:
"""导出文件落盘与 URL 生成"""
def test_writes_file_and_returns_url(self, tmp_path, monkeypatch):
monkeypatch.setattr(excel_utils, "FLASK_TEMP_PATH", str(tmp_path))
monkeypatch.setattr(excel_utils, "get_file_url", lambda url: "/static" + url)
headers = ["名称", "状态"]
rows = [["初级", "正常"], ["高级", "禁用"]]
url = excel_utils.save_export_file(headers, rows, filename_prefix="level")
# URL 由 get_file_url 拼接(返回约定前缀 + 路径)
assert url.startswith("/static")
# 文件真实落盘到临时目录,表头与数据一致
import glob
saved = glob.glob(str(tmp_path) + "/**/*.xlsx", recursive=True)
assert len(saved) == 1
wb = load_workbook(saved[0])
ws = wb.worksheets[0]
assert list(ws.iter_rows(values_only=True)) == [
("名称", "状态"),
("初级", "正常"),
("高级", "禁用"),
]
wb.close()