package items import ( "testing" "wuziqi-server/core" "github.com/heroiclabs/nakama-common/runtime" ) // --- Mocks --- type MockLogger struct { runtime.Logger // Embed interface to skip implementing all methods } func (m *MockLogger) Info(format string, v ...interface{}) {} func (m *MockLogger) Warn(format string, v ...interface{}) {} func (m *MockLogger) Error(format string, v ...interface{}) {} func (m *MockLogger) Debug(format string, v ...interface{}) {} type MockDispatcher struct { runtime.MatchDispatcher } func (m *MockDispatcher) BroadcastMessage(opCode int64, data []byte, presences []runtime.Presence, sender runtime.Presence, reliable bool) error { return nil } type MockGameLogic struct { LastEvent *core.GameEvent HealCalled bool DamageCalls []struct { TargetID string Amount int } } func (m *MockGameLogic) ApplyDamage(state *core.GameState, target *core.Player, amount int, isItemEffect bool) { m.DamageCalls = append(m.DamageCalls, struct { TargetID string Amount int }{TargetID: target.UserID, Amount: amount}) // Simulate basic damage for test state target.HP -= amount } func (m *MockGameLogic) HealPlayer(player *core.Player, amount int) { m.HealCalled = true player.HP += amount if player.HP > player.MaxHP { player.HP = player.MaxHP } } func (m *MockGameLogic) GetRandomAliveTarget(state *core.GameState, excludeID string) *core.Player { for _, p := range state.Players { if p.UserID != excludeID && p.HP > 0 { return p } } return nil } func (m *MockGameLogic) BroadcastEvent(event core.GameEvent) { m.LastEvent = &event } func (m *MockGameLogic) SendPrivateEvent(targetID string, event core.GameEvent) { m.LastEvent = &event } // --- Helper --- func createTestContext(logic GameLogic) ItemContext { return ItemContext{ Logger: &MockLogger{}, Dispatcher: &MockDispatcher{}, Logic: logic, } } func createTestState() (*core.GameState, *core.Player) { p1 := &core.Player{UserID: "p1", Username: "Player1", HP: 3, MaxHP: 4, Character: "dog", RevealedCells: make(map[int]string)} p2 := &core.Player{UserID: "p2", Username: "Player2", HP: 4, MaxHP: 4, Character: "cat", RevealedCells: make(map[int]string)} grid := make([]*core.GridCell, 100) for i := range grid { grid[i] = &core.GridCell{Type: "empty", Revealed: false} } state := &core.GameState{ Players: map[string]*core.Player{ "p1": p1, "p2": p2, }, Grid: grid, } return state, p1 } // --- Tests --- func TestMedkit(t *testing.T) { state, user := createTestState() mockLogic := &MockGameLogic{} ctx := createTestContext(mockLogic) strategy := &MedkitStrategy{} // Setup: Poison user user.Poisoned = true user.PoisonSteps = 2 user.HP = 2 // Test Normal Use consumed := strategy.Use(state, user, ctx) if !consumed { t.Error("Medkit should be consumed") } if user.Poisoned { t.Error("Medkit should cure poison") } if user.PoisonSteps != 0 { t.Error("Medkit should reset poison steps") } if user.HP != 3 { t.Errorf("Medkit should heal 1 HP, got %d", user.HP) } if mockLogic.LastEvent == nil || mockLogic.LastEvent.ItemID != "medkit" { t.Error("Medkit should broadcast event") } // Test Elephant Refusal user.Character = "elephant" consumed = strategy.Use(state, user, ctx) if consumed { t.Error("Medkit should NOT be consumed by Elephant") } } func TestBombTimer(t *testing.T) { state, user := createTestState() mockLogic := &MockGameLogic{} ctx := createTestContext(mockLogic) strategy := &BombTimerStrategy{} consumed := strategy.Use(state, user, ctx) if !consumed { t.Error("BombTimer should be consumed") } if user.TimeBombTurns != 3 { t.Errorf("BombTimer should set turns to 3, got %d", user.TimeBombTurns) } } func TestPoison(t *testing.T) { state, user := createTestState() mockLogic := &MockGameLogic{} ctx := createTestContext(mockLogic) strategy := &PoisonStrategy{} // Target is p2 (available) consumed := strategy.Use(state, user, ctx) if !consumed { t.Error("Poison should be consumed") } target := state.Players["p2"] if !target.Poisoned { t.Error("Target should be poisoned") } // Test Sloth Resistance target.Character = "sloth" target.Poisoned = false strategy.Use(state, user, ctx) if target.Poisoned { t.Error("Sloth should resist poison") } } func TestShield(t *testing.T) { state, user := createTestState() mockLogic := &MockGameLogic{} ctx := createTestContext(mockLogic) strategy := &ShieldStrategy{} if user.Shield { t.Error("User should not have shield initially") } consumed := strategy.Use(state, user, ctx) if !consumed { t.Error("Shield should be consumed") } if !user.Shield { t.Error("User should have shield") } } func TestSkip(t *testing.T) { state, user := createTestState() mockLogic := &MockGameLogic{} ctx := createTestContext(mockLogic) strategy := &SkipStrategy{} consumed := strategy.Use(state, user, ctx) if !consumed { t.Error("Skip should be consumed") } if !user.SkipTurn { t.Error("User should skip turn") } if !user.Shield { t.Error("Skip card should grant shield") } // Test Elephant user.Character = "elephant" user.SkipTurn = false consumed = strategy.Use(state, user, ctx) if !consumed { t.Error("Elephant should now be able to use skip") } if !user.SkipTurn { t.Error("Elephant skip turn should be true") } } func TestMagnifier(t *testing.T) { state, user := createTestState() mockLogic := &MockGameLogic{} ctx := createTestContext(mockLogic) strategy := &MagnifierStrategy{} // Make sure grid has items to reveal state.Grid[5].Type = "bomb" consumed := strategy.Use(state, user, ctx) if !consumed { t.Error("Magnifier should be consumed") } if len(user.RevealedCells) != 1 { t.Errorf("Should reveal 1 cell, got %d", len(user.RevealedCells)) } } func TestKnife(t *testing.T) { state, user := createTestState() mockLogic := &MockGameLogic{} ctx := createTestContext(mockLogic) strategy := &KnifeStrategy{} // Case 1: Normal Knife (1 dmg to random target) consumed := strategy.Use(state, user, ctx) if !consumed { t.Error("Knife should be consumed") } if len(mockLogic.DamageCalls) != 1 { t.Errorf("Knife should cause 1 damage call, got %d", len(mockLogic.DamageCalls)) } if mockLogic.DamageCalls[0].TargetID != "p2" || mockLogic.DamageCalls[0].Amount != 1 { t.Error("Knife should deal 1 dmg to p2") } // Case 2: Tiger Knife (2 dmg AOE) mockLogic.DamageCalls = nil // reset user.Character = "tiger" // Add a third player to verify AOE state.Players["p3"] = &core.Player{UserID: "p3", HP: 4, MaxHP: 4} strategy.Use(state, user, ctx) if len(mockLogic.DamageCalls) != 2 { t.Errorf("Tiger Knife should hit all enemies (2), got %d", len(mockLogic.DamageCalls)) } if mockLogic.DamageCalls[0].Amount != 2 { t.Error("Tiger Knife should deal 2 dmg") } } func TestRevive(t *testing.T) { state, user := createTestState() mockLogic := &MockGameLogic{} ctx := createTestContext(mockLogic) strategy := &ReviveStrategy{} consumed := strategy.Use(state, user, ctx) if !consumed { t.Error("Revive should be consumed") } if !user.Revive { t.Error("User should have Revive flag") } // Elephant user.Character = "elephant" user.Revive = false consumed = strategy.Use(state, user, ctx) if consumed { t.Error("Elephant should check privilege") } } func TestLightning(t *testing.T) { state, user := createTestState() mockLogic := &MockGameLogic{} ctx := createTestContext(mockLogic) strategy := &LightningStrategy{} strategy.Use(state, user, ctx) // Should hit ALL players (including self per logic? "Lightning hits ALL players") // Let's check effects.go: "for _, p := range state.Players { ApplyDamage }" // Yes, hits everyone. if len(mockLogic.DamageCalls) != 2 { t.Errorf("Lightning should hit 2 players, got %d", len(mockLogic.DamageCalls)) } } func TestChest(t *testing.T) { state, user := createTestState() mockLogic := &MockGameLogic{} ctx := createTestContext(mockLogic) strategy := &ChestStrategy{} strategy.Use(state, user, ctx) if user.ChestCount != 1 { t.Error("Chest count should increment") } } func TestCurse(t *testing.T) { state, user := createTestState() mockLogic := &MockGameLogic{} ctx := createTestContext(mockLogic) strategy := &CurseStrategy{} strategy.Use(state, user, ctx) if !user.Curse { t.Error("User should be cursed") } }