From 57e56ae7a4cfa5c9004b776e97515e9052013db4 Mon Sep 17 00:00:00 2001 From: tolelom <98kimsungmin@naver.com> Date: Tue, 24 Mar 2026 15:07:51 +0900 Subject: [PATCH] =?UTF-8?q?test:=20lobby,=20session,=20store=20tests=20?= =?UTF-8?q?=E2=80=94=20deep=20copy,=20logs,=20inventory,=20stats?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- game/lobby_test.go | 40 ++++++++++++++++++++ game/session_test.go | 88 ++++++++++++++++++++++++++++++++++++++++++++ store/db_test.go | 38 +++++++++++++++++++ 3 files changed, 166 insertions(+) diff --git a/game/lobby_test.go b/game/lobby_test.go index 1ec2fbd..64543fa 100644 --- a/game/lobby_test.go +++ b/game/lobby_test.go @@ -57,3 +57,43 @@ func TestJoinRoomFull(t *testing.T) { t.Error("Should reject 5th player") } } + +func TestSetPlayerClass(t *testing.T) { + l := NewLobby() + code := l.CreateRoom("Test") + l.JoinRoom(code, "Alice", "fp-alice") + l.SetPlayerClass(code, "fp-alice", "Warrior") + room := l.GetRoom(code) + if room.Players[0].Class != "Warrior" { + t.Errorf("expected class Warrior, got %s", room.Players[0].Class) + } +} + +func TestAllReady(t *testing.T) { + l := NewLobby() + code := l.CreateRoom("Test") + l.JoinRoom(code, "Alice", "fp-alice") + l.JoinRoom(code, "Bob", "fp-bob") + + if l.AllReady(code) { + t.Error("no one ready yet, should return false") + } + + l.SetPlayerReady(code, "fp-alice", true) + if l.AllReady(code) { + t.Error("only Alice ready, should return false") + } + + l.SetPlayerReady(code, "fp-bob", true) + if !l.AllReady(code) { + t.Error("both ready, should return true") + } +} + +func TestAllReadyEmptyRoom(t *testing.T) { + l := NewLobby() + code := l.CreateRoom("Test") + if l.AllReady(code) { + t.Error("empty room should not be all ready") + } +} diff --git a/game/session_test.go b/game/session_test.go index 380a135..952053a 100644 --- a/game/session_test.go +++ b/game/session_test.go @@ -60,3 +60,91 @@ func TestSessionTurnTimeout(t *testing.T) { t.Error("Turn did not timeout within 7 seconds") } } + +func TestRevealNextLog(t *testing.T) { + s := NewGameSession() + + // No logs to reveal + if s.RevealNextLog() { + t.Error("should return false when no pending logs") + } + + // Manually add pending logs + s.mu.Lock() + s.state.PendingLogs = []string{"msg1", "msg2", "msg3"} + s.mu.Unlock() + + if !s.RevealNextLog() { + t.Error("should return true when log revealed") + } + + st := s.GetState() + if len(st.CombatLog) != 1 || st.CombatLog[0] != "msg1" { + t.Errorf("expected [msg1], got %v", st.CombatLog) + } + if len(st.PendingLogs) != 2 { + t.Errorf("expected 2 pending, got %d", len(st.PendingLogs)) + } + + // Reveal remaining + s.RevealNextLog() + s.RevealNextLog() + if s.RevealNextLog() { + t.Error("should return false after all revealed") + } +} + +func TestDeepCopyIndependence(t *testing.T) { + s := NewGameSession() + p := entity.NewPlayer("Test", entity.ClassWarrior) + p.Fingerprint = "fp-test" + p.Inventory = append(p.Inventory, entity.Item{Name: "Sword", Type: entity.ItemWeapon, Bonus: 5}) + s.AddPlayer(p) + + state := s.GetState() + + // Mutate the copy + state.Players[0].HP = 999 + state.Players[0].Inventory = append(state.Players[0].Inventory, entity.Item{Name: "Shield"}) + + // Original should be unchanged + origState := s.GetState() + if origState.Players[0].HP == 999 { + t.Error("deep copy failed: HP mutation leaked to original") + } + if len(origState.Players[0].Inventory) != 1 { + t.Error("deep copy failed: inventory mutation leaked to original") + } +} + +func TestBuyItemInventoryFull(t *testing.T) { + s := NewGameSession() + p := entity.NewPlayer("Buyer", entity.ClassWarrior) + p.Fingerprint = "fp-buyer" + p.Gold = 1000 + // Fill inventory to 10 + for i := 0; i < 10; i++ { + p.Inventory = append(p.Inventory, entity.Item{Name: "Junk"}) + } + s.AddPlayer(p) + + s.mu.Lock() + s.state.Phase = PhaseShop + s.state.ShopItems = []entity.Item{ + {Name: "Potion", Type: entity.ItemConsumable, Bonus: 30, Price: 10}, + } + s.mu.Unlock() + + if s.BuyItem("fp-buyer", 0) { + t.Error("should not buy when inventory is full") + } +} + +func TestSendChat(t *testing.T) { + s := NewGameSession() + s.SendChat("Alice", "hello") + st := s.GetState() + if len(st.CombatLog) != 1 || st.CombatLog[0] != "[Alice] hello" { + t.Errorf("expected chat log, got %v", st.CombatLog) + } +} diff --git a/store/db_test.go b/store/db_test.go index 0b49937..3025074 100644 --- a/store/db_test.go +++ b/store/db_test.go @@ -53,3 +53,41 @@ func TestRanking(t *testing.T) { t.Errorf("Top player: got %q, want Charlie", rankings[0].Player) } } + +func TestGetStats(t *testing.T) { + dir := t.TempDir() + db, err := Open(dir + "/test.db") + if err != nil { + t.Fatal(err) + } + defer db.Close() + + // Save some runs + db.SaveRun("Alice", 5, 100) + db.SaveRun("Alice", 10, 250) + db.SaveRun("Alice", 20, 500) // victory (floor >= 20) + db.SaveRun("Bob", 3, 50) + + stats, err := db.GetStats("Alice") + if err != nil { + t.Fatal(err) + } + if stats.TotalRuns != 3 { + t.Errorf("expected 3 total runs, got %d", stats.TotalRuns) + } + if stats.BestFloor != 20 { + t.Errorf("expected best floor 20, got %d", stats.BestFloor) + } + if stats.Victories != 1 { + t.Errorf("expected 1 victory, got %d", stats.Victories) + } + if stats.TotalGold != 850 { // 100+250+500 + t.Errorf("expected total gold 850, got %d", stats.TotalGold) + } + + // Bob's stats should be separate + bobStats, _ := db.GetStats("Bob") + if bobStats.TotalRuns != 1 { + t.Errorf("Bob should have 1 run, got %d", bobStats.TotalRuns) + } +}