Files
gen1recomp/tests/engine/update_worker_probe_gate.lua
T
bryanthaboi 09445ab6b8 CLOSES #2588
2026-10-02 13:58:59 -04:00

132 lines
5.7 KiB
Lua

package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.harness")
local check, eq = T.check, T.eq
local Json = require("src.link.Json")
local Check = require("src.update.Check")
local Semver = require("src.update.Semver")
local payloadName = "gen1recomp-0.3.47.love"
local payloadRel = "updates/" .. payloadName
local hash = string.rep("a", 64)
local notes = "Windows update regression fixture"
local body = Json.encode({
tag_name = "v0.3.47", body = notes,
assets = {
{ name = payloadName, browser_download_url = "fixture:payload", size = 7 },
{ name = "sha256sums.txt", browser_download_url = "fixture:sums" },
{ name = "gen1recomp-0.3.47-windows.zip", browser_download_url = "fixture:full" },
},
})
-- Execute the real worker command loop with deterministic transport, hashing
-- and archive probes. Both cached and fresh payloads must pass the same gate.
local function runWorker(boot, cached)
local files, states = {}, {}
if cached then files[payloadRel] = "payload" end
local commands = { { cmd = "check", target = { os = "Windows" } } }
if not cached then commands[#commands + 1] = { cmd = "download" } end
commands[#commands + 1] = { cmd = "quit" }
local index = 0
local modules = {
["src/link/Json.lua"] = Json,
["src/update/Check.lua"] = Check,
["src/update/Semver.lua"] = Semver,
["src/core/Version.lua"] = { engine = "0.3.46", shell = 2, payloadHost = "love" },
["src/update/Boot.lua"] = boot,
["src/core/HostShell.lua"] = {
canFetch = function() return true end,
haveCurl = function() return false end,
httpGet = function(url)
if url == "fixture:sums" then return hash .. " " .. payloadName .. "\n" end
return body
end,
httpDownload = function(_, path)
files[path:gsub("^fixture%-save/", "")] = "payload"
return true
end,
},
}
local savedLove, savedRename = _G.love, os.rename
local savedModules = {}
local moduleNames = { "love.thread", "love.filesystem", "love.data", "love.timer", "love.system" }
for _, name in ipairs(moduleNames) do
savedModules[name] = package.loaded[name]
package.loaded[name] = true
end
_G.love = {
filesystem = {
load = function(path) return function() return modules[path] end end,
getSaveDirectory = function() return "fixture-save" end,
getInfo = function(path)
if files[path] then return { type = "file", size = #files[path] } end
end,
read = function(path) return files[path] end,
write = function(path, data) files[path] = data; return true end,
remove = function(path) files[path] = nil; return true end,
createDirectory = function() return true end,
},
system = { getOS = function() return "Windows" end },
data = { hash = function() return "digest" end, encode = function() return hash end },
timer = {},
thread = { getChannel = function(name)
if name == "update_check_state" then
return { push = function(_, state) states[#states + 1] = state end }
end
return { demand = function()
index = index + 1
assert(commands[index], "worker consumed all fixture commands")
return commands[index]
end }
end },
}
os.rename = function() return false end
local ok, err = pcall(dofile, "src/update/check_worker.lua")
_G.love, os.rename = savedLove, savedRename
for _, name in ipairs(moduleNames) do package.loaded[name] = savedModules[name] end
assert(ok, err)
return states, files
end
local cases = {
{ name = "missing Boot", reason = "payload_probe_unavailable" },
{ name = "missing probe", boot = {}, reason = "payload_probe_unavailable" },
{ name = "failed mount", boot = { probePayload = function() return nil, "mount failed" end },
reason = "payload_probe_failed" },
{ name = "throwing probe", boot = { probePayload = function() error("probe failed") end },
reason = "payload_probe_failed" },
{ name = "invalid probe result", boot = { probePayload = function() return false end },
reason = "payload_probe_failed" },
{ name = "newer shell", boot = { probePayload = function() return { minShell = 3 } end },
reason = "min_shell" },
{ name = "different host", boot = { probePayload = function() return { payloadHost = "other" } end },
reason = "payload_host" },
}
for _, cached in ipairs({ true, false }) do
local route = cached and "cached" or "downloaded"
for _, case in ipairs(cases) do
local label = route .. " " .. case.name
local states, files = runWorker(case.boot, cached)
local final = states[#states]
eq(final.status, "needs_full", label .. " requires a native package")
eq(final.reason, case.reason, label .. " reports its reason")
eq(final.notes, notes, label .. " retains release notes")
for _, state in ipairs(states) do
check(state.status ~= "ready", label .. " never offers a false restart")
end
local record = Json.decode(files["updates/full-update.json"] or "{}")
eq(record.reason, case.reason, label .. " persists its reason")
eq(record.full and record.full.url, "fixture:full", label .. " persists the Windows package")
eq(files[payloadRel], nil, label .. " removes the rejected payload")
eq(files[payloadRel .. ".part"], nil, label .. " removes the rejected partial payload")
eq(Json.decode(files["updates/notes_cache.json"])["0.3.47"], notes,
label .. " caches notes before rejecting the payload")
end
local states, files = runWorker({ probePayload = function()
return { engine = "0.3.47", minShell = 2, payloadHost = "love" }
end }, cached)
eq(states[#states].status, "ready", route .. " compatible payload offers a restart")
eq(files[payloadRel], "payload", route .. " compatible payload is retained")
end
T.finish("update worker probe gate")