package main
import "testing"
// TestNewStartingSystems_layout verifies the 5-entry slice is indexed by
// int(RoomRole) and that each entry carries the correct role and cap.
func TestNewStartingSystems_layout(t *testing.T) {
sys := NewStartingSystems()
if len(sys) != 5 {
t.Fatalf("expected 5 systems, got %d", len(sys))
}
type want struct {
role RoomRole
maxLevel int
}
cases := []want{
{RolePilot, 1},
{RoleWeapons, 4},
{RoleShields, 4},
{RoleMedBay, 2},
{RoleEngines, 4},
}
for _, w := range cases {
idx := int(w.role)
s := sys[idx]
if s.Role != w.role {
t.Errorf("sys[%d].Role = %v, want %v", idx, s.Role, w.role)
}
if s.MaxLevel != w.maxLevel {
t.Errorf("sys[%d].MaxLevel = %d, want %d", idx, s.MaxLevel, w.maxLevel)
}
if s.PowerLevel != 0 {
t.Errorf("sys[%d].PowerLevel = %d, want 0", idx, s.PowerLevel)
}
}
}
// TestReactorUsed_zero verifies that fresh systems report zero reactor usage.
func TestReactorUsed_zero(t *testing.T) {
sys := NewStartingSystems()
if got := reactorUsed(sys); got != 0 {
t.Fatalf("reactorUsed on fresh systems = %d, want 0", got)
}
}
// TestReactorUsed_sums verifies that reactorUsed returns the sum of all PowerLevels.
func TestReactorUsed_sums(t *testing.T) {
sys := NewStartingSystems()
// Directly set some levels for this test only (bypassing addPower to isolate the sum logic).
sys[int(RolePilot)].PowerLevel = 1
sys[int(RoleWeapons)].PowerLevel = 3
sys[int(RoleEngines)].PowerLevel = 2
want := 1 + 3 + 2
if got := reactorUsed(sys); got != want {
t.Fatalf("reactorUsed = %d, want %d", got, want)
}
}
// TestAddPower_happy verifies a normal add when below cap and reactor has headroom.
func TestAddPower_happy(t *testing.T) {
sys := NewStartingSystems()
ok := addPower(sys, RoleWeapons, ReactorCap)
if !ok {
t.Fatal("addPower returned false, expected true")
}
if sys[int(RoleWeapons)].PowerLevel != 1 {
t.Fatalf("PowerLevel = %d, want 1", sys[int(RoleWeapons)].PowerLevel)
}
}
// TestAddPower_systemAtCap verifies that adding beyond MaxLevel is rejected.
func TestAddPower_systemAtCap(t *testing.T) {
sys := NewStartingSystems()
// Pilot cap is 1; add once to hit the cap.
if !addPower(sys, RolePilot, ReactorCap) {
t.Fatal("first addPower on Pilot should succeed")
}
if sys[int(RolePilot)].PowerLevel != 1 {
t.Fatalf("expected level 1 after first add, got %d", sys[int(RolePilot)].PowerLevel)
}
// Now at cap — must be rejected.
ok := addPower(sys, RolePilot, ReactorCap)
if ok {
t.Fatal("addPower at cap returned true, expected false")
}
if sys[int(RolePilot)].PowerLevel != 1 {
t.Fatalf("PowerLevel changed after rejected add: got %d", sys[int(RolePilot)].PowerLevel)
}
}
// TestAddPower_reactorFull verifies that adding power when reactor is exhausted is rejected.
func TestAddPower_reactorFull(t *testing.T) {
sys := NewStartingSystems()
// Fill reactor to cap using Weapons (cap 4) and Engines (cap 4).
for i := 0; i < 4; i++ {
if !addPower(sys, RoleWeapons, ReactorCap) {
t.Fatalf("addPower Weapons step %d failed unexpectedly", i)
}
}
for i := 0; i < 4; i++ {
if !addPower(sys, RoleEngines, ReactorCap) {
t.Fatalf("addPower Engines step %d failed unexpectedly", i)
}
}
if reactorUsed(sys) != ReactorCap {
t.Fatalf("expected reactor full (%d), got %d", ReactorCap, reactorUsed(sys))
}
// Shields is below cap but reactor is full — must be rejected.
ok := addPower(sys, RoleShields, ReactorCap)
if ok {
t.Fatal("addPower with full reactor returned true, expected false")
}
if sys[int(RoleShields)].PowerLevel != 0 {
t.Fatalf("PowerLevel changed on rejected add: got %d", sys[int(RoleShields)].PowerLevel)
}
}
// TestAddPower_invariantsHold verifies IV3 and IV4 across all outcomes of addPower.
func TestAddPower_invariantsHold(t *testing.T) {
checkInvariants := func(sys []System) {
t.Helper()
used := reactorUsed(sys)
if used > ReactorCap {
t.Errorf("IV3 violated: reactorUsed %d > ReactorCap %d", used, ReactorCap)
}
for i, s := range sys {
if s.PowerLevel < 0 || s.PowerLevel > s.MaxLevel {
t.Errorf("IV4 violated: sys[%d].PowerLevel %d not in [0, %d]", i, s.PowerLevel, s.MaxLevel)
}
}
}
roles := []RoomRole{RolePilot, RoleWeapons, RoleShields, RoleMedBay, RoleEngines}
sys := NewStartingSystems()
// Repeatedly try to add power to every system in a round-robin until the
// reactor is full, then one more round of attempts (all should fail).
for round := 0; round < 10; round++ {
for _, r := range roles {
addPower(sys, r, ReactorCap) //nolint:errcheck — we only care about invariants
checkInvariants(sys)
}
}
}
// TestRemovePower_happy verifies that removing from a powered system decrements level.
func TestRemovePower_happy(t *testing.T) {
sys := NewStartingSystems()
if !addPower(sys, RoleShields, ReactorCap) {
t.Fatal("addPower Shields failed")
}
ok := removePower(sys, RoleShields)
if !ok {
t.Fatal("removePower returned false, expected true")
}
if sys[int(RoleShields)].PowerLevel != 0 {
t.Fatalf("PowerLevel = %d, want 0", sys[int(RoleShields)].PowerLevel)
}
}
// TestRemovePower_zeroLevel verifies that removing from an unpowered system is rejected.
func TestRemovePower_zeroLevel(t *testing.T) {
sys := NewStartingSystems()
ok := removePower(sys, RoleEngines)
if ok {
t.Fatal("removePower at level 0 returned true, expected false")
}
if sys[int(RoleEngines)].PowerLevel != 0 {
t.Fatalf("PowerLevel changed after rejected remove: got %d", sys[int(RoleEngines)].PowerLevel)
}
}