Files
factorio-scenario-ExpCluster/exp_roles/test/lua/assignment.lua
T
bbassieandClaude Fable 5 3218351db6 Address review on the suite and instance tests
- The instance tests run against a real Instance with a real InstanceConfig,
  matching the controller tests: the spies are sendTo and the server, which
  record what leaves the instance, so sendRcon passes through the real script
  command gate. The plugin's config fields are registered so the sync mode is
  a real field.
- Shallow eq is gone and eq compares recursively; the suite is created with
  Framework.suite(extend_env), which hands the extension a fresh set of stubs
  per test, so the plugin's env.lua is only the extension.
- add_player assigns continuous indexes itself.
- The lua fixtures are referenced by role name: assignments take names, and
  ids in expectations come from the role object. The name lookup in env.R
  uses a fixture index rather than searching the roles.
- Test names format functions as name() and methods as .name().
- Branch coverage filled in: sort tie breaking, the highest role assertion
  when a player has no roles, has all for the server, the by player default
  from game.player, deleted assignment records on initialise, set_emit_events
  with no argument, printing to a role with no holders, role property update
  validation, the assignment subscription replay, unassignment over the ipc,
  and a refused removal having nothing to roll back.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 21:11:58 +00:00

162 lines
7.0 KiB
Lua

--- Tests for the assignment methods of module/control.lua
local Suite = ... --- The suite this file adds its tests to, see test/lua/framework.lua
local test, check, eq = Suite.test, Suite.check, Suite.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"),
bob = env.add_player("bob"),
}
env.initialise{
env.assignment("alice", { "Moderator" }),
env.assignment("bob", { "Regular" }),
}
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)
local moderator = env.R("Moderator")
moderator:assign(players.bob, { by_player_name = "alice" })
eq(env.sent, {
{ channel = "exp_roles:assignment_update", data = { name = "bob", assign = { moderator.id } } },
}, "the assignment is sent to the controller")
check(moderator:has_player(players.bob), "the role applies before confirmation")
eq(env.events, {
{
name = env.Roles.events.on_player_roles_changed,
tick = 1,
player_index = players.bob.index,
by_player_index = players.alice.index,
assigned = { moderator.id },
unassigned = {},
},
}, "the event carries the change")
check(#env.printed == 1 and env.printed[1][1] == "exp-roles.game-message-assign", "the change is announced")
eq(env.sounds, { "bob:utility/achievement_unlocked" }, "the assign sound plays")
local sd = env.Roles._script_data()
eq(sd.local_players.bob, { moderator.id }, "the role is held locally")
eq(sd.pending.bob, { moderator.id }, "the role is pending")
env.reset_log()
moderator:assign(players.bob)
check(#env.sent == 0 and #env.events == 0, "assigning a held role is a no-op")
end)
test(".assign() defaults the by player to game.player", function(env)
local players = setup(env)
game.player = players.alice
env.R("Jail"):assign(players.bob, { silent = true })
check(env.events[1].by_player_index == players.alice.index, "the acting player is game.player")
end)
test("receive_assignment_updates() releases the local hold once confirmed", function(env)
local players = setup(env)
env.R("Moderator"):assign(players.bob)
env.reset_log()
env.Roles.receive_assignment_updates{ env.assignment("bob", { "Regular", "Moderator" }) }
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() removes a synced role", function(env)
local players = setup(env)
local regular = env.R("Regular")
regular:unassign(players.bob, { by_player_name = "alice", silent = true })
eq(env.sent, {
{ channel = "exp_roles:assignment_update", data = { name = "bob", unassign = { regular.id } } },
}, "the unassignment is sent to the controller")
check(not regular:has_player(players.bob), "the role is removed locally")
eq(env.events, {
{
name = env.Roles.events.on_player_roles_changed,
tick = 1,
player_index = players.bob.index,
by_player_index = players.alice.index,
assigned = {},
unassigned = { regular.id },
},
}, "the event carries the change")
check(#env.printed == 0, "silent suppresses the announcement")
eq(env.sounds, { "bob:utility/game_lost" }, "the unassign sound plays")
end)
test("reject_assignment() rolls the assignment back", function(env)
local players = setup(env)
local jail = env.R("Jail")
jail:assign(players.alice)
check(jail:has_player(players.alice), "the role applies before rejection")
env.reset_log()
env.Roles.reject_assignment{ name = "alice", role_ids = { jail.id } }
check(not jail:has_player(players.alice), "the rejected role is rolled back")
check(#env.sent == 0, "the rollback is not sent to the controller")
eq(env.events, {
{
name = env.Roles.events.on_player_roles_changed,
tick = 1,
player_index = players.alice.index,
by_player_index = 0,
assigned = {},
unassigned = { jail.id },
},
}, "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(".assign() with local_only never reaches the controller", function(env)
local players = setup(env)
local regular = env.R("Regular")
regular:assign(players.alice, { silent = true, local_only = true })
check(#env.sent == 0, "the assignment is not sent")
check(regular:has_player(players.alice), "the role applies")
local sd = env.Roles._script_data()
check(sd.pending.alice == nil, "the role is not pending")
eq(sd.local_players.alice, { regular.id }, "the role is held locally")
env.Roles.receive_assignment_updates{ env.assignment("alice", { "Moderator" }) }
check(regular:has_player(players.alice), "the role survives controller updates")
env.reset_log()
regular:unassign(players.alice, { local_only = true })
check(not regular:has_player(players.alice) and #env.sent == 0, "removed without sync")
end)
test(".assign() of a higher priority role suppresses the rest", function(env)
local players = setup(env)
local jail = env.R("Jail")
jail:assign(players.alice, { by_player_name = "<server>", silent = true })
eq(Suite.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")
eq(env.events[1].assigned, { jail.id }, "the event lists the jail role")
check(#env.events[1].unassigned == 0, "the suppressed roles are not listed as unassigned")
env.reset_log()
jail:unassign(players.alice, { by_player_name = "<server>", silent = true })
eq(Suite.sorted(Suite.names(env.Roles.get_player_roles(players.alice))), { "Moderator", "Player" },
"unjail restores the roles")
check(#env.events[1].assigned == 0, "the restored roles are not listed as assigned")
eq(env.events[1].unassigned, { jail.id }, "the unjail event lists the jail role")
end)
test(".assign() ignores the server", 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 Suite.run()