Files
factorio-scenario-ExpCluster/exp_roles/test/lua/assignment.lua
T
bbassieandClaude Fable 5 24d36db601 Address review on the test harness
- The generic parts move to test/ in the repository root so other plugins can
  reuse them: the factorio and clusterio stubs, the test framework, the
  fengari runner, and clusterio's testMatrix and round trip helpers. A plugin
  composes them from its own env.lua, which adds its stubs and fixtures.
- Tests are declared with Test.test(name, fn) and every test function
  receives a fresh environment, so nothing carries over between them. A test
  which errors is reported as a failure rather than aborting the file.
- Every stub raises on properties it does not implement, which mirrors the
  game api. game.player is the one property allowed to read as nil.
- Test.deep_eq compares tables recursively with keys checked from both sides.
- The message records round trip through the same testMatrix and
  testRoundTripJsonSerialisable helpers the clusterio tests use, covering
  every optional field combination of the records, events and requests.
- controller.test.js and instance.test.js cover the node side of the plugin
  against faked controller and instance internals: property creation and
  sweeping, record building, broadcasts, subscription replay, assignment
  validation, auto assignment and its blocking role, seeding, the initialise
  payload, sync mode gating, and the rejection rollback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 16:45:51 +00:00

120 lines
5.9 KiB
Lua

--- Assignment through role methods, confirmation, and rejection
local Env = ...
local Test = Env.Test
local test, check, eq = Test.test, Test.check, Test.eq
--- alice is a moderator and bob a regular, with changes sent to the controller
local function setup(env)
local players = {
alice = env.add_player("alice", 1),
bob = env.add_player("bob", 2),
}
env.initialise{
Env.assignment("alice", { 5 }),
Env.assignment("bob", { 6 }),
}
env.Roles.set_emit_events(true)
env.reset_log()
return players
end
test("assign applies locally and is sent to the controller", function(env)
local players = setup(env)
env.R("Moderator"):assign(players.bob, { by_player_name = "alice" })
check(#env.sent == 1 and env.sent[1].channel == "exp_roles:assignment_update", "the assignment is sent")
check(eq(env.sent[1].data.assign, { 5 }) and env.sent[1].data.name == "bob", "the payload names the role and player")
check(env.R("Moderator"):has_player(players.bob), "the role applies before confirmation")
check(#env.events == 1 and eq(env.events[1].data.assigned, { 5 }), "the event carries the assigned role id")
check(env.events[1].data.by_player_index == 1, "the event carries who made the change")
check(#env.printed == 1 and env.printed[1][1] == "exp-roles.game-message-assign", "the change is announced")
check(#env.sounds == 1 and env.sounds[1] == "bob:utility/achievement_unlocked", "the assign sound plays")
local sd = env.Roles._script_data()
check(eq(sd.local_players.bob, { 5 }) and eq(sd.pending.bob, { 5 }), "the role is held locally and pending")
env.reset_log()
env.R("Moderator"):assign(players.bob)
check(#env.sent == 0 and #env.events == 0, "assigning a held role is a no-op")
end)
test("a confirmation releases the local hold", function(env)
local players = setup(env)
env.R("Moderator"):assign(players.bob)
env.reset_log()
env.Roles.receive_assignment_updates{ Env.assignment("bob", { 6, 5 }) }
check(#env.events == 0 and #env.printed == 0, "a confirmation raises nothing")
local sd = env.Roles._script_data()
check(sd.local_players.bob == nil and sd.pending.bob == nil, "the role is no longer held locally")
check(env.R("Moderator"):has_player(players.bob), "the role is still held after confirmation")
end)
test("unassign a synced role", function(env)
local players = setup(env)
env.R("Regular"):unassign(players.bob, { by_player_name = "alice", silent = true })
check(#env.sent == 1 and eq(env.sent[1].data.unassign, { 6 }), "the unassignment is sent")
check(not env.R("Regular"):has_player(players.bob), "the role is removed locally")
check(#env.events == 1 and eq(env.events[1].data.unassigned, { 6 }), "the event carries the unassigned role id")
check(#env.printed == 0, "silent suppresses the announcement")
check(#env.sounds == 1 and env.sounds[1] == "bob:utility/game_lost", "the unassign sound plays")
end)
test("a rejection rolls the assignment back", function(env)
local players = setup(env)
env.R("Jail"):assign(players.alice)
check(env.R("Jail"):has_player(players.alice), "the role applies before rejection")
env.reset_log()
env.Roles.reject_assignment{ name = "alice", role_ids = { 7 } }
check(not env.R("Jail"):has_player(players.alice), "the rejected role is rolled back")
check(#env.sent == 0, "the rollback is not sent to the controller")
check(#env.events == 1 and eq(env.events[1].data.unassigned, { 7 }), "the rollback raises the event")
local sd = env.Roles._script_data()
check(sd.local_players.alice == nil and sd.pending.alice == nil, "the rollback clears the local state")
end)
test("local only roles never reach the controller", function(env)
local players = setup(env)
env.R("Regular"):assign(players.alice, { silent = true, local_only = true })
check(#env.sent == 0, "a local only role is not sent")
check(env.R("Regular"):has_player(players.alice), "a local only role applies")
local sd = env.Roles._script_data()
check(sd.pending.alice == nil and eq(sd.local_players.alice, { 6 }), "a local only role is not pending")
env.Roles.receive_assignment_updates{ Env.assignment("alice", { 5 }) }
check(env.R("Regular"):has_player(players.alice), "a local only role survives controller updates")
env.reset_log()
env.R("Regular"):unassign(players.alice, { local_only = true })
check(not env.R("Regular"):has_player(players.alice) and #env.sent == 0, "removed without sync")
end)
test("jail suppresses every other role", function(env)
local players = setup(env)
env.R("Jail"):assign(players.alice, { by_player_name = "<server>", silent = true })
check(eq(Test.names(env.Roles.get_player_roles(players.alice)), { "Jail" }), "only the jail role applies")
check(env.R("Moderator"):has_player(players.alice), "a suppressed role is still held")
check(not env.Roles.player_has_permission(players.alice, "exp_scenario.command.kill"), "permissions are lost")
check(not env.Roles.player_has_permission(players.alice, "exp_scenario.gui.readme"), "the default role is lost")
check(not env.Roles.player_outranks(players.alice, players.bob), "a jailed player no longer outranks")
check(eq(env.events[1].data.assigned, { 7 }) and #env.events[1].data.unassigned == 0,
"the event lists only the jail role")
env.reset_log()
env.R("Jail"):unassign(players.alice, { by_player_name = "<server>", silent = true })
check(eq(Test.sorted(Test.names(env.Roles.get_player_roles(players.alice))), { "Moderator", "Player" }),
"unjail restores the roles")
check(#env.events[1].data.assigned == 0 and eq(env.events[1].data.unassigned, { 7 }),
"the unjail event lists only the jail role")
end)
test("the server can not be assigned roles", function(env)
setup(env)
env.R("Moderator"):assign(env.server)
check(#env.sent == 0 and #env.events == 0, "assigning to the server is a no-op")
end)
return Env.finish()