Files
MediaOrganizer/tests/cases/media/identify.sh
T
Shuery 0df07ad0ae refactor: 迁移至 bashly CLI 与功能域模块结构
- CLI 定义迁移至 src/bashly.yml(bashly 1.4 生成参数解析),入口收敛为 src/root_command.sh
- 源码按功能域重组:config/storage/integrate/media/pipeline,消除扁平散落
- 新增零依赖测试框架 tests/(123 用例)与 CI 流水线(lint + test + build + 产物一致性)
- build.sh 支持版本单一来源注入与 --check 产物校验;新增 lint.sh(bash -n + shellcheck 零容忍)
- 删除旧单文件 media_organizer.sh 与旧扁平模块
2026-08-14 22:41:28 +08:00

208 lines
9.0 KiB
Bash
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.
# ============================================================================
# 识别搜索链测试(src/lib/media/identify.sh)
#
# 覆盖 R1 搜索链抽象后的行为保持:统一尝试器(tv_search_once/movie_search_once)
# 的命中/无结果/请求失败返回码、两行协议回传、识别函数的层级短路语义
# (主搜索失败即 return 2,后续层失败忽略)、目录/MAL/别名/后缀剥离兜底。
#
# mock 策略:按 "endpoint|语言" 查 MOCK_RESPONSES 表;无表项 = 空哨兵(无结果);
# MOCK_FAIL_ENDPOINTS 匹配 → 请求失败(rc=2)。tmdb_api_lang 经动态作用域
# 透传 TMDB_LANG,mock 用它区分 zh/en 响应。
# ============================================================================
# mock 响应表(文件级全局:declare 在函数内是函数局部,mock 函数访问不到)
declare -A MOCK_RESPONSES=()
declare -A MOCK_FAIL_ENDPOINTS=()
setup() {
TEST_DIR="$(mktemp -d)"
DRY_RUN=false
TMDB_DELAY=0
TMDB_LANG="zh-CN"
SOURCE_DIR="$TEST_DIR/src"
FOLDER_SHOWS="节目"
FOLDER_MOVIES="电影"
mkdir -p "$SOURCE_DIR"
MOCK_RESPONSES=()
MOCK_FAIL_ENDPOINTS=()
}
teardown() {
rm -rf "$TEST_DIR"
}
# mock:按 "endpoint|lang" 查表;无表项 → 无结果(rc=1);失败表命中 → rc=2
tmdb_api() {
local endpoint="$1"
shift
local key="${endpoint}|${TMDB_LANG:-}"
if [[ -n "${MOCK_FAIL_ENDPOINTS[$endpoint]:-}" ]]; then
return 2
fi
local resp="${MOCK_RESPONSES[$key]:-}"
[[ -z "$resp" ]] && return 1
echo "$resp"
}
# mock:用户规则别名查找(真实函数读规则文件,测试桩直接返回)
match_rule_lookup_alias() {
echo ""
}
# --- 统一尝试器 ---
# tv_search_once:zh 命中 → 输出 id,返回 0
test_tv_search_once_hit() {
MOCK_RESPONSES["/search/tv|zh-CN"]='{"results":[{"id":100,"name":"Show A"}]}'
local id="" rc=0
id=$(tv_search_once "Show A" "1" "" "") || rc=$?
assert_eq "0" "$rc" "tv_search_once 命中返回码"
assert_eq "100" "$id" "tv_search_once 命中 id"
}
# tv_search_once:无结果 → 返回 1,无输出
test_tv_search_once_miss() {
local id="" rc=0
id=$(tv_search_once "No Such Show" "1" "" "") || rc=$?
assert_eq "1" "$rc" "tv_search_once 无结果返回码"
assert_empty "$id" "tv_search_once 无结果输出"
}
# tv_search_once:请求失败 → 返回 2(不重试语义由调用方处理)
test_tv_search_once_request_failed() {
MOCK_FAIL_ENDPOINTS["/search/tv"]=1
local rc=0
tv_search_once "Show A" "1" "" "" >/dev/null || rc=$?
assert_eq "2" "$rc" "tv_search_once 请求失败返回码"
}
# tv_search_once:zh 空 → en-US 命中(语言分支走 tmdb_api_lang)
test_tv_search_once_en_fallback() {
MOCK_RESPONSES["/search/tv|en-US"]='{"results":[{"id":200,"name":"Show A"}]}'
local id="" rc=0
id=$(tv_search_once "Show A" "1" "" "en-US") || rc=$?
assert_eq "0" "$rc" "en 命中返回码"
assert_eq "200" "$id" "en 命中 id"
}
# movie_search_once:两行协议(id + 单行响应 JSON)
test_movie_search_once_two_line_protocol() {
MOCK_RESPONSES["/search/movie|zh-CN"]='{"results":[{"id":50,"title":"Movie X","release_date":"2020-01-01"}]}'
local out="" rc=0
out=$(movie_search_once "Movie X" "2020" "") || rc=$?
assert_eq "0" "$rc" "movie_search_once 命中返回码"
local id resp
id=$(printf '%s\n' "$out" | sed -n '1p')
resp=$(printf '%s\n' "$out" | sed -n '2p')
assert_eq "50" "$id" "两行协议 id 行"
assert_contains "$resp" '"title":"Movie X"' "两行协议响应行"
}
# --- 目录兜底 ---
# tv_search_by_dir:源根直属散放不兜底(无输出)
test_tv_search_by_dir_source_root_skipped() {
local id
id=$(tv_search_by_dir "$SOURCE_DIR/Show.mkv" "Show" "1" "")
assert_empty "$id" "源根散放不兜底"
}
# tv_search_by_dir:目录名命中(zh → en)
test_tv_search_by_dir_hit() {
mkdir -p "$TEST_DIR/src/[Group] Show X"
local f="$TEST_DIR/src/[Group] Show X/01.mkv"
MOCK_RESPONSES["/search/tv|zh-CN"]='{"results":[{"id":300,"name":"Show X"}]}'
local id
id=$(tv_search_by_dir "$f" "01" "1" "")
assert_eq "300" "$id" "目录名兜底命中"
}
# --- 完整识别链 ---
# identify_tv_show:zh 命中 → build_tv_dest 输出目标
test_identify_tv_show_zh_hit() {
MOCK_RESPONSES["/search/tv|zh-CN"]='{"results":[{"id":100,"name":"Show A"}]}'
MOCK_RESPONSES["/tv/100|zh-CN"]='{"name":"Show A","first_air_date":"2020-01-01","number_of_seasons":2,"seasons":[{"season_number":1},{"season_number":2}]}'
MOCK_RESPONSES["/tv/100/season/1|zh-CN"]='{"episodes":[{"episode_number":1,"name":"Pilot"},{"episode_number":2,"name":"Two"}]}'
local dest="" rc=0
dest=$(identify_tv_show "Show A" "1" "1" "" "Show A S01E01" "mkv" "" "$TEST_DIR/src/Show A/Show A S01E01.mkv") || rc=$?
assert_eq "0" "$rc" "zh 命中识别返回码"
assert_contains "$dest" "节目/Show A (2020)/Season 01|S01E01 - Pilot.mkv" "zh 命中目标"
}
# identify_tv_show:zh 空 → en 命中
test_identify_tv_show_zh_miss_en_hit() {
MOCK_RESPONSES["/search/tv|en-US"]='{"results":[{"id":200,"name":"Show B"}]}'
MOCK_RESPONSES["/tv/200|zh-CN"]='{"name":"Show B","first_air_date":"2021-01-01","number_of_seasons":1,"seasons":[{"season_number":1}]}'
MOCK_RESPONSES["/tv/200/season/1|zh-CN"]='{"episodes":[{"episode_number":1,"name":"First"}]}'
local dest="" rc=0
dest=$(identify_tv_show "Show B" "1" "1" "" "Show B S01E01" "mkv" "" "$TEST_DIR/src/Show B/Show B S01E01.mkv") || rc=$?
assert_eq "0" "$rc" "en 命中识别返回码"
assert_contains "$dest" "Show B (2021)" "en 命中目标"
}
# identify_tv_show:主搜索(zh)请求失败 → 短路 return 2(不试后续层)
test_identify_tv_show_zh_fail_shortcircuit() {
MOCK_FAIL_ENDPOINTS["/search/tv"]=1
MOCK_RESPONSES["/search/tv|en-US"]='{"results":[{"id":200,"name":"Show B"}]}'
local rc=0
identify_tv_show "Show B" "1" "1" "" "Show B S01E01" "mkv" "" "$TEST_DIR/src/Show B/Show B S01E01.mkv" >/dev/null 2>&1 || rc=$?
assert_eq "2" "$rc" "主搜索失败短路返回码"
}
# identify_tv_show:zh/en 空 → 后缀二次剥离重搜命中("Railgun S" → "Railgun")
test_identify_tv_show_suffix_strip() {
MOCK_RESPONSES["/search/tv|zh-CN"]='{"results":[{"id":400,"name":"Railgun"}]}'
MOCK_RESPONSES["/tv/400|zh-CN"]='{"name":"Railgun","first_air_date":"2020-01-01","number_of_seasons":3,"seasons":[{"season_number":1},{"season_number":2},{"season_number":3}]}'
MOCK_RESPONSES["/tv/400/season/1|zh-CN"]='{"episodes":[{"episode_number":1,"name":"First"}]}'
# 注意:首次搜索 "Railgun S" 无结果(mock 表无 "Railgun S" 条目),二次剥离后 "Railgun" 命中
local dest="" rc=0
dest=$(identify_tv_show "Railgun S" "1" "1" "" "Railgun S S01E01" "mkv" "" "$TEST_DIR/src/Railgun S/Railgun S S01E01.mkv") || rc=$?
assert_eq "0" "$rc" "后缀剥离识别返回码"
assert_contains "$dest" "Railgun (2020)" "后缀剥离命中目标"
}
# identify_tv_show:全链失败 → return 1(无结果)
test_identify_tv_show_all_miss() {
local rc=0
identify_tv_show "No Such Show" "1" "1" "" "No Such Show S01E01" "mkv" "" "$TEST_DIR/src/No Such Show/No Such Show S01E01.mkv" >/dev/null 2>&1 || rc=$?
assert_eq "1" "$rc" "全链无结果返回码"
}
# identify_movie:zh 命中 → build_movie_dest 输出目标
test_identify_movie_zh_hit() {
MOCK_RESPONSES["/search/movie|zh-CN"]='{"results":[{"id":50,"title":"Movie X","release_date":"2020-01-01"}]}'
MOCK_RESPONSES["/movie/50|zh-CN"]='{"title":"Movie X","release_date":"2020-01-01"}'
local dest="" rc=0
dest=$(identify_movie "Movie X" "mkv" "$TEST_DIR/src/Movie X (2020).mkv" "Movie X" "2020") || rc=$?
assert_eq "0" "$rc" "movie zh 命中返回码"
assert_contains "$dest" "电影/Movie X (2020)|Movie X (2020).mkv" "movie zh 命中目标"
}
# identify_movie:年份不符且无一致候选 → 需甄别(emit MATCH,返回 3)
test_identify_movie_year_mismatch_match() {
MOCK_RESPONSES["/search/movie|zh-CN"]='{"results":[{"id":50,"title":"Movie X","release_date":"2019-05-01"}]}'
local rc=0
identify_movie "Movie X" "mkv" "$TEST_DIR/src/Movie X (2020).mkv" "Movie X" "2020" >/dev/null 2>&1 || rc=$?
assert_eq "3" "$rc" "年份不符需甄别返回码"
assert_success test -n "${PENDING_AI_MATCH["$TEST_DIR/src/Movie X (2020).mkv"]:-}"
}
# identify_movie:主搜索失败 → 短路 return 2
test_identify_movie_zh_fail_shortcircuit() {
MOCK_FAIL_ENDPOINTS["/search/movie"]=1
local rc=0
identify_movie "Movie X" "mkv" "$TEST_DIR/src/Movie X (2020).mkv" "Movie X" "2020" >/dev/null 2>&1 || rc=$?
assert_eq "2" "$rc" "movie 主搜索失败短路返回码"
}
# identify_movie:zh 空 → en 命中(两行协议回传年份校验通过)
test_identify_movie_en_fallback() {
MOCK_RESPONSES["/search/movie|en-US"]='{"results":[{"id":60,"title":"Movie Y","release_date":"2021-01-01"}]}'
MOCK_RESPONSES["/movie/60|zh-CN"]='{"title":"Movie Y","release_date":"2021-01-01"}'
local dest="" rc=0
dest=$(identify_movie "Movie Y" "mkv" "$TEST_DIR/src/Movie Y (2021).mkv" "Movie Y" "2021") || rc=$?
assert_eq "0" "$rc" "movie en 命中返回码"
assert_contains "$dest" "Movie Y (2021)" "movie en 命中目标"
}