Session checkpoint on webui-plugin: - engine dump (nautctl dump) + engine endgame integration - per-file move locations persistence; torrent-level "Set location" with reset/keep-relative/leave-separate handling + residual prune - Lua: naut.get_labels, define_settings/get_setting (script_host struct) - daemon-owned labels (category+tags) + taxonomy persistence; webui write-through - fix: pausing a completed/seeding torrent now sticks (stop wins over result) - automation tab responsive layout; anime_sort label gating + settings Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
140 lines
5.5 KiB
Lua
140 lines
5.5 KiB
Lua
-- Tests anime_sort.lua end to end (stubbing the `naut` host API) and verifies
|
|
-- its embedded parser still matches examples/anitomy.lua.
|
|
-- Run: lua examples/test_anime_sort.lua
|
|
local dir = arg[0]:match("^(.*/)") or "./"
|
|
package.path = dir .. "?.lua;" .. package.path
|
|
local ref = require("anitomy")
|
|
|
|
-- stub the host API the script calls, and silence its prints
|
|
local captured
|
|
-- Per-torrent label stub: torrent 1 is labelled "anime"; others are unlabelled.
|
|
local LABELS = { [1] = { "anime" } }
|
|
-- Optional per-key setting overrides; nil falls back to the script's defaults.
|
|
local SETTINGS = {}
|
|
_G.naut = {
|
|
move_file = function(tid, idx, dest) captured = dest end,
|
|
get_labels = function(tid) return LABELS[tid] or {} end,
|
|
define_settings = function(_) end, -- schema declaration: no-op here
|
|
get_setting = function(key) return SETTINGS[key] end,
|
|
}
|
|
local realprint = print
|
|
_G.print = function() end
|
|
|
|
dofile(dir .. "anime_sort.lua") -- defines on_file_complete + _G.anime_sort
|
|
|
|
_G.print = realprint
|
|
|
|
local configured_root = _G.anime_sort.destination({
|
|
title = "__ROOT_PROBE__",
|
|
file_name = "__ROOT_PROBE__.mkv",
|
|
extension = "mkv",
|
|
}):match("^(.*)/__ROOT_PROBE__/__ROOT_PROBE__%.mkv$")
|
|
assert(configured_root, "could not derive SORTED_ROOT from anime_sort.lua")
|
|
|
|
local fails = 0
|
|
local function fire(path, torrent_id)
|
|
captured = nil
|
|
on_file_complete({ torrent_id = torrent_id or 1, index = 0, path = path })
|
|
return captured
|
|
end
|
|
local function expect(path, want_dest)
|
|
local got = fire(path)
|
|
if got ~= want_dest then
|
|
fails = fails + 1
|
|
realprint("FAIL " .. path)
|
|
realprint(" got: " .. tostring(got))
|
|
realprint(" want: " .. tostring(want_dest))
|
|
else
|
|
realprint("ok " .. path)
|
|
end
|
|
end
|
|
|
|
-- episodes
|
|
expect("/downloads/1/[HorribleSubs] Boku no Hero Academia - 12 [1080p].mkv",
|
|
configured_root .. "/Boku no Hero Academia/Season 01/Boku no Hero Academia - S01E12.mkv")
|
|
expect("/downloads/2/[Group] Attack on Titan S04E28 [BD 1080p HEVC].mkv",
|
|
configured_root .. "/Attack on Titan/Season 04/Attack on Titan - S04E28.mkv")
|
|
expect("/dl/[Commie] Steins;Gate 0 - 12 [BD 1080p].mkv",
|
|
configured_root .. "/Steins;Gate 0/Season 01/Steins;Gate 0 - S01E12.mkv")
|
|
expect("Demon.Slayer.Kimetsu.no.Yaiba.S03E11.1080p.mkv",
|
|
configured_root .. "/Demon Slayer Kimetsu no Yaiba/Season 03/Demon Slayer Kimetsu no Yaiba - S03E11.mkv")
|
|
expect("/x/[Erai-raws] Jujutsu Kaisen 2nd Season - 17 [1080p].mkv",
|
|
configured_root .. "/Jujutsu Kaisen/Season 02/Jujutsu Kaisen - S02E17.mkv")
|
|
|
|
-- movies (no episode)
|
|
expect("/m/[Group] A Silent Voice [BD 1080p FLAC].mkv",
|
|
configured_root .. "/A Silent Voice/A Silent Voice.mkv")
|
|
expect("/m/Spirited.Away.2001.1080p.BluRay.x264.mkv",
|
|
configured_root .. "/Spirited Away/Spirited Away (2001).mkv")
|
|
|
|
-- non-video file must be skipped (no move queued)
|
|
do
|
|
local got = fire("/d/[Group] Some Show - 01 [1080p].ass")
|
|
if got ~= nil then
|
|
fails = fails + 1
|
|
realprint("FAIL non-video should be skipped, got: " .. tostring(got))
|
|
else
|
|
realprint("ok non-video skipped")
|
|
end
|
|
end
|
|
|
|
-- label gate: a torrent without the "anime" label is left alone (no move),
|
|
-- while an "anime"-labelled torrent is still sorted.
|
|
do
|
|
local episode = "/downloads/[HorribleSubs] Boku no Hero Academia - 12 [1080p].mkv"
|
|
local unlabelled = fire(episode, 2) -- torrent 2 has no labels
|
|
if unlabelled ~= nil then
|
|
fails = fails + 1
|
|
realprint("FAIL unlabelled torrent should be skipped, got: "
|
|
.. tostring(unlabelled))
|
|
else
|
|
realprint("ok unlabelled torrent skipped")
|
|
end
|
|
local labelled = fire(episode, 1) -- torrent 1 is labelled "anime"
|
|
if labelled == nil then
|
|
fails = fails + 1
|
|
realprint("FAIL anime-labelled torrent should be sorted")
|
|
else
|
|
realprint("ok anime-labelled torrent sorted")
|
|
end
|
|
end
|
|
|
|
-- settings: a value configured in the UI (delivered via naut.get_setting) takes
|
|
-- effect live, without editing the script.
|
|
do
|
|
SETTINGS.sorted_root = "/custom/lib"
|
|
local got = fire("/downloads/[HorribleSubs] Boku no Hero Academia - 12 [1080p].mkv", 1)
|
|
SETTINGS.sorted_root = nil
|
|
local want = "/custom/lib/Boku no Hero Academia/Season 01/Boku no Hero Academia - S01E12.mkv"
|
|
if got ~= want then
|
|
fails = fails + 1
|
|
realprint("FAIL sorted_root override not applied, got: " .. tostring(got))
|
|
else
|
|
realprint("ok sorted_root setting override applied")
|
|
end
|
|
end
|
|
|
|
-- drift guard: embedded parser must agree with anitomy.lua
|
|
local embedded = _G.anime_sort.anitomy
|
|
local drift = 0
|
|
for _, name in ipairs({
|
|
"[HorribleSubs] Boku no Hero Academia - 12 [1080p].mkv",
|
|
"[Group] Attack on Titan S04E28 [BD 1080p HEVC].mkv",
|
|
"One.Piece.S01E1071.1080p.WEB.x264.mkv",
|
|
"Spirited.Away.2001.1080p.BluRay.x264.mkv",
|
|
"[Commie] Steins;Gate 0 - 12 [BD 1080p].mkv",
|
|
}) do
|
|
local a, b = ref.parse(name), embedded.parse(name)
|
|
for _, k in ipairs({ "title", "season", "episode", "year", "episode_end" }) do
|
|
if a[k] ~= b[k] then
|
|
drift = drift + 1
|
|
realprint(("DRIFT %s field %s: anitomy=%s embedded=%s")
|
|
:format(name, k, tostring(a[k]), tostring(b[k])))
|
|
end
|
|
end
|
|
end
|
|
if drift == 0 then realprint("ok embedded parser matches anitomy.lua")
|
|
else fails = fails + drift end
|
|
|
|
if fails == 0 then realprint("\nALL PASS"); os.exit(0)
|
|
else realprint("\n" .. fails .. " FAILED"); os.exit(1) end
|