Files
PaintMyKI5/scanner/tests/lua/run.lua
T
2026-08-12 13:36:14 -04:00

292 lines
10 KiB
Lua

local luaRoot = PROJECT_ROOT .. "/42.20/media/lua"
package.path = table.concat({
luaRoot .. "/shared/?.lua",
luaRoot .. "/shared/?/?.lua",
package.path,
}, ";")
local passed = 0
local function test(name, body)
local ok, errorMessage = pcall(body)
if not ok then error(name .. ": " .. tostring(errorMessage), 0) end
passed = passed + 1
end
local function assertNear(actual, expected)
assert(math.abs(actual - expected) < 0.0001,
"expected " .. tostring(expected) .. ", got " .. tostring(actual))
end
local function javaList(values)
return {
size = function() return #values end,
get = function(_, index) return values[index + 1] end,
}
end
PaintMyKI5 = {
VehiclePaintData = {
bucketUses = 10,
paintCans = {},
vehicles = {
["Base.TestCar"] = {
textures = {
{
skinIndex = 2,
texture = "Vehicles/TestCar_Green",
paints = {
{ item = "Base.PaintGreen", percent = 97.9, uses = 9.79 },
{ item = "Base.PaintWhite", percent = 2.1, uses = 0.21 },
},
},
},
},
},
},
}
package.loaded["PaintMyKI5/PaintMyKI5VehiclePaintData"] = true
require "PaintMyKI5/PaintRequirements"
require "PaintMyKI5/PaintInventory"
local function fakeVehicle()
return {
getScript = function()
return {
getFullName = function() return "Base.TestCar" end,
getAreaById = function() return true end,
}
end,
getSkinCount = function() return 3 end,
getSkinIndex = function() return 0 end,
getCurrentSpeedKmHour = function() return 0 end,
isRemovedFromWorld = function() return false end,
isInArea = function() return true end,
getUseablePart = function() return nil end,
}
end
test("requirements retain fractional proportional uses", function()
local requirements = PaintMyKI5.PaintRequirements.getRequirements(fakeVehicle(), 2)
assert(#requirements == 2)
assertNear(requirements[1].uses, 9.79)
assertNear(requirements[2].uses, 0.21)
assertNear(requirements[1].uses + requirements[2].uses, 10)
end)
test("invalid skin is rejected", function()
assert(PaintMyKI5.PaintRequirements.getRequirements(fakeVehicle(), 1) == nil)
end)
local syncCount = 0
local removedCount = 0
local addedCount = 0
sendItemStats = function() syncCount = syncCount + 1 end
sendRemoveItemFromContainer = function() removedCount = removedCount + 1 end
sendAddItemToContainer = function() addedCount = addedCount + 1 end
instanceItem = function() return {} end
local function fakeItem(itemType, delta, failAdd)
local item = { itemType = itemType, delta = delta, inContainer = true }
local container = {
DoRemoveItem = function(_, target) target.inContainer = false end,
AddItem = function(_, target)
if failAdd then return nil end
target.inContainer = true
return target
end,
}
item.getUseDelta = function() return 0.1 end
item.getCurrentUsesFloat = function(self) return self.delta end
item.setUsedDelta = function(self, value) self.delta = value end
item.getContainer = function() return container end
return item
end
test("inventory aggregates partial cans and consumes exact fractions", function()
local greenA = fakeItem("Base.PaintGreen", 0.5)
local greenB = fakeItem("Base.PaintGreen", 0.5)
local white = fakeItem("Base.PaintWhite", 1.0)
local byType = {
["Base.PaintGreen"] = javaList({ greenA, greenB }),
["Base.PaintWhite"] = javaList({ white }),
}
local character = {
getInventory = function()
return { getAllTypeRecurse = function(_, itemType) return byType[itemType] end }
end,
removeFromHands = function() end,
}
local requirements = {
{ item = "Base.PaintGreen", uses = 9.79 },
{ item = "Base.PaintWhite", uses = 0.21 },
}
assert(PaintMyKI5.PaintInventory.consumeRequirements(character, requirements))
assertNear(greenB.delta, 0.021)
assertNear(white.delta, 0.979)
assert(removedCount == 1 and addedCount == 1)
end)
test("a missing color prevents every mutation", function()
local green = fakeItem("Base.PaintGreen", 1.0)
local byType = {
["Base.PaintGreen"] = javaList({ green }),
["Base.PaintWhite"] = javaList({}),
}
local character = {
getInventory = function()
return { getAllTypeRecurse = function(_, itemType) return byType[itemType] end }
end,
removeFromHands = function() end,
}
assert(not PaintMyKI5.PaintInventory.consumeRequirements(character, {
{ item = "Base.PaintGreen", uses = 5 },
{ item = "Base.PaintWhite", uses = 1 },
}))
assertNear(green.delta, 1.0)
end)
test("replacement failure rolls back earlier paint", function()
local green = fakeItem("Base.PaintGreen", 1.0)
local white = fakeItem("Base.PaintWhite", 0.1, true)
local byType = {
["Base.PaintGreen"] = javaList({ green }),
["Base.PaintWhite"] = javaList({ white }),
}
local character = {
getInventory = function()
return { getAllTypeRecurse = function(_, itemType) return byType[itemType] end }
end,
removeFromHands = function() end,
}
assert(not PaintMyKI5.PaintInventory.consumeRequirements(character, {
{ item = "Base.PaintGreen", uses = 1 },
{ item = "Base.PaintWhite", uses = 1 },
}))
assertNear(green.delta, 1.0)
assert(white.inContainer)
end)
ISBaseTimedAction = {
derive = function(_, name)
local derived = { Type = name }
derived.__index = derived
return setmetatable(derived, { __index = ISBaseTimedAction })
end,
new = function(actionClass, character)
return setmetatable({ character = character }, actionClass)
end,
stop = function() end,
perform = function() end,
}
package.loaded["TimedActions/ISBaseTimedAction"] = true
isClient = function() return false end
isServer = function() return true end
dofile(luaRoot .. "/shared/PaintMyKI5/TimedActions/ISPaintMyKI5Vehicle.lua")
PaintMyKI5.PaintInventory.checkRequirements = function() return true end
local function serverCharacter()
return {
getVehicle = function() return nil end,
isTimedActionInstant = function() return false end,
}
end
local function mutableVehicle()
local vehicle = fakeVehicle()
vehicle.skin = 0
vehicle.transmitted = 0
vehicle.updated = 0
vehicle.getSkinIndex = function(self) return self.skin end
vehicle.setSkinIndex = function(self, skin) self.skin = skin end
vehicle.transmitSkinIndex = function(self) self.transmitted = self.transmitted + 1 end
vehicle.updateSkin = function(self) self.updated = self.updated + 1 end
return vehicle
end
test("server completion consumes then transmits the selected skin", function()
local vehicle = mutableVehicle()
local character = serverCharacter()
local consumed = 0
PaintMyKI5.PaintInventory.consumeRequirements = function()
consumed = consumed + 1
return true
end
local action = ISPaintMyKI5Vehicle:new(character, vehicle, 2, "Engine")
action:serverStart()
assert(action:complete())
assert(consumed == 1 and vehicle.skin == 2 and vehicle.transmitted == 1)
end)
test("server rejects forged and raced skin changes without consuming", function()
local character = serverCharacter()
local consumed = 0
PaintMyKI5.PaintInventory.consumeRequirements = function()
consumed = consumed + 1
return true
end
local forgedVehicle = mutableVehicle()
local forged = ISPaintMyKI5Vehicle:new(character, forgedVehicle, 99, "Engine")
forged:serverStart()
assert(not forged:complete())
local racedVehicle = mutableVehicle()
local raced = ISPaintMyKI5Vehicle:new(character, racedVehicle, 2, "Engine")
raced:serverStart()
racedVehicle.skin = 1
assert(not raced:complete())
assert(consumed == 0)
end)
test("failed skin or inventory commit restores state without losing paint", function()
local character = serverCharacter()
local consumed = 0
PaintMyKI5.PaintInventory.consumeRequirements = function()
consumed = consumed + 1
return false
end
local vehicle = mutableVehicle()
local action = ISPaintMyKI5Vehicle:new(character, vehicle, 2, "Engine")
action:serverStart()
assert(not action:complete())
assert(vehicle.skin == 0 and vehicle.transmitted == 2 and consumed == 1)
local broken = mutableVehicle()
broken.setSkinIndex = function(self, skin)
if skin == 2 then error("skin mutation failed") end
self.skin = skin
end
consumed = 0
local brokenAction = ISPaintMyKI5Vehicle:new(character, broken, 2, "Engine")
brokenAction:serverStart()
assert(not brokenAction:complete())
assert(broken.skin == 0 and consumed == 0)
end)
test("singleplayer completion updates the local skin", function()
isServer = function() return false end
local vehicle = mutableVehicle()
PaintMyKI5.PaintInventory.consumeRequirements = function() return true end
local action = ISPaintMyKI5Vehicle:new(serverCharacter(), vehicle, 2, "Engine")
action:serverStart()
assert(action:complete())
assert(vehicle.skin == 2 and vehicle.updated == 1 and vehicle.transmitted == 0)
isServer = function() return true end
end)
test("client completion never mutates inventory or the vehicle", function()
isClient = function() return true end
local vehicle = mutableVehicle()
local consumed = 0
PaintMyKI5.PaintInventory.consumeRequirements = function()
consumed = consumed + 1
return true
end
local action = ISPaintMyKI5Vehicle:new(serverCharacter(), vehicle, 1, "Engine")
assert(action:complete())
assert(consumed == 0 and vehicle.skin == 0 and vehicle.transmitted == 0)
isClient = function() return false end
end)
print("Lua tests passed: " .. passed)