Files
2026-11-03 12:41:09 -05:00

123 lines
5.9 KiB
Lua
Executable File

-- Isolated tests for real claim commands, persistence, and wrapper protection.
local target = assert(arg[1], "init.lua path required")
local saved = {claims = {}, homes = {}, access = {}}
local writes, commands, particles = {}, {}, {}
local players = {}
local function copy(value)
if type(value) ~= "table" then return value end
local result = {}; for k, v in pairs(value) do result[k] = copy(v) end; return result
end
for _, name in ipairs({"owner", "housemate", "keeper", "outsider", "another-owner"}) do
local player = {name = name, pos = {x = 0, y = 10, z = 0}}
function player:get_player_name() return self.name end
function player:get_pos() return self.pos end
players[name] = player
end
core = {
registered_nodes = {},
get_modpath = function() return "/hearth" end,
get_mod_storage = function() return {} end,
get_player_by_name = function(name) return players[name] end,
register_chatcommand = function(name, def) commands[name] = def end,
is_protected = function() return false end,
record_protection_violation = function() end,
chat_send_player = function() end,
add_particle = function(spec) particles[#particles + 1] = spec end,
}
vector = {round = function(p) return {x = math.floor(p.x + 0.5), y = math.floor(p.y + 0.5), z = math.floor(p.z + 0.5)} end}
local persistence = {
load_world = function(kind) return copy(saved[kind]) end,
save_world = function(kind, value) saved[kind] = copy(value); writes[#writes + 1] = kind end,
}
local original_dofile = dofile
local loaded, character_marker = {}, {}
dofile = function(path)
local name = path:match("([^/\\]+%.lua)$")
loaded[#loaded + 1] = name
if name == "character.lua" then return function(h, api)
assert(h == hearth and api == core); return character_marker
end end
if name == "interface.lua" or name == "players.lua" then
assert(hearth.character == character_marker, "Character factory must precede UI and players")
end
if name == "model.lua" then return {location = function() return true end} end
if name == "schema.lua" then return {} end
if name == "persistence.lua" then return function() return persistence end end
if name == "access.lua" then
local source = path:match("[/\\]init%.lua$") and path:gsub("init%.lua$", "access.lua") or "/hearth/access.lua"
return original_dofile(source)
end
if name == "movement.lua" or name == "combat.lua" then return {} end
return true -- Remaining bootstrap modules are intentionally inert in this suite.
end
local total = 0
local function test(name, fn)
local ok, err = pcall(fn)
assert(ok, name .. ": " .. tostring(err))
total = total + 1
print("ok " .. total .. " - " .. name)
end
local function command(param, name)
return commands.claim.func(name or "owner", param or "")
end
original_dofile(target)
test("character factory loads once before interface and player initialization", function()
local positions = {}
for i, name in ipairs(loaded) do
assert(not positions[name], "Duplicate module load: " .. name); positions[name] = i
end
assert(hearth.character == character_marker)
assert(positions["character.lua"] < positions["interface.lua"] and positions["character.lua"] < positions["players.lua"])
assert(positions["presentation.lua"] < positions["players.lua"])
end)
test("claim creation, boundaries, and world persistence are owner-scoped", function()
local ok = command("")
assert(ok and saved.claims.owner.x == 0 and saved.claims.owner.y == 10)
assert(hearth.claim_role_at({x = 0, y = 10, z = 0}, "owner") == "owner")
assert(hearth.claim_allowed({x = 12, y = 34, z = 12}, "owner", "build"))
assert(not hearth.claim_allowed({x = 12, y = 10, z = 0}, "outsider", "build"))
local shown, message = command("show")
assert(shown and message:find("X %-12%.%.12") and #particles >= 40)
local listed = command("list")
assert(listed and #writes == 1)
end)
test("resident and storekeeper roles are least-privilege and immediately persisted", function()
assert(command("share housemate resident"))
assert(hearth.claim_allowed({x = 0, y = 10, z = 0}, "housemate", "build"))
assert(hearth.claim_allowed({x = 0, y = 10, z = 0}, "housemate", "dig"))
assert(hearth.claim_allowed({x = 0, y = 10, z = 0}, "housemate", "use"))
assert(not hearth.claim_allowed({x = 0, y = 10, z = 0}, "housemate", "combat"))
assert(command("share keeper storekeeper"))
assert(hearth.claim_allowed({x = 0, y = 10, z = 0}, "keeper", "storage"))
for _, action in ipairs({"build", "dig", "use", "combat"}) do
assert(not hearth.claim_allowed({x = 0, y = 10, z = 0}, "keeper", action), action)
end
local ok, members = command("list")
assert(ok and members:find("housemate %(resident%)") and members:find("keeper %(storekeeper%)"))
end)
test("invalid grants, outsider mutations, and revoked permissions fail closed", function()
players["another-owner"].pos = {x = 100, y = 10, z = 0}
assert(command("", "another-owner"))
for _, param in ipairs({"share bad/name resident", "share outsider admin", "share owner resident",
"share another-owner resident", "unshare bad/name"}) do
assert(not command(param), param)
end
assert(not command("share outsider resident", "outsider"))
assert(command("unshare housemate"))
assert(not hearth.claim_allowed({x = 0, y = 10, z = 0}, "housemate", "build"))
assert(not command("share " .. string.rep("a", 33) .. " resident"))
for i = 1, 15 do assert(command("share crew" .. i .. " resident")) end
assert(not command("share overflow resident"))
end)
test("claim release revokes grants and removes the owner's saved home", function()
assert(hearth.in_own_claim({x = 0, y = 10, z = 0}, "owner"))
hearth.store_home("owner", {x = 0, y = 10, z = 0})
assert(hearth.home_for("owner"))
assert(command("remove"))
assert(not saved.claims.owner and not saved.access.owner and not hearth.home_for("owner"))
assert(hearth.claim_allowed({x = 0, y = 10, z = 0}, "outsider", "build"))
end)
print("PASS " .. total .. " claim service tests")