Files
gen1recomp/tests/engine/sync_mods_test.lua
T
2026-08-19 05:57:44 -04:00

149 lines
5.7 KiB
Lua

package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.harness")
love = love or require("tests.love_stub")
local SyncMods = require("src.sync.SyncMods")
local function row(id, version, enabled, github)
return { id = id, version = version, github = github,
enabledByVersion = enabled }
end
local function deps(installed, indexes, catalog)
local calls = { installed = {}, enabled = {}, indexes = {} }
return calls, {
installed = function() return installed end,
indexes = function() return indexes or {} end,
addIndex = function(url)
calls.indexes[#calls.indexes + 1] = url
return { feed = url }
end,
findEntry = function(id) return (catalog or {})[id] end,
install = function(entry)
calls.installed[#calls.installed + 1] = entry.id
return true
end,
setEnabled = function(id, enabled, version)
calls.enabled[#calls.enabled + 1] = id .. ":" .. tostring(version)
return true
end,
}
end
do
local _, d = deps({
row("zeta", "1.0.0", { red = true, blue = false, yellow = false, gold = false }),
row("alpha", "2.1.0", { red = true, gold = true }, "someone/alpha"),
}, { { url = "https://mods.example/index.json",
feed = "https://mods.example/index.json" } })
local manifest = SyncMods.build(d)
T.eq(manifest.rev, SyncMods.REV, "the manifest carries its shape revision")
T.eq(#manifest.indexes, 1, "the player's index list rides along")
T.eq(manifest.indexes[1], "https://mods.example/index.json",
"as the url they typed")
T.eq(#manifest.mods, 2, "every installed mod is listed")
T.eq(manifest.mods[1].id, "alpha", "sorted by id so the manifest is stable")
T.eq(manifest.mods[1].source, "github:someone/alpha",
"a github mod records where it came from")
T.eq(manifest.mods[2].source, "local",
"a hand-installed mod is marked local rather than invented")
T.eq(#manifest.mods[1].enabledFor, 2, "alpha is on for two games")
T.eq(manifest.mods[1].enabledFor[1], "red", "in GameVersion order")
T.eq(manifest.mods[1].enabledFor[2], "gold", "red then gold")
T.eq(#manifest.mods[2].enabledFor, 1, "zeta is on for one")
end
do
local manifest = {
rev = 1,
indexes = { "https://mods.example/index.json", "https://other.example/i.json" },
mods = {
{ id = "alpha", version = "2.1.0", enabledFor = { "red", "gold" } },
{ id = "beta", version = "1.0.0", enabledFor = { "red" } },
{ id = "ghost", version = "0.1.0", source = "local", enabledFor = { "red" } },
},
}
local _, d = deps(
{ row("alpha", "2.1.0", { red = true }) },
{ { url = "https://mods.example/index.json",
feed = "https://mods.example/index.json" } },
{ beta = { id = "beta" } })
local plan = SyncMods.plan(manifest, d)
T.eq(#plan.indexes, 1, "only the index this device is missing is planned")
T.eq(plan.indexes[1], "https://other.example/i.json", "the new one")
T.eq(#plan.toInstall, 1, "one mod can be fetched from an index")
T.eq(plan.toInstall[1].id, "beta", "the one the catalog knows")
T.eq(#plan.missing, 1, "the mod nobody publishes is reported, not invented")
T.eq(plan.missing[1].id, "ghost", "by id")
T.eq(#plan.toEnable, 2, "every game answer that differs is planned")
for _, want in ipairs(plan.toEnable) do
T.check(want.id ~= "ghost",
"a mod that cannot be installed is never enabled")
end
T.eq(SyncMods.planEmpty(plan), false, "a plan with work is not empty")
local same = SyncMods.plan({ rev = 1, indexes = {}, mods = {
{ id = "alpha", version = "2.1.0", enabledFor = { "red" } } } }, d)
T.eq(SyncMods.planEmpty(same), true, "a matching device plans nothing")
end
do
local calls, d = deps({}, {}, { beta = { id = "beta" } })
local plan = {
indexes = { "https://other.example/i.json" },
toInstall = { { id = "beta", entry = { id = "beta" } } },
toEnable = { { id = "beta", version = "red" } },
missing = { { id = "ghost" } },
}
local seen = {}
local ok = SyncMods.apply(plan, function(done, total, label)
seen[#seen + 1] = ("%d/%d %s"):format(done, total, label)
end, d)
T.eq(ok, true, "applying a plan reports success")
T.eq(calls.indexes[1], "https://other.example/i.json", "the index is added")
T.eq(calls.installed[1], "beta", "the mod is installed through the launcher path")
T.eq(calls.enabled[1], "beta:red", "and enabled for the game that wanted it")
T.eq(#seen, 3, "progress is reported once per step")
T.eq(seen[3], "3/3 beta", "counting up to the total")
end
do
local _, d = deps({}, {}, {})
d.install = function() return nil, "download failed" end
local ok, err = SyncMods.apply({
toInstall = { { id = "beta", entry = { id = "beta" } } } }, nil, d)
T.eq(ok, false, "a failed install fails the apply")
T.check(tostring(err):find("download failed", 1, true) ~= nil,
"naming the mod and the reason")
end
do
local calls, d = deps({}, {}, {})
d.install = function() return nil, "download failed" end
local ok = SyncMods.apply({
toInstall = { { id = "beta", entry = { id = "beta" } } },
toEnable = { { id = "beta", version = "red" } },
}, nil, d)
T.eq(ok, false, "the apply still reports the failure")
T.eq(#calls.enabled, 0,
"a mod whose install failed is not switched on regardless")
end
do
local calls, d = deps({}, {}, {})
local steps = SyncMods.steps({
indexes = { "https://other.example/i.json" },
toInstall = { { id = "beta", entry = { id = "beta" } } },
toEnable = { { id = "beta", version = "red" } },
}, d)
T.eq(#steps, 3, "a plan splits into one step per unit of work")
T.eq(steps[1].run(), true, "steps run one at a time")
T.eq(#calls.indexes, 1, "so the caller can draw between them")
T.eq(#calls.installed, 0, "without the rest of the plan having run yet")
end
T.finish("sync_mods")