test: lobby, session, store tests — deep copy, logs, inventory, stats
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user