mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2026-09-22 01:04:01 +00:00
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>
This commit is contained in:
co-authored by
Claude Fable 5
parent
59a0e0be13
commit
24d36db601
@@ -1,93 +1,119 @@
|
||||
--- Assignment through role methods, confirmation, and rejection
|
||||
local Env = ...
|
||||
local env = Env.new()
|
||||
local check, eq, R = env.check, env.eq, env.R
|
||||
local Roles = env.Roles
|
||||
local sd = Roles._script_data()
|
||||
local Test = Env.Test
|
||||
local test, check, eq = Test.test, Test.check, Test.eq
|
||||
|
||||
local alice = env.add_player("alice", 1)
|
||||
local bob = env.add_player("bob", 2)
|
||||
env.initialise{
|
||||
Env.assignment("alice", { 5 }),
|
||||
Env.assignment("bob", { 6 }),
|
||||
}
|
||||
Roles.set_emit_events(true)
|
||||
env.reset_log()
|
||||
--- 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
|
||||
|
||||
-- Assign with sync
|
||||
R("Moderator"):assign(bob, { by_player_name = "alice" })
|
||||
check(#env.sent == 1 and env.sent[1].channel == "exp_roles:assignment_update", "assignment is sent to the controller")
|
||||
check(eq(env.sent[1].data.assign, { 5 }) and env.sent[1].data.name == "bob", "assignment payload")
|
||||
check(R("Moderator"):has_player(bob), "the role applies before confirmation")
|
||||
check(#env.events == 1 and eq(env.events[1].data.assigned, { 5 }), "event carries the assigned role id")
|
||||
check(env.events[1].data.by_player_index == 1, "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", "assign sound")
|
||||
check(eq(sd.local_players.bob, { 5 }) and eq(sd.pending.bob, { 5 }), "held locally and pending")
|
||||
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")
|
||||
|
||||
env.reset_log()
|
||||
R("Moderator"):assign(bob)
|
||||
check(#env.sent == 0 and #env.events == 0, "assigning a held role is a no-op")
|
||||
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")
|
||||
|
||||
-- Controller confirms
|
||||
env.reset_log()
|
||||
Roles.receive_assignment_updates{ Env.assignment("bob", { 6, 5 }) }
|
||||
check(#env.events == 0 and #env.printed == 0, "a confirmation raises nothing")
|
||||
check(sd.local_players.bob == nil and sd.pending.bob == nil, "a confirmed role is no longer held locally")
|
||||
check(R("Moderator"):has_player(bob), "the role is still held after confirmation")
|
||||
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)
|
||||
|
||||
-- Unassign a synced role
|
||||
env.reset_log()
|
||||
R("Moderator"):unassign(bob, { by_player_name = "alice", silent = true })
|
||||
check(#env.sent == 1 and eq(env.sent[1].data.unassign, { 5 }), "unassignment is sent to the controller")
|
||||
check(not R("Moderator"):has_player(bob), "the role is removed locally")
|
||||
check(#env.events == 1 and eq(env.events[1].data.unassigned, { 5 }), "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", "unassign sound")
|
||||
Roles.receive_assignment_updates{ Env.assignment("bob", { 6 }) }
|
||||
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")
|
||||
|
||||
-- Rejection rolls back
|
||||
env.reset_log()
|
||||
R("Jail"):assign(alice)
|
||||
check(R("Jail"):has_player(alice), "the role applies before rejection")
|
||||
env.reset_log()
|
||||
Roles.reject_assignment{ name = "alice", role_ids = { 7 } }
|
||||
check(not R("Jail"):has_player(alice), "a 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")
|
||||
check(sd.local_players.alice == nil and sd.pending.alice == nil, "the rollback clears the local state")
|
||||
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)
|
||||
|
||||
-- Local only roles
|
||||
env.reset_log()
|
||||
R("Regular"):assign(alice, { silent = true, local_only = true })
|
||||
check(#env.sent == 0, "a local only role is not sent")
|
||||
check(R("Regular"):has_player(alice), "a local only role applies")
|
||||
check(sd.pending.alice == nil and eq(sd.local_players.alice, { 6 }), "a local only role is not pending")
|
||||
Roles.receive_assignment_updates{ Env.assignment("alice", { 5 }) }
|
||||
check(R("Regular"):has_player(alice), "a local only role survives controller updates")
|
||||
env.reset_log()
|
||||
R("Regular"):unassign(alice, { local_only = true })
|
||||
check(not R("Regular"):has_player(alice) and #env.sent == 0, "a local only role is removed without sync")
|
||||
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)
|
||||
|
||||
-- Jail priority
|
||||
env.reset_log()
|
||||
R("Jail"):assign(alice, { by_player_name = "<server>", silent = true })
|
||||
check(eq(env.names(Roles.get_player_roles(alice)), { "Jail" }), "jail suppresses every other role")
|
||||
check(R("Moderator"):has_player(alice), "a suppressed role is still held")
|
||||
check(not Roles.player_has_permission(alice, "exp_scenario.command.kill"), "a jailed player loses permissions")
|
||||
check(not Roles.player_has_permission(alice, "exp_scenario.gui.readme"), "a jailed player loses the default role")
|
||||
check(not Roles.player_outranks(alice, bob), "a jailed player no longer outranks")
|
||||
check(eq(env.events[1].data.assigned, { 7 }) and #env.events[1].data.unassigned == 0,
|
||||
"the jail event lists only the jail role")
|
||||
env.reset_log()
|
||||
R("Jail"):unassign(alice, { by_player_name = "<server>", silent = true })
|
||||
check(eq(env.sorted(env.names(Roles.get_player_roles(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")
|
||||
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")
|
||||
|
||||
-- The server can not be assigned roles
|
||||
env.reset_log()
|
||||
R("Moderator"):assign(env.server)
|
||||
check(#env.sent == 0 and #env.events == 0, "assigning to the server is a no-op")
|
||||
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")
|
||||
|
||||
return Env.results_json(env)
|
||||
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()
|
||||
|
||||
Reference in New Issue
Block a user