"""web/assets/batch.js 的模块级测试(数据 → 测试过程 → 验证结果)。 被测模块:`web/assets/batch.js`(批量处理页的渲染逻辑:任务进度、操作按钮、 视频明细表 HTML)。 实现方式:在 Node.js 子进程中加载真实 JS 文件并调用真实函数(不重写逻辑), 验证渲染出的 HTML 内容;环境无 node 时跳过(保持跨平台可运行)。 """ from __future__ import annotations import json import shutil import subprocess from pathlib import Path import pytest # 仓库根与被测脚本。 WORKSPACE = Path(__file__).resolve().parents[3] APP_JS = WORKSPACE / "web" / "assets" / "app.js" BATCH_JS = WORKSPACE / "web" / "assets" / "batch.js" # 用真实 JS 引擎执行调用的封装:按页面加载顺序执行 app.js(提供 # escapeHtml/badge 等公共函数)与 batch.js,再调用后者的真实函数。 # 渲染不需要真实 DOM,只提供脚本顶层引用到的 document 桩。 _CALL_SCRIPT = """ const fs = require("fs"); const vm = require("vm"); const sandbox = { module: { exports: {} }, document: { addEventListener: () => {}, getElementById: () => null }, setInterval: () => 0, console, }; vm.createContext(sandbox); for (const path of [process.argv[1], process.argv[2]]) { vm.runInContext(fs.readFileSync(path, "utf8"), sandbox); } const payload = JSON.parse(process.argv[3]); const fn = sandbox.module.exports[payload.fn]; process.stdout.write(JSON.stringify(fn(...payload.args))); """ def _run(fn: str, *args) -> object: """在 node 中调用 batch.js 的真实函数并返回解析后的结果。""" node = shutil.which("node") if node is None: pytest.skip("环境没有 node,跳过 JS 模块测试") completed = subprocess.run( [ node, "-e", _CALL_SCRIPT, str(APP_JS), str(BATCH_JS), json.dumps({"fn": fn, "args": list(args)}), ], capture_output=True, text=True, ) assert completed.returncode == 0, completed.stderr return json.loads(completed.stdout) # --------------------------------------------------------------------------- # videoDetailTable:详情明细表 # --------------------------------------------------------------------------- def _video(name: str, status: str) -> dict: """构造一条明细数据(字段与 GET /api/batch/jobs/{id} 返回的一致)。""" return { "id": f"bv_{name}", "video_path": f"/videos/{name}", "status": status, "error": None, "finals": {}, } def test_detail_table_lists_processing_and_completed_videos() -> None: """正常明细:待处理与已完成的视频都出现在表格行里。""" # 数据:一个待处理、一个已完成。 videos = [_video("a.mp4", "PENDING"), _video("c.mp4", "COMPLETED")] # 测试过程 html = _run("videoDetailTable", "batch_1", videos) # 验证结果 assert "a.mp4" in html assert "c.mp4" in html assert "PENDING" in html and "COMPLETED" in html def test_detail_table_hides_skipped_videos() -> None: """详情列表不展示 SKIPPED(视频旁已有字幕、本次未处理)的视频行。""" # 数据:待处理、跳过、完成各一个。 videos = [ _video("a.mp4", "PENDING"), _video("b.mp4", "SKIPPED"), _video("c.mp4", "COMPLETED"), ] # 测试过程 html = _run("videoDetailTable", "batch_1", videos) # 验证结果:跳过的那行完全不出现。 assert "a.mp4" in html assert "c.mp4" in html assert "b.mp4" not in html assert "SKIPPED" not in html def test_detail_table_with_only_skipped_shows_hint() -> None: """整批视频都已被跳过时给出提示,而不是渲染空表格。""" # 数据:两个 SKIPPED。 videos = [_video("a.mp4", "SKIPPED"), _video("b.mp4", "SKIPPED")] # 测试过程 html = _run("videoDetailTable", "batch_1", videos) # 验证结果:无表格行,只提示无待处理视频。 assert "a.mp4" not in html and "b.mp4" not in html assert "
| 阶段 | " in html assert "阶段 2/4 · 转写" in html def test_detail_table_shows_placeholder_without_stage() -> None: """未开始的视频阶段列显示占位符(不报错)。""" # 数据:一个待处理视频(没有阶段字段)。 video = _video("a.mp4", "PENDING") # 测试过程 html = _run("videoDetailTable", "batch_1", [video]) # 验证结果:阶段列为占位符。 assert "阶段 | " in html assert "阶段 " not in html
|---|