Files
DjangoAdmin_Flask_EleVue/tests/unit/test_modules/test_level_service.py
T
2026-09-12 18:57:19 +08:00

214 lines
8.6 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.
"""
职级服务单元测试(Excel 导入/导出复用 excel_utils)
=============================================================
验证:
- export_levels 支持与分页一致的筛选参数(name 模糊、status 精确)
- 无筛选时导出全部记录;表头与数据列对齐
- import_levels 上传/解析/入库/清理(临时文件生命周期)
- 上传失败与文件缺失时透传错误
文件落盘通过 monkeypatch excel_utils.save_export_file 跳过,
仅验证查询、列构建与写盘调用契约。
"""
import pytest
from src.common.utils import excel_utils
from src.core.exceptions import BizError
from src.modules.system.level import service as level_service
from src.modules.system.level.repository import LevelRepository
@pytest.fixture
def seed_levels(app):
"""插入职级测试数据(sqlite 内存库)"""
from src.core.database import db
with app.app_context():
LevelRepository.create(name="初级工程师", status=1, sort=1)
LevelRepository.create(name="中级工程师", status=1, sort=2)
LevelRepository.create(name="高级工程师", status=2, sort=3)
LevelRepository.create(name="产品经理", status=1, sort=4)
db.session.commit()
def _run_export(app, monkeypatch, params):
"""调用导出并捕获表头/行数据(跳过文件落盘,仅验证查询与列构建)"""
captured = {}
def fake_save(headers, rows, filename_prefix="export"):
captured["headers"] = headers
captured["rows"] = rows
return "/static/temp/level_xxx.xlsx"
monkeypatch.setattr(excel_utils, "save_export_file", fake_save)
with app.app_context():
result = level_service.export_levels(params)
url = result.get_json()["data"]
return captured, url
class TestExportLevels:
"""Excel 导出:筛选参数 + 列对齐"""
HEADER = ["职级ID", "职级名称", "职级排序", "职级状态", "创建时间", "更新时间"]
def test_export_no_filter_exports_all(self, app, monkeypatch, seed_levels):
captured, url = _run_export(app, monkeypatch, None)
assert captured["headers"] == self.HEADER
assert [r[1] for r in captured["rows"]] == ["初级工程师", "中级工程师", "高级工程师", "产品经理"]
assert url.startswith("/static")
def test_export_filters_by_name(self, app, monkeypatch, seed_levels):
captured, _ = _run_export(app, monkeypatch, {"name": "工程师"})
assert captured["headers"] == self.HEADER
assert [r[1] for r in captured["rows"]] == ["初级工程师", "中级工程师", "高级工程师"]
def test_export_filters_by_status(self, app, monkeypatch, seed_levels):
captured, _ = _run_export(app, monkeypatch, {"status": 2})
assert [r[1] for r in captured["rows"]] == ["高级工程师"]
def test_export_filters_by_name_and_status(self, app, monkeypatch, seed_levels):
captured, _ = _run_export(app, monkeypatch, {"name": "工程师", "status": 1})
assert [r[1] for r in captured["rows"]] == ["初级工程师", "中级工程师"]
def test_export_columns_aligned(self, app, monkeypatch, seed_levels):
captured, _ = _run_export(app, monkeypatch, {"name": "产品经理"})
assert captured["headers"] == self.HEADER
row = captured["rows"][0]
assert row[0] is not None # 职级ID
assert row[1] == "产品经理" # 职级名称
assert row[2] == 4 # 职级排序
assert row[3] == "正常" # 职级状态(formatter 展示为中文)
assert row[4] # 创建时间
assert row[5] # 更新时间
def test_export_empty_result_keeps_header(self, app, monkeypatch, seed_levels):
captured, _ = _run_export(app, monkeypatch, {"name": "不存在的职级"})
assert captured["headers"] == self.HEADER
assert captured["rows"] == []
class TestLevelList:
"""职级列表(无分页):参数条件筛选 + status=1 硬性过滤"""
def test_list_no_filter_returns_enabled_only(self, app, seed_levels):
with app.app_context():
data = level_service.get_level_list(None)
assert [d["name"] for d in data] == ["初级工程师", "中级工程师", "产品经理"]
def test_list_filters_by_name(self, app, seed_levels):
with app.app_context():
data = level_service.get_level_list({"name": "工程师"})
assert [d["name"] for d in data] == ["初级工程师", "中级工程师"]
def test_list_status_param_is_ignored(self, app, seed_levels):
# status 为硬性条件,即使传入 status=2 也只返回启用(status=1)记录
with app.app_context():
data = level_service.get_level_list({"name": "工程师", "status": 2})
assert [d["name"] for d in data] == ["初级工程师", "中级工程师"]
class TestImportLevels:
"""职级Excel导入(复用 excel_utils 上传/解析/清理机制)"""
def _patch_upload(self, monkeypatch, tmp_path, file_name="levels.xlsx"):
"""将上传模拟指向 tmp_path/temp/20260817 目录,返回该文件路径"""
target_dir = tmp_path / "temp" / "20260817"
target_dir.mkdir(parents=True, exist_ok=True)
target = target_dir / file_name
monkeypatch.setattr(
excel_utils.upload, "upload_file",
lambda *a, **k: {"filePath": "/temp/20260817/" + file_name},
)
monkeypatch.setattr(excel_utils, "FLASK_UPLOAD_DIR", str(tmp_path))
return target
def test_import_success(self, app, monkeypatch, tmp_path):
import shutil
from openpyxl.workbook import Workbook
from src.modules.system.level.models import Level
# 构造真实 xlsx 并放入模拟的上传目录(temp/日期/文件)
src = tmp_path / "levels.xlsx"
wb = Workbook()
ws = wb.worksheets[0]
ws.append(["职级ID", "职级名称", "职级排序", "职级状态"])
ws.append([None, "初级", 1, "正常"])
ws.append([None, "停用级", 2, "禁用"])
ws.append([None, None, None, None]) # 整行为空 → 跳过
wb.save(src)
target = self._patch_upload(monkeypatch, tmp_path)
shutil.copy(src, target)
with app.app_context():
result = level_service.import_levels()
rows = Level.query.order_by(Level.sort).all()
assert result.get_json()["code"] == 0
assert [r.name for r in rows] == ["初级", "停用级"]
assert rows[0].status == 1 # "正常" → 1
assert rows[1].status == 2 # "禁用" → 2
assert not target.exists() # 临时导入文件已清理
def test_import_required_missing_fails_whole_import(self, app, monkeypatch, tmp_path):
import shutil
from openpyxl.workbook import Workbook
from src.modules.system.level.models import Level
# 名称必填缺失 → 行级错误 → 整体失败,无任何数据入库
src = tmp_path / "levels.xlsx"
wb = Workbook()
ws = wb.worksheets[0]
ws.append(["职级ID", "职级名称", "职级排序", "职级状态"])
ws.append([None, "", 1, "正常"])
wb.save(src)
target = self._patch_upload(monkeypatch, tmp_path)
shutil.copy(src, target)
with app.app_context():
result = level_service.import_levels()
count = Level.query.count()
body = result.get_json()
assert body["code"] == 1
assert "职级名称不能为空" in body["msg"]
assert count == 0
def test_import_upload_failure_propagates(self, app, monkeypatch):
# 上传失败由 upload_file 抛 BizError,import_levels 捕获后透传具体错误信息
def _fail_upload(*args, **kwargs):
raise BizError("文件格式不支持,仅支持 .xls, .xlsx 格式")
monkeypatch.setattr(excel_utils.upload, "upload_file", _fail_upload)
with app.app_context():
result = level_service.import_levels()
body = result.get_json()
assert body["code"] == 1
assert "文件格式不支持" in body["msg"]
def test_import_file_missing_returns_failed(self, app, monkeypatch):
monkeypatch.setattr(
excel_utils.upload, "upload_file",
lambda *a, **k: {"filePath": "/temp/20260817/nonexist.xlsx"},
)
monkeypatch.setattr(excel_utils, "FLASK_UPLOAD_DIR", "nonexistent_dir")
with app.app_context():
result = level_service.import_levels()
body = result.get_json()
assert body["code"] == 1
assert "导入文件不存在" in body["msg"]