package seating import ( "context" "fmt" "testing" ) func TestBreakTable_DistributesEvenly(t *testing.T) { db := testDB(t) ctx := context.Background() tournamentID := "t1" seedTournament(t, db, tournamentID) tbl1 := seedTable(t, db, tournamentID, "Table 1", 9) tbl2 := seedTable(t, db, tournamentID, "Table 2", 9) tbl3 := seedTable(t, db, tournamentID, "Table 3", 9) // Table 1: 6 players (will be broken) // Table 2: 5 players // Table 3: 5 players for i := 1; i <= 6; i++ { pid := fmt.Sprintf("p1_%d", i) seedPlayer(t, db, pid, fmt.Sprintf("Player 1-%d", i)) seatPlayer(t, db, tournamentID, pid, tbl1, i) } for i := 1; i <= 5; i++ { pid := fmt.Sprintf("p2_%d", i) seedPlayer(t, db, pid, fmt.Sprintf("Player 2-%d", i)) seatPlayer(t, db, tournamentID, pid, tbl2, i) } for i := 1; i <= 5; i++ { pid := fmt.Sprintf("p3_%d", i) seedPlayer(t, db, pid, fmt.Sprintf("Player 3-%d", i)) seatPlayer(t, db, tournamentID, pid, tbl3, i) } svc := NewBreakTableService(db, nil, nil) result, err := svc.BreakTable(ctx, tournamentID, tbl1) if err != nil { t.Fatalf("break table: %v", err) } if result.BrokenTableID != tbl1 { t.Errorf("expected broken table id %d, got %d", tbl1, result.BrokenTableID) } if len(result.Moves) != 6 { t.Fatalf("expected 6 moves, got %d", len(result.Moves)) } // Count how many went to each table: should be 3 each (5+3=8, 5+3=8) movesToTable := make(map[int]int) for _, m := range result.Moves { movesToTable[m.ToTableID]++ } for tid, count := range movesToTable { if count < 2 || count > 4 { t.Errorf("table %d got %d players, expected roughly even distribution", tid, count) } } // Verify table 1 is deactivated var isActive int db.QueryRow(`SELECT is_active FROM tables WHERE id = ?`, tbl1).Scan(&isActive) if isActive != 0 { t.Error("broken table should be deactivated") } // Verify all players are seated at other tables var unseatedCount int db.QueryRow( `SELECT COUNT(*) FROM tournament_players WHERE tournament_id = ? AND status = 'active' AND seat_table_id IS NULL`, tournamentID, ).Scan(&unseatedCount) if unseatedCount != 0 { t.Errorf("expected 0 unseated players, got %d", unseatedCount) } } func TestBreakTable_OddPlayerCount(t *testing.T) { db := testDB(t) ctx := context.Background() tournamentID := "t1" seedTournament(t, db, tournamentID) tbl1 := seedTable(t, db, tournamentID, "Table 1", 9) tbl2 := seedTable(t, db, tournamentID, "Table 2", 9) tbl3 := seedTable(t, db, tournamentID, "Table 3", 9) // Table 1: 5 players (will be broken) // Table 2: 4 players // Table 3: 3 players for i := 1; i <= 5; i++ { pid := fmt.Sprintf("p1_%d", i) seedPlayer(t, db, pid, fmt.Sprintf("Player 1-%d", i)) seatPlayer(t, db, tournamentID, pid, tbl1, i) } for i := 1; i <= 4; i++ { pid := fmt.Sprintf("p2_%d", i) seedPlayer(t, db, pid, fmt.Sprintf("Player 2-%d", i)) seatPlayer(t, db, tournamentID, pid, tbl2, i) } for i := 1; i <= 3; i++ { pid := fmt.Sprintf("p3_%d", i) seedPlayer(t, db, pid, fmt.Sprintf("Player 3-%d", i)) seatPlayer(t, db, tournamentID, pid, tbl3, i) } svc := NewBreakTableService(db, nil, nil) result, err := svc.BreakTable(ctx, tournamentID, tbl1) if err != nil { t.Fatalf("break table: %v", err) } if len(result.Moves) != 5 { t.Fatalf("expected 5 moves, got %d", len(result.Moves)) } // Count final player counts at tbl2 and tbl3 var count2, count3 int db.QueryRow(`SELECT COUNT(*) FROM tournament_players WHERE tournament_id = ? AND seat_table_id = ? AND status = 'active'`, tournamentID, tbl2).Scan(&count2) db.QueryRow(`SELECT COUNT(*) FROM tournament_players WHERE tournament_id = ? AND seat_table_id = ? AND status = 'active'`, tournamentID, tbl3).Scan(&count3) // 12 total players across 2 tables -> 6 each if count2+count3 != 12 { t.Errorf("expected 12 total, got %d", count2+count3) } // Difference should be at most 1 diff := count2 - count3 if diff < 0 { diff = -diff } if diff > 1 { t.Errorf("expected tables balanced to within 1, got diff=%d (table2=%d, table3=%d)", diff, count2, count3) } } func TestBreakTable_DeactivatesTable(t *testing.T) { db := testDB(t) ctx := context.Background() tournamentID := "t1" seedTournament(t, db, tournamentID) tbl1 := seedTable(t, db, tournamentID, "Table 1", 9) tbl2 := seedTable(t, db, tournamentID, "Table 2", 9) // 2 players at each table seedPlayer(t, db, "p1", "P1") seatPlayer(t, db, tournamentID, "p1", tbl1, 1) seedPlayer(t, db, "p2", "P2") seatPlayer(t, db, tournamentID, "p2", tbl1, 2) seedPlayer(t, db, "p3", "P3") seatPlayer(t, db, tournamentID, "p3", tbl2, 1) seedPlayer(t, db, "p4", "P4") seatPlayer(t, db, tournamentID, "p4", tbl2, 2) svc := NewBreakTableService(db, nil, nil) _, err := svc.BreakTable(ctx, tournamentID, tbl1) if err != nil { t.Fatalf("break table: %v", err) } var isActive int db.QueryRow(`SELECT is_active FROM tables WHERE id = ?`, tbl1).Scan(&isActive) if isActive != 0 { t.Error("broken table should be deactivated (is_active = 0)") } } func TestBreakTable_InactiveTableReturnsError(t *testing.T) { db := testDB(t) ctx := context.Background() tournamentID := "t1" seedTournament(t, db, tournamentID) tbl := seedTable(t, db, tournamentID, "Table 1", 9) _ = seedTable(t, db, tournamentID, "Table 2", 9) // Deactivate the table first db.Exec(`UPDATE tables SET is_active = 0 WHERE id = ?`, tbl) svc := NewBreakTableService(db, nil, nil) _, err := svc.BreakTable(ctx, tournamentID, tbl) if err == nil { t.Fatal("expected error for inactive table") } } func TestBreakTable_NoDestinationTablesReturnsError(t *testing.T) { db := testDB(t) ctx := context.Background() tournamentID := "t1" seedTournament(t, db, tournamentID) tbl := seedTable(t, db, tournamentID, "Table 1", 9) seedPlayer(t, db, "p1", "P1") seatPlayer(t, db, tournamentID, "p1", tbl, 1) svc := NewBreakTableService(db, nil, nil) _, err := svc.BreakTable(ctx, tournamentID, tbl) if err == nil { t.Fatal("expected error when no destination tables") } }