felt/internal/player/player_test.go
Mikkel Georgsen 8b4b131371 feat(01-07): add player API routes, ranking tests, CSV export safety
- PlayerHandler with all CRUD routes and tournament player operations
- Buy-in flow: register + financial engine + auto-seat suggestion
- Bust flow: hitman selection + bounty transfer + re-ranking
- Undo bust with full re-ranking and rankings response
- Rankings API endpoint returning derived positions
- QR code endpoint returns PNG image with Cache-Control header
- CSV import via multipart upload (admin only)
- Player merge endpoint (admin only)
- CSV export safety: formula injection neutralization (tab-prefix =,+,-,@)
- Ranking tests: bust order, undo re-ranking, early undo, re-entry,
  deal positions, auto-close, concurrent busts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 04:35:05 +01:00

111 lines
2.6 KiB
Go

package player
import (
"testing"
)
// ---------- CSV Export Safety Tests ----------
func TestSanitizeCSVField_FormulaInjection(t *testing.T) {
tests := []struct {
input string
expected string
name string
}{
{"=CMD()", "\t=CMD()", "equals command"},
{"+1+1", "\t+1+1", "plus prefix"},
{"-1+1", "\t-1+1", "minus prefix"},
{"@SUM(A1)", "\t@SUM(A1)", "at sign function"},
{"=HYPERLINK(\"http://evil.com\")", "\t=HYPERLINK(\"http://evil.com\")", "hyperlink formula"},
{"Normal text", "Normal text", "normal text"},
{"123", "123", "numeric"},
{"", "", "empty string"},
{"Hello World", "Hello World", "regular greeting"},
{"John Doe", "John Doe", "player name"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := SanitizeCSVField(tt.input)
if result != tt.expected {
t.Errorf("SanitizeCSVField(%q) = %q, want %q", tt.input, result, tt.expected)
}
})
}
}
func TestSanitizeCSVRow(t *testing.T) {
row := []string{"John", "=SUM(A1)", "+1", "-1", "@SUM", "Normal"}
expected := []string{"John", "\t=SUM(A1)", "\t+1", "\t-1", "\t@SUM", "Normal"}
result := SanitizeCSVRow(row)
for i, v := range result {
if v != expected[i] {
t.Errorf("SanitizeCSVRow[%d] = %q, want %q", i, v, expected[i])
}
}
}
func TestIsFormulaInjection(t *testing.T) {
tests := []struct {
input string
expected bool
}{
{"=CMD()", true},
{"+1+1", true},
{"-1+1", true},
{"@SUM(A1)", true},
{"Normal text", false},
{"123", false},
{"", false},
}
for _, tt := range tests {
if result := IsFormulaInjection(tt.input); result != tt.expected {
t.Errorf("IsFormulaInjection(%q) = %v, want %v", tt.input, result, tt.expected)
}
}
}
// ---------- CSV Import Safety Limit Tests ----------
func TestCSVImportLimits(t *testing.T) {
// These are constant assertions, not behavioral tests.
// They verify the safety constants are set to the documented values.
if MaxCSVRows != 10_000 {
t.Errorf("MaxCSVRows = %d, want 10000", MaxCSVRows)
}
if MaxCSVColumns != 20 {
t.Errorf("MaxCSVColumns = %d, want 20", MaxCSVColumns)
}
if MaxCSVFieldLen != 1_000 {
t.Errorf("MaxCSVFieldLen = %d, want 1000", MaxCSVFieldLen)
}
}
// ---------- UUID Generation Tests ----------
func TestGenerateUUID(t *testing.T) {
id := generateUUID()
if len(id) == 0 {
t.Error("generateUUID returned empty string")
}
// Check format: 8-4-4-4-12
parts := 0
for _, c := range id {
if c == '-' {
parts++
}
}
if parts != 4 {
t.Errorf("UUID should have 4 dashes, got %d: %s", parts, id)
}
// Check uniqueness
id2 := generateUUID()
if id == id2 {
t.Error("two UUIDs should not be equal")
}
}