mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-25 23:11:15 +02:00
1905261c5b
CLOSES #1483, CLOSES #1610, CLOSES #1615, CLOSES #1646, CLOSES #1649, CLOSES #1651, CLOSES #1653, CLOSES #1656, CLOSES #1683, CLOSES #1685, CLOSES #1686, CLOSES #1687, CLOSES #1688, CLOSES #1689, CLOSES #1690, CLOSES #1693, CLOSES #1694, CLOSES #1695, CLOSES #1696, CLOSES #1702, CLOSES #1704, CLOSES #1705, CLOSES #1706, CLOSES #1707, CLOSES #1708, CLOSES #1710, CLOSES #1711, CLOSES #1712, CLOSES #1713, CLOSES #1716, CLOSES #1717, CLOSES #1718, CLOSES #1719, CLOSES #1720, CLOSES #1721, CLOSES #1725, CLOSES #1732, CLOSES #1745, CLOSES #1748, CLOSES #1749, CLOSES #1751, CLOSES #1754
434 lines
21 KiB
Lua
434 lines
21 KiB
Lua
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
if not rawget(_G, "bit") and not rawget(_G, "bit32") then
|
|
local ok, bit32 = pcall(require, "bit32")
|
|
if ok then _G.bit32 = bit32 end
|
|
end
|
|
|
|
local T = require("tests.harness")
|
|
love = love or require("tests.love_stub")
|
|
|
|
local Base64 = require("src.core.Base64")
|
|
local SaveData = require("src.core.SaveData")
|
|
local CartManifest = require("src.carts.CartManifest")
|
|
local CartStore = require("src.carts.CartStore")
|
|
|
|
local SHA = ("a1b2c3d4"):rep(8)
|
|
local SHA2 = ("beefcafe"):rep(8)
|
|
local MD5 = ("0123456789abcdef"):rep(2)
|
|
|
|
local function memfs()
|
|
local files, dirs = {}, {}
|
|
local fs
|
|
fs = {
|
|
files = files,
|
|
write = function(path, body) files[path] = body return true end,
|
|
read = function(path) return files[path] end,
|
|
remove = function(path) files[path] = nil return true end,
|
|
createDirectory = function(path) dirs[path] = true return true end,
|
|
getInfo = function(path)
|
|
if files[path] ~= nil then return { type = "file" } end
|
|
if dirs[path] then return { type = "directory" } end
|
|
for name in pairs(files) do
|
|
if name:sub(1, #path + 1) == path .. "/" then return { type = "directory" } end
|
|
end
|
|
return nil
|
|
end,
|
|
getDirectoryItems = function(path)
|
|
local prefix = (path == "" or path == nil) and "" or (path .. "/")
|
|
local out, seen = {}, {}
|
|
for name in pairs(files) do
|
|
if name:sub(1, #prefix) == prefix then
|
|
local child = name:sub(#prefix + 1):match("^([^/]+)")
|
|
if child and not seen[child] then
|
|
seen[child] = true
|
|
out[#out + 1] = child
|
|
end
|
|
end
|
|
end
|
|
table.sort(out)
|
|
return out
|
|
end,
|
|
}
|
|
return fs
|
|
end
|
|
|
|
local function cartTable(over)
|
|
local tbl = {
|
|
id = "kanto_plus",
|
|
title = "Kanto Plus",
|
|
version = "1.2.0",
|
|
author = "Ren",
|
|
shell = "#3fa9f5",
|
|
base = "red",
|
|
seal = "sealed",
|
|
mods = {
|
|
{ id = "rare_soda", source = "github", repo = "ren/rare-soda",
|
|
version = "0.4.1", sha256 = SHA,
|
|
options = { flavour = "grape", sweetness = 3 } },
|
|
{ id = "hard_mode", source = "gamebanana", mod = 4821, file = 99123,
|
|
md5 = MD5 },
|
|
},
|
|
}
|
|
for key, value in pairs(over or {}) do tbl[key] = value end
|
|
return tbl
|
|
end
|
|
|
|
local function bytesOf(over)
|
|
local cart, err = CartManifest.parse(cartTable(over))
|
|
if not cart then error("fixture does not parse: " .. tostring(err)) end
|
|
return CartManifest.encode(cart), cart
|
|
end
|
|
|
|
local fs = memfs()
|
|
local bytes, fixture = bytesOf()
|
|
local installed, hash = CartStore.install(bytes, fs)
|
|
T.check(installed ~= nil, "a good cart installs: " .. tostring(hash))
|
|
T.eq(installed.id, "kanto_plus", "install returns the parsed cart")
|
|
T.eq(hash, CartManifest.hash(fixture), "install returns the cart hash")
|
|
T.check(fs.files["carts/kanto_plus.g1rcart"] ~= nil,
|
|
"install writes carts/<id>.g1rcart")
|
|
|
|
local reg = SaveData.loadOptions(fs).carts
|
|
T.check(type(reg) == "table" and type(reg.kanto_plus) == "table",
|
|
"install registers the cart in options.carts")
|
|
T.eq(reg.kanto_plus.title, "Kanto Plus", "the registry carries the title")
|
|
T.eq(reg.kanto_plus.base, "red", "the registry carries the base game")
|
|
T.eq(reg.kanto_plus.version, "1.2.0", "the registry carries the cart version")
|
|
T.eq(reg.kanto_plus.hash, hash, "the registry carries the cart hash")
|
|
T.eq(reg.kanto_plus.file, "carts/kanto_plus.g1rcart",
|
|
"the registry names the cart file")
|
|
|
|
local index = CartStore.index(fs)
|
|
T.eq(#index, 1, "index lists the registry without reading the files")
|
|
T.eq(index[1].base, "red", "an index row carries the base game")
|
|
|
|
local rows = CartStore.list(fs)
|
|
T.eq(#rows, 1, "list returns the installed cart")
|
|
T.eq(rows[1].id, "kanto_plus", "the row names the cart")
|
|
T.eq(rows[1].base, "red", "the row carries the base")
|
|
T.eq(rows[1].cartHash, hash, "the row carries the cart hash")
|
|
T.check(type(rows[1].cart) == "table" and rows[1].cart.mods ~= nil,
|
|
"the row carries the parsed cart")
|
|
T.eq(rows[1].cart.mods[1].sha256, SHA, "the parsed cart keeps its pins")
|
|
|
|
local got, gotHash = CartStore.get("kanto_plus", fs)
|
|
T.check(got ~= nil, "get returns the cart")
|
|
T.eq(gotHash, hash, "get returns the cart hash")
|
|
T.same(got, fixture, "get returns exactly what was installed")
|
|
T.eq(CartStore.get("nothing_here", fs), nil, "get refuses an unknown id")
|
|
T.eq(CartStore.get("../etc/passwd", fs), nil, "get refuses a climbing id")
|
|
|
|
local exported, exportHash = CartStore.export("kanto_plus", fs)
|
|
T.check(type(exported) == "string", "export hands back bytes")
|
|
T.eq(exportHash, hash, "export reports the cart hash")
|
|
T.same(CartManifest.decode(exported), fixture, "the exported bytes decode back")
|
|
T.eq(CartStore.export("nothing_here", fs), nil, "export refuses an unknown id")
|
|
|
|
local blueBytes = bytesOf({ id = "johto_lite", title = "Aaa Johto Lite",
|
|
base = "blue" })
|
|
T.check(CartStore.install(blueBytes, fs) ~= nil, "a blue cart installs")
|
|
T.eq(#CartStore.list(fs), 2, "list returns both carts")
|
|
T.eq(CartStore.list(fs)[1].id, "johto_lite", "list sorts by title")
|
|
T.eq(#CartStore.listFor("red", fs), 1, "listFor red returns one cart")
|
|
T.eq(CartStore.listFor("red", fs)[1].id, "kanto_plus", "listFor red picks the red cart")
|
|
T.eq(#CartStore.listFor("blue", fs), 1, "listFor blue returns one cart")
|
|
T.eq(#CartStore.listFor("yellow", fs), 0, "listFor yellow returns nothing")
|
|
|
|
local newer, newerErr = CartStore.install(
|
|
bytesOf({ version = "1.3.0", title = "Kanto Plus" }), fs)
|
|
T.check(newer ~= nil, "a newer cart replaces the installed one: " .. tostring(newerErr))
|
|
T.eq(CartStore.get("kanto_plus", fs).version, "1.3.0",
|
|
"the newer version is what is installed")
|
|
T.eq(#CartStore.list(fs), 2, "replacing does not add a second row")
|
|
T.eq(SaveData.loadOptions(fs).carts.kanto_plus.version, "1.3.0",
|
|
"the registry follows the replacement")
|
|
|
|
local same, sameErr = CartStore.install(bytesOf({ version = "1.3.0" }), fs)
|
|
T.check(same ~= nil, "the same version reinstalls: " .. tostring(sameErr))
|
|
|
|
local older, olderErr = CartStore.install(bytesOf({ version = "1.1.0" }), fs)
|
|
T.eq(older, nil, "an older cart is refused")
|
|
T.check(type(olderErr) == "string" and olderErr:find("older", 1, true) ~= nil,
|
|
"the refusal says why (got " .. tostring(olderErr) .. ")")
|
|
T.eq(CartStore.get("kanto_plus", fs).version, "1.3.0",
|
|
"the refused install leaves the newer cart in place")
|
|
|
|
T.eq(CartStore.install("return { }", fs), nil, "install refuses an untagged file")
|
|
T.eq(CartStore.install(nil, fs), nil, "install refuses a non-string")
|
|
T.eq(CartStore.install("\1\2\3 not lua", fs), nil, "install refuses noise")
|
|
|
|
fs.files["saves/cart_kanto_plus/slot1.lua"] = "return { player = { name = \"RED\" } }"
|
|
local opts = SaveData.loadOptions(fs)
|
|
opts.cartSlots = { kanto_plus = { list = { "slot1" }, active = "slot1" } }
|
|
SaveData.saveOptions(opts, fs)
|
|
|
|
T.check(CartStore.uninstall("kanto_plus", fs), "uninstall reports success")
|
|
T.eq(fs.files["carts/kanto_plus.g1rcart"], nil, "uninstall removes the cart file")
|
|
T.eq(SaveData.loadOptions(fs).carts.kanto_plus, nil,
|
|
"uninstall clears the registry entry")
|
|
T.eq(#CartStore.list(fs), 1, "the uninstalled cart is gone from the list")
|
|
T.check(fs.files["saves/cart_kanto_plus/slot1.lua"] ~= nil,
|
|
"uninstall leaves the cart's save file alone")
|
|
local slots = SaveData.loadOptions(fs).cartSlots
|
|
T.check(type(slots) == "table" and type(slots.kanto_plus) == "table",
|
|
"uninstall leaves the cart's slot registry alone")
|
|
T.eq(slots.kanto_plus.active, "slot1", "the active slot survives an uninstall")
|
|
|
|
local gone, goneErr = CartStore.uninstall("kanto_plus", fs)
|
|
T.eq(gone, nil, "uninstalling twice is refused")
|
|
T.check(type(goneErr) == "string" and goneErr:find("not installed", 1, true) ~= nil,
|
|
"the second uninstall says why (got " .. tostring(goneErr) .. ")")
|
|
T.eq(CartStore.uninstall("../etc/passwd", fs), nil, "uninstall refuses a climbing id")
|
|
|
|
T.check(CartStore.install(bytes, fs) ~= nil, "the cart reinstalls after removal")
|
|
T.same(CartStore.get("kanto_plus", fs), fixture, "reinstalling restores the cart")
|
|
T.check(fs.files["saves/cart_kanto_plus/slot1.lua"] ~= nil,
|
|
"the old playthrough is still there for the reinstalled cart")
|
|
|
|
fs.files["carts/kanto_plus.g1rcart"] = "return { format = \"nonsense\" }"
|
|
local damaged = CartStore.list(fs)
|
|
T.eq(#damaged, 1, "a corrupt cart file is skipped and the rest still list")
|
|
T.eq(damaged[1].id, "johto_lite", "the healthy cart survives a corrupt sibling")
|
|
T.check(fs.files["carts/kanto_plus.g1rcart"] ~= nil,
|
|
"listing never deletes the file it could not read")
|
|
T.eq(CartStore.get("kanto_plus", fs), nil, "get reports the corrupt cart as unreadable")
|
|
fs.files["carts/kanto_plus.g1rcart"] = nil
|
|
|
|
opts = SaveData.loadOptions(fs)
|
|
opts.carts = opts.carts or {}
|
|
opts.carts.ghost = { id = "ghost", title = "Ghost", base = "red",
|
|
version = "1.0.0", file = "carts/ghost.g1rcart" }
|
|
SaveData.saveOptions(opts, fs)
|
|
local haunted = CartStore.list(fs)
|
|
T.eq(#haunted, 1, "a registry entry with no file is skipped")
|
|
T.eq(haunted[1].id, "johto_lite", "the rest of the list still comes back")
|
|
T.eq(SaveData.loadOptions(fs).carts.ghost, nil,
|
|
"listing prunes the registry entry whose file is gone")
|
|
|
|
local strayCart = select(2, bytesOf({ id = "wanderer", title = "Zzz Wanderer" }))
|
|
fs.files["carts/wanderer.g1rcart"] = CartManifest.encode(strayCart)
|
|
local adopted = CartStore.list(fs)
|
|
T.eq(#adopted, 2, "a cart file with no registry entry is still listed")
|
|
T.eq(adopted[2].id, "wanderer", "the stray cart sorts in by title")
|
|
T.eq(adopted[2].cartHash, CartManifest.hash(strayCart),
|
|
"the stray cart is hashed from its own file")
|
|
T.check(SaveData.loadOptions(fs).carts.wanderer ~= nil,
|
|
"listing registers the stray cart it adopted")
|
|
|
|
fs.files["carts/readme.txt"] = "hello"
|
|
fs.files["carts/half written.g1rcart"] = "return { }"
|
|
T.eq(#CartStore.list(fs), 2, "junk in carts/ is ignored")
|
|
|
|
local empty = memfs()
|
|
T.eq(#CartStore.list(empty), 0, "a fresh install lists no carts")
|
|
T.eq(#CartStore.listFor("red", empty), 0, "listFor is empty on a fresh install")
|
|
|
|
-- ------- the fields the registry rows have to carry through the store
|
|
|
|
local dressedFs = memfs()
|
|
local dressed = select(2, bytesOf({ id = "dressed", title = "Dressed",
|
|
finish = "holo", speeds = { 1, 2 }, seal = "sealed+",
|
|
options = { textSpeed = 1 } }))
|
|
local dressedCart, dressedHash = CartStore.install(CartManifest.encode(dressed),
|
|
dressedFs)
|
|
T.check(dressedCart ~= nil, "a cart with a finish, speeds and settings installs")
|
|
T.eq(dressedHash, CartManifest.hash(dressed), "hashing the same as it was written")
|
|
T.same(CartStore.get("dressed", dressedFs), dressed,
|
|
"and coming back off disk unchanged")
|
|
|
|
local dressedReg = SaveData.loadOptions(dressedFs).carts.dressed
|
|
T.eq(dressedReg.finish, "holo", "the registry row carries the finish")
|
|
T.same(dressedReg.speeds, { 1, 2 }, "and the speed ladder")
|
|
T.eq(dressedReg.seal, "sealed+", "and the seal, spelled in full")
|
|
|
|
local dressedRow = CartStore.list(dressedFs)[1]
|
|
T.eq(dressedRow.finish, "holo", "the list row carries the finish")
|
|
T.same(dressedRow.speeds, { 1, 2 }, "and the speed ladder")
|
|
T.eq(dressedRow.seal, "sealed+", "and the seal")
|
|
T.eq(dressedRow.cart.options.textSpeed, 1,
|
|
"and the settings the cart ships, on the parsed cart")
|
|
T.eq(SaveData.loadOptions(dressedFs).carts.dressed.hash, dressedHash,
|
|
"listing does not rewrite the registry it already agrees with")
|
|
|
|
local function rowSet()
|
|
return {
|
|
{ id = "hard_mode", name = "Hard Mode", version = "2.0.0", enabled = true,
|
|
github = "ren/hard-mode",
|
|
manifest = { id = "hard_mode", version = "2.0.0",
|
|
github = "ren/hard-mode", sha256 = SHA } },
|
|
{ id = "off_mode", name = "Off Mode", version = "1.0.0", enabled = false,
|
|
github = "ren/off-mode",
|
|
manifest = { id = "off_mode", version = "1.0.0",
|
|
github = "ren/off-mode", sha256 = SHA2 } },
|
|
{ id = "rare_soda", name = "Rare Soda", version = "0.4.1", enabled = true,
|
|
github = "ren/rare-soda",
|
|
manifest = { id = "rare_soda", version = "0.4.1",
|
|
github = "ren/rare-soda" } },
|
|
{ id = "sprite_pack", name = "Sprite Pack", version = "beta", enabled = true,
|
|
manifest = { id = "sprite_pack", version = "beta" } },
|
|
}
|
|
end
|
|
|
|
local identity = { id = "my_cart", title = "My Cart", version = "0.1.0",
|
|
author = "Ren", base = "red", shell = "#FF8800",
|
|
seal = "open", summary = "Built in the launcher" }
|
|
local modOptions = {
|
|
rare_soda = { flavour = "grape", sweetness = 3, nested = { 1, 2 } },
|
|
off_mode = { unused = true },
|
|
}
|
|
|
|
local captured, unresolved = CartStore.capture(identity, rowSet(), modOptions)
|
|
T.check(captured ~= nil, "capture builds a cart: " .. tostring(unresolved))
|
|
T.eq(captured.id, "my_cart", "the captured cart keeps the identity id")
|
|
T.eq(captured.title, "My Cart", "the captured cart keeps the title")
|
|
T.eq(captured.shell, "#ff8800", "the captured shell normalises")
|
|
T.eq(captured.seal, "open", "the captured seal is the author's choice")
|
|
T.eq(captured.base, "red", "the captured base is the identity's")
|
|
T.eq(#captured.mods, 4, "every installed mod is pinned, switched on or off")
|
|
T.eq(captured.load_order[1], "hard_mode", "load order follows the row order")
|
|
T.eq(captured.load_order[2], "off_mode", "including the row that is switched off")
|
|
T.eq(captured.load_order[3], "rare_soda", "load order follows the row order")
|
|
T.eq(captured.load_order[4], "sprite_pack", "load order follows the row order")
|
|
|
|
T.eq(captured.mods[1].source, "github", "a mod with repo, version and hash pins to github")
|
|
T.eq(captured.mods[1].repo, "ren/hard-mode", "the github pin keeps the repo")
|
|
T.eq(captured.mods[1].sha256, SHA, "the github pin keeps the recorded hash")
|
|
T.eq(captured.mods[1].enabled, nil, "an enabled mod pins with no enabled field")
|
|
T.eq(CartManifest.modEnabled(captured.mods[1]), true, "and reads back as enabled")
|
|
T.eq(captured.mods[2].id, "off_mode", "the switched-off mod is pinned in place")
|
|
T.eq(captured.mods[2].enabled, false, "and is pinned switched off")
|
|
T.eq(CartManifest.modEnabled(captured.mods[2]), false, "which reads back as off")
|
|
T.eq(captured.mods[2].source, "github", "a disabled pin still resolves its source")
|
|
T.eq(captured.mods[2].sha256, SHA2, "and still carries its archive hash")
|
|
T.eq(captured.mods[2].options.unused, true,
|
|
"a disabled mod's options are captured like any other")
|
|
T.eq(captured.mods[3].source, "local", "a mod with no archive hash pins locally")
|
|
T.eq(captured.mods[3].version, "0.4.1", "the local pin keeps the installed version")
|
|
T.eq(captured.mods[3].repo, nil, "a local pin carries no repo")
|
|
T.eq(captured.mods[3].sha256, nil, "a local pin carries no hash")
|
|
T.eq(captured.mods[3].options.flavour, "grape", "the author's option values are frozen in")
|
|
T.eq(captured.mods[3].options.sweetness, 3, "every scalar option is frozen in")
|
|
T.eq(captured.mods[3].options.nested, nil, "a table option value is dropped")
|
|
T.eq(captured.mods[4].source, "local", "a mod with no repo pins locally")
|
|
T.eq(captured.mods[4].version, "0.0.0", "an unparsable version pins as 0.0.0")
|
|
T.eq(captured.mods[1].options, nil, "a mod with no options freezes none")
|
|
|
|
T.eq(#unresolved, 2, "capture reports every locally pinned mod")
|
|
T.eq(unresolved[1].id, "rare_soda", "the first unresolved mod is named")
|
|
T.eq(unresolved[1].name, "Rare Soda", "the unresolved row carries the mod name")
|
|
T.check(unresolved[1].reason:find("archive hash", 1, true) ~= nil,
|
|
"a missing hash is the reason (got " .. tostring(unresolved[1].reason) .. ")")
|
|
T.eq(unresolved[2].id, "sprite_pack", "the second unresolved mod is named")
|
|
T.check(unresolved[2].reason:find("GitHub repo", 1, true) ~= nil,
|
|
"a missing repo is a reason (got " .. tostring(unresolved[2].reason) .. ")")
|
|
T.check(unresolved[2].reason:find("semantic version", 1, true) ~= nil,
|
|
"an unpinnable version is a reason (got " .. tostring(unresolved[2].reason) .. ")")
|
|
|
|
local publishable, why = CartManifest.publishable(captured)
|
|
T.eq(publishable, false, "a captured cart with local pins cannot be published")
|
|
T.check(why:find("rare_soda", 1, true) ~= nil, "the reason names rare_soda")
|
|
T.check(why:find("sprite_pack", 1, true) ~= nil, "the reason names sprite_pack")
|
|
T.check(why:find("hard_mode", 1, true) == nil, "the reason leaves the pinned mod out")
|
|
|
|
local storeFs = memfs()
|
|
local roundTrip, roundHash = CartStore.install(CartManifest.encode(captured), storeFs)
|
|
T.check(roundTrip ~= nil, "a captured cart installs: " .. tostring(roundHash))
|
|
T.same(roundTrip, captured, "a captured cart survives the file round trip")
|
|
T.eq(roundHash, CartManifest.hash(captured), "a captured cart hashes the same on disk")
|
|
|
|
local pinned = rowSet()
|
|
pinned[3].manifest.sha256 = SHA2
|
|
pinned[4] = nil
|
|
local full, fullUnresolved = CartStore.capture(identity, pinned, modOptions)
|
|
T.check(full ~= nil, "a fully pinned capture builds a cart")
|
|
T.eq(#fullUnresolved, 0, "a fully pinned capture reports nothing unresolved")
|
|
T.eq(full.mods[3].source, "github", "a recorded hash promotes the pin to github")
|
|
T.eq(full.mods[3].sha256, SHA2, "the promoted pin uses the recorded hash")
|
|
T.eq(CartManifest.publishable(full), true, "a fully pinned cart is publishable")
|
|
|
|
local offOnly = CartStore.capture(identity, { rowSet()[2] }, modOptions)
|
|
T.check(offOnly ~= nil, "a capture of nothing but switched-off mods still builds")
|
|
T.eq(#offOnly.mods, 1, "pinning the one mod it was given")
|
|
T.eq(offOnly.mods[1].enabled, false, "switched off")
|
|
T.eq(offOnly.mods[1].options.unused, true, "with its settings kept")
|
|
|
|
local noMods, noModsErr = CartStore.capture(identity, {}, modOptions)
|
|
T.eq(noMods, nil, "a capture with no mods at all is refused")
|
|
T.check(type(noModsErr) == "string" and noModsErr:find("cart must pin", 1, true) ~= nil,
|
|
"the empty capture says why (got " .. tostring(noModsErr) .. ")")
|
|
T.eq(CartStore.capture({ id = "bad id" }, rowSet(), modOptions), nil,
|
|
"a capture with a bad identity is refused")
|
|
T.eq(CartStore.capture(nil, rowSet(), modOptions), nil,
|
|
"a capture with no identity is refused")
|
|
|
|
local PNG = CartManifest.PNG_SIGNATURE .. "\0\0\0\13IHDRa cart label"
|
|
local ART_DATA = Base64.encode(PNG)
|
|
|
|
local function artedCart()
|
|
local plain = select(2, bytesOf({ id = "art_cart", title = "Art Cart",
|
|
label = "label.png" }))
|
|
plain.labelArt = { name = "label.png", encoding = "base64", bytes = #PNG,
|
|
data = ART_DATA }
|
|
return plain
|
|
end
|
|
|
|
local artFs = memfs()
|
|
local artFixture = artedCart()
|
|
local packed = CartManifest.encode(artFixture)
|
|
local artInstalled, artHash = CartStore.install(packed, artFs)
|
|
T.check(artInstalled ~= nil, "a cart with label art installs: " .. tostring(artHash))
|
|
T.eq(artInstalled.labelArt.data, ART_DATA, "install returns the cart with its art")
|
|
T.check(artFs.files["carts/art_cart.g1rcart"]:find(ART_DATA, 1, true) ~= nil,
|
|
"install writes the art payload to the cart file")
|
|
T.same(CartStore.get("art_cart", artFs), artFixture,
|
|
"the installed cart reads back with its art")
|
|
|
|
local artless = select(2, bytesOf({ id = "art_cart", title = "Art Cart",
|
|
label = "label.png" }))
|
|
T.eq(artHash, CartManifest.hash(artless),
|
|
"the art is not part of the hash a save pins itself to")
|
|
|
|
local artExported, artExportHash = CartStore.export("art_cart", artFs)
|
|
T.eq(artExportHash, artHash, "export reports the same hash for an arted cart")
|
|
T.eq(artExported, packed, "export hands back the bytes that were packed")
|
|
local reread = CartManifest.decode(artExported)
|
|
T.same(reread, artFixture, "the exported bytes decode to the same cart and art")
|
|
T.eq(reread.labelArt.data, ART_DATA, "the exported payload is byte identical")
|
|
|
|
local sharedFs = memfs()
|
|
T.check(CartStore.install(artExported, sharedFs) ~= nil,
|
|
"an exported cart installs somewhere else")
|
|
T.same(CartStore.get("art_cart", sharedFs), artFixture,
|
|
"pack, install, export and install again leaves the cart unchanged")
|
|
|
|
local shownBytes, shownName = CartStore.labelArt("art_cart", sharedFs)
|
|
T.eq(shownBytes, PNG, "labelArt hands back the decoded PNG")
|
|
T.eq(shownName, "label.png", "labelArt hands back the art name")
|
|
T.eq(CartStore.labelArt("nothing_here", sharedFs), nil,
|
|
"labelArt refuses an unknown id")
|
|
T.eq(CartStore.labelArt("../etc/passwd", sharedFs), nil,
|
|
"labelArt refuses a climbing id")
|
|
|
|
local plainFs = memfs()
|
|
T.check(CartStore.install(CartManifest.encode(artless), plainFs) ~= nil,
|
|
"a cart with no art still installs")
|
|
T.eq(CartStore.labelArt("art_cart", plainFs), nil,
|
|
"a cart with no art has no label art")
|
|
T.same(CartStore.get("art_cart", plainFs), artless,
|
|
"a cart with no art round trips exactly as before")
|
|
|
|
local tamperedBytes = (packed:gsub("bytes = " .. #PNG, "bytes = " .. (#PNG + 1), 1))
|
|
T.neq(tamperedBytes, packed, "the tampered bundle really changed")
|
|
local tamperedFs = memfs()
|
|
local tampered, tamperedErr = CartStore.install(tamperedBytes, tamperedFs)
|
|
T.check(tampered ~= nil, "bad art never fails the install: " .. tostring(tamperedErr))
|
|
T.eq(tampered.labelArt, nil, "the bad art is dropped")
|
|
T.eq(CartStore.labelArt("art_cart", tamperedFs), nil,
|
|
"the installed cart shows no art")
|
|
T.eq(tamperedFs.files["carts/art_cart.g1rcart"], CartManifest.encode(artless),
|
|
"the bad art is not written back to disk")
|
|
T.eq(#CartStore.list(tamperedFs), 1, "the cart still lists without its art")
|
|
|
|
T.finish("cart_store")
|