Files
2026-10-27 17:31:41 +03:00

265 lines
6.9 KiB
Go

package protocol
import (
"encoding/json"
"testing"
)
func TestParseJoinRoundtrip(t *testing.T) {
opcode, args, err := ParseMessage([]byte(`[1,"game-token","user-token"]`))
if err != nil {
t.Fatalf("parse: %v", err)
}
if opcode != OpJoin {
t.Fatalf("opcode = %d, want %d", opcode, OpJoin)
}
gt, ut, err := ParseJoin(args)
if err != nil {
t.Fatalf("parse join: %v", err)
}
if gt != "game-token" || ut != "user-token" {
t.Fatalf("got (%q, %q)", gt, ut)
}
}
func TestParseMessageErrors(t *testing.T) {
for _, data := range []string{`{}`, `[]`, `["1"]`, `not json`} {
if _, _, err := ParseMessage([]byte(data)); err == nil {
t.Fatalf("expected error for %s", data)
}
}
}
func TestBuildEnd(t *testing.T) {
data := BuildEnd(7, EndCodeJoinTimeout, "join timeout exceeded")
var v []any
if err := json.Unmarshal(data, &v); err != nil {
t.Fatalf("unmarshal: %v", err)
}
want := []any{float64(2), float64(7), float64(-1), "join timeout exceeded"}
if len(v) != len(want) {
t.Fatalf("got %v, want %v", v, want)
}
for i := range want {
if v[i] != want[i] {
t.Fatalf("got %v, want %v", v, want)
}
}
}
func TestBuildStateAndAck(t *testing.T) {
if got := string(BuildState(3, StatePlacingShips)); got != `[3,3,1]` {
t.Fatalf("BuildState = %s", got)
}
if got := string(BuildAck(3)); got != `[4,3]` {
t.Fatalf("BuildAck = %s", got)
}
}
func TestParseAck(t *testing.T) {
_, args, err := ParseMessage([]byte(`[4,12]`))
if err != nil {
t.Fatalf("parse: %v", err)
}
seq, err := ParseAck(args)
if err != nil {
t.Fatalf("parse ack: %v", err)
}
if seq != 12 {
t.Fatalf("seq = %d, want 12", seq)
}
}
func validTestShips() []Ship {
return []Ship{
// 4 size 1
{Orientation: OrientationHorizontal, X: 9, Y: 4, Size: 1},
{Orientation: OrientationVertical, X: 7, Y: 4, Size: 1},
{Orientation: OrientationHorizontal, X: 5, Y: 4, Size: 1},
{Orientation: OrientationHorizontal, X: 3, Y: 4, Size: 1},
// 3 size 2
{Orientation: OrientationHorizontal, X: 0, Y: 4, Size: 2},
{Orientation: OrientationHorizontal, X: 7, Y: 2, Size: 2},
{Orientation: OrientationHorizontal, X: 4, Y: 2, Size: 2},
// 2 size 3
{Orientation: OrientationHorizontal, X: 0, Y: 2, Size: 3},
{Orientation: OrientationHorizontal, X: 5, Y: 0, Size: 3},
// 1 size 4
{Orientation: OrientationHorizontal, X: 0, Y: 0, Size: 4},
}
}
func TestPlaceShipsRoundtrip(t *testing.T) {
ships := validTestShips()
data := BuildPlaceShips(ships)
opcode, args, err := ParseMessage(data)
if err != nil {
t.Fatalf("ParseMessage: %v", err)
}
if opcode != OpPlaceShips {
t.Fatalf("opcode = %d, want %d", opcode, OpPlaceShips)
}
parsed, err := ParsePlaceShips(args)
if err != nil {
t.Fatalf("ParsePlaceShips: %v", err)
}
if len(parsed) != len(ships) {
t.Fatalf("got %d ships, want %d", len(parsed), len(ships))
}
for i := range ships {
if parsed[i] != ships[i] {
t.Fatalf("ship %d = %+v, want %+v", i, parsed[i], ships[i])
}
}
if err := ValidateShips(parsed); err != nil {
t.Fatalf("ValidateShips: %v", err)
}
}
func TestParsePlaceShipsErrors(t *testing.T) {
t.Run("wrong argument count", func(t *testing.T) {
args := make([]json.RawMessage, 29)
for i := range args {
args[i] = json.RawMessage(`0`)
}
if _, err := ParsePlaceShips(args); err == nil {
t.Fatal("expected error for 29 arguments")
}
})
t.Run("orientation not boolean", func(t *testing.T) {
ships := validTestShips()
raw := make([]any, 31)
raw[0] = OpPlaceShips
for i, s := range ships {
raw[1+i*3] = s.Orientation
raw[1+i*3+1] = s.X
raw[1+i*3+2] = s.Y
}
raw[1] = "invalid_orientation" // string instead of bool
data, _ := json.Marshal(raw)
_, args, _ := ParseMessage(data)
if _, err := ParsePlaceShips(args); err == nil {
t.Fatal("expected error for non-boolean orientation")
}
})
t.Run("x not integer", func(t *testing.T) {
ships := validTestShips()
raw := make([]any, 31)
raw[0] = OpPlaceShips
for i, s := range ships {
raw[1+i*3] = s.Orientation
raw[1+i*3+1] = s.X
raw[1+i*3+2] = s.Y
}
raw[2] = 1.5 // float instead of integer
data, _ := json.Marshal(raw)
_, args, _ := ParseMessage(data)
if _, err := ParsePlaceShips(args); err == nil {
t.Fatal("expected error for float x")
}
})
t.Run("y not integer", func(t *testing.T) {
ships := validTestShips()
raw := make([]any, 31)
raw[0] = OpPlaceShips
for i, s := range ships {
raw[1+i*3] = s.Orientation
raw[1+i*3+1] = s.X
raw[1+i*3+2] = s.Y
}
raw[3] = "zero" // string instead of integer
data, _ := json.Marshal(raw)
_, args, _ := ParseMessage(data)
if _, err := ParsePlaceShips(args); err == nil {
t.Fatal("expected error for string y")
}
})
}
func TestValidateShips(t *testing.T) {
t.Run("valid placement passes", func(t *testing.T) {
ships := validTestShips()
if err := ValidateShips(ships); err != nil {
t.Fatalf("unexpected validation error: %v", err)
}
})
t.Run("wrong ship count", func(t *testing.T) {
ships := validTestShips()[:9]
if err := ValidateShips(ships); err == nil {
t.Fatal("expected error for 9 ships")
}
})
t.Run("wrong ship size", func(t *testing.T) {
ships := validTestShips()
ships[0].Size = 2 // ship 0 must have size 1
if err := ValidateShips(ships); err == nil {
t.Fatal("expected error for wrong ship size")
}
})
t.Run("negative coordinate", func(t *testing.T) {
ships := validTestShips()
ships[0].X = -1
if err := ValidateShips(ships); err == nil {
t.Fatal("expected error for negative X")
}
})
t.Run("out of bounds horizontal", func(t *testing.T) {
ships := validTestShips()
// Ship 9 has size 4; at X=7, cells are 7, 8, 9, 10
ships[9].X = 7
if err := ValidateShips(ships); err == nil {
t.Fatal("expected error for horizontal ship sticking out of bounds")
}
})
t.Run("out of bounds vertical", func(t *testing.T) {
ships := validTestShips()
// Ship 9 has size 4, vertical at Y=7: cells are 7, 8, 9, 10
ships[9].Orientation = OrientationVertical
ships[9].Y = 7
if err := ValidateShips(ships); err == nil {
t.Fatal("expected error for vertical ship sticking out of bounds")
}
})
t.Run("overlapping ships", func(t *testing.T) {
ships := validTestShips()
// Place ship 0 at exact same cell as ship 1
ships[0].X = ships[1].X
ships[0].Y = ships[1].Y
if err := ValidateShips(ships); err == nil {
t.Fatal("expected error for overlapping ships")
}
})
t.Run("orthogonal touching", func(t *testing.T) {
ships := validTestShips()
// Ship 0 is at (9, 4), place ship 1 at (9, 5) -> touches orthogonally
ships[1].X = 9
ships[1].Y = 5
if err := ValidateShips(ships); err == nil {
t.Fatal("expected error for orthogonally touching ships")
}
})
t.Run("diagonal touching", func(t *testing.T) {
ships := validTestShips()
// Ship 0 is at (9, 4), place ship 1 at (8, 5) -> touches diagonally
ships[1].X = 8
ships[1].Y = 5
if err := ValidateShips(ships); err == nil {
t.Fatal("expected error for diagonally touching ships")
}
})
}