Files
cat-shark 8a715a8064 test: 按模块重写测试代码,删除旧平铺结构
按"测试规则"重写 tests/:一个模块一个目录、用例按数据→过程→验证三段书写、
不保留全局 conftest.py、测试过程只调用真实生产代码。

结构(73 个文件、30 个模块目录、477 用例):
- tests/nodes/  15 个模块目录(srt/whisper/ass/ffmpeg/frame_extract/vlm/
  subtitle_ocr/llm/llm_filter/subtitle_cleanup/subtitle_correction/
  proper_nouns/adaptive_pool/vad_profiler/echo);
- tests/app/    11 个模块目录(db/scheduler/batch/maintenance/registry/seed/
  storage/config/logging/main/routers 三组 API);
- tests/sdk/test_models、tests/web/test_crop、tests/shared(公共设施)。

测试数据随模块目录入库(tests/**/data/),删除根级 testdata/;.gitignore
的 data/ 改为 /data/,否则会连带忽略 tests/**/data/ 导致测试数据无法入库。

顺带发现并修复三个真实缺陷:
- nodes/srt.py:相邻条目缺少空行时把下一条时间轴吞进正文(静默错位),
  改为正文行遇时间戳行即报错;
- src/wov_app/scheduler.py:_file_size 只捕获 OSError,含 \x00 的产物 URI
  抛 ValueError 导致任务误判失败,改为同时捕获;
- nodes/subtitle_correction.py:生产代码依赖测试包解析 SRT,
  改用生产模块 nodes/srt.py。

真实模型/服务集成测试按外部状态跳过:新增 tests/shared/gpu_memory.py
(运行时探测显存、CUDA OOM 转跳过)与 tests/shared/llm_service.py
(无 Key / 余额 / 限流转跳过)。全量 477 passed。
2026-09-13 15:40:56 +08:00

195 lines
7.1 KiB
Python
Raw Permalink 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.
"""src/wov_app/config.py 的模块级测试(数据 → 测试过程 → 验证结果)。
被测模块:`src/wov_app/config.py`(环境变量读取与路径推导),可独立调用。
由于该模块在**导入时**锁定路径常量,隔离用例在子进程中运行(真实导入路径
+ 干净环境),其余用例校验默认值与类型。
注意:config 的路径常量在进程内已固化,因此"环境变量覆盖"必须用子进程验证,
否则测的是缓存值而非真实行为(这也是规则要求"可独立运行、不依赖外部配置"
的具体体现)。
"""
from __future__ import annotations
import json
import subprocess
import sys
from pathlib import Path
import pytest
from wov_app import config
# 仓库根目录(config.WORKSPACE_ROOT 应该指向它)。
WORKSPACE = Path(__file__).resolve().parents[3]
# 与路径相关的环境变量:子进程验证前必须清除,否则会继承外层测试隔离值。
_PATH_ENV_KEYS = (
"WOV_DATA_DIR", "WOV_DB_PATH", "WOV_STORAGE_DIR",
"WOV_SCHEDULER_INTERVAL_SECONDS", "WOV_BATCH_INTERVAL_SECONDS",
"WOV_CLEANUP_INTERVAL_SECONDS", "WOV_CLEANUP_GRACE_SECONDS",
"WOV_BATCH_ENABLED", "WOV_CLEANUP_ENABLED",
)
def _run_in_subprocess(code: str, env: dict[str, str] | None = None) -> dict:
"""在干净子进程中导入 config 并返回指定变量(真实进程隔离)。
先清除全部相关环境变量,再用 env 注入本次用例的值,保证测到的是
config 的真实解析逻辑而不是外层测试运行环境。
"""
script = (
"import json\n"
"from wov_app import config\n"
f"{code}\n"
"print(json.dumps(result))\n"
)
clean_env = {k: v for k, v in __import__("os").environ.items() if k not in _PATH_ENV_KEYS}
clean_env.update(env or {})
result = subprocess.run(
[sys.executable, "-c", script],
capture_output=True,
text=True,
cwd=str(WORKSPACE),
env=clean_env,
)
assert result.returncode == 0, result.stderr
return json.loads(result.stdout.strip().splitlines()[-1])
# ---------------------------------------------------------------------------
# 默认值(当前进程内已导入的常量)
# ---------------------------------------------------------------------------
def test_workspace_root_points_to_repo_root() -> None:
"""WORKSPACE_ROOT 指向仓库根(用于推导其余路径)。"""
# 数据:模块常量。
# 测试过程与验证结果
assert config.WORKSPACE_ROOT == WORKSPACE
assert (config.WORKSPACE_ROOT / "pyproject.toml").is_file()
def test_default_paths_are_under_data_dir() -> None:
"""无环境变量时默认路径为 <仓库根>/data 下的推导值(子进程验证真实默认)。"""
# 数据:清空全部相关环境变量。
# 测试过程
result = _run_in_subprocess(
"result = {'root': str(config.WORKSPACE_ROOT), 'data': str(config.DATA_DIR), "
"'db': str(config.DB_PATH), 'storage': str(config.STORAGE_DIR), "
"'batch': config.BATCH_ENABLED, 'cleanup': config.CLEANUP_ENABLED}"
)
# 验证结果:默认 data 目录在仓库根下,db/storage 由它推导,开关默认开。
assert result["root"] == str(WORKSPACE)
assert result["data"] == str(WORKSPACE / "data")
assert result["db"] == str(WORKSPACE / "data" / "wov.db")
assert result["storage"] == str(WORKSPACE / "data" / "storage")
assert result["batch"] is True
assert result["cleanup"] is True
def test_numeric_settings_are_floats() -> None:
"""数值型配置被解析为 float(避免字符串参与算术)。"""
# 数据:模块常量。
# 测试过程与验证结果
for value in (
config.SCHEDULER_INTERVAL_SECONDS,
config.BATCH_INTERVAL_SECONDS,
config.CLEANUP_INTERVAL_SECONDS,
config.CLEANUP_GRACE_SECONDS,
):
assert isinstance(value, float)
assert value > 0
def test_boolean_settings_are_bool() -> None:
"""布尔型开关被解析为 bool,默认全部开启(1)。"""
# 数据:模块常量(测试运行环境由隔离层设为 0,故仅校验类型)。
# 测试过程与验证结果
for value in (config.BATCH_ENABLED, config.CLEANUP_ENABLED):
assert isinstance(value, bool)
# ---------------------------------------------------------------------------
# 环境变量覆盖(子进程真实导入)
# ---------------------------------------------------------------------------
def test_data_dir_env_override_changes_all_derived_paths(tmp_path: Path) -> None:
"""WOV_DATA_DIR 覆盖后,DB_PATH 与 STORAGE_DIR 随之推导(路径联动)。"""
# 数据:自定义数据目录。
custom = tmp_path / "custom-data"
# 测试过程
result = _run_in_subprocess(
"result = {'data': str(config.DATA_DIR), 'db': str(config.DB_PATH), "
"'storage': str(config.STORAGE_DIR)}",
env={"WOV_DATA_DIR": str(custom)},
)
# 验证结果
assert result["data"] == str(custom)
assert result["db"] == str(custom / "wov.db")
assert result["storage"] == str(custom / "storage")
def test_explicit_db_and_storage_env_take_precedence(tmp_path: Path) -> None:
"""WOV_DB_PATH / WOV_STORAGE_DIR 可独立覆盖(不跟随 DATA_DIR)。"""
# 数据:分别指定的数据库与存储路径。
db_path = tmp_path / "x" / "custom.db"
storage = tmp_path / "y" / "store"
# 测试过程
result = _run_in_subprocess(
"result = {'db': str(config.DB_PATH), 'storage': str(config.STORAGE_DIR)}",
env={"WOV_DB_PATH": str(db_path), "WOV_STORAGE_DIR": str(storage)},
)
# 验证结果
assert result["db"] == str(db_path)
assert result["storage"] == str(storage)
def test_boolean_env_parsing() -> None:
"""开关型环境变量:'1' 为 True'0' 为 False。"""
# 数据:批量与清理开关分别置 1 与 0。
# 测试过程
result = _run_in_subprocess(
"result = {'batch': config.BATCH_ENABLED, 'cleanup': config.CLEANUP_ENABLED}",
env={"WOV_BATCH_ENABLED": "1", "WOV_CLEANUP_ENABLED": "0"},
)
# 验证结果
assert result["batch"] is True
assert result["cleanup"] is False
def test_interval_env_parsing() -> None:
"""轮询间隔环境变量被解析为对应浮点值。"""
# 数据:指定的调度与批量间隔。
# 测试过程
result = _run_in_subprocess(
"result = {'sched': config.SCHEDULER_INTERVAL_SECONDS, "
"'batch': config.BATCH_INTERVAL_SECONDS}",
env={"WOV_SCHEDULER_INTERVAL_SECONDS": "2.5", "WOV_BATCH_INTERVAL_SECONDS": "0.25"},
)
# 验证结果
assert result["sched"] == 2.5
assert result["batch"] == 0.25
def test_paths_are_pathlib_objects(tmp_path: Path) -> None:
"""路径配置是 pathlib.Path(跨平台,不写死 Windows 盘符)。"""
# 数据:自定义数据目录。
# 测试过程
result = _run_in_subprocess(
"result = {'is_path': isinstance(config.DATA_DIR, __import__('pathlib').Path)}",
env={"WOV_DATA_DIR": str(tmp_path / "d")},
)
# 验证结果
assert result["is_path"] is True