265 lines
6.9 KiB
Go
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")
|
|
}
|
|
})
|
|
}
|