193 lines
5.7 KiB
Go

package main
import (
"math"
"strings"
"testing"
"time"
)
func TestTicketSingleUse(t *testing.T) {
store := newTicketStore()
value, err := store.issue("player-1", "Dave")
if err != nil {
t.Fatal(err)
}
if item, ok := store.consume(value); !ok || item.Subject != "player-1" {
t.Fatalf("first consume failed: %#v %v", item, ok)
}
if _, ok := store.consume(value); ok {
t.Fatal("ticket must be one-time")
}
}
func TestTransformSanitizers(t *testing.T) {
if got := clampFloat(-5, 0, worldSize); got != 0 {
t.Fatalf("clamp low=%v", got)
}
if got := clampFloat(4000, 0, worldSize); got != worldSize {
t.Fatalf("clamp high=%v", got)
}
if got := clampFloat(math.NaN(), 0, worldSize); got != 0 {
t.Fatalf("nan=%v", got)
}
if got := normalizeAngle(-90); got != 270 {
t.Fatalf("angle=%v", got)
}
}
func TestAudienceMatch(t *testing.T) {
if !audienceMatches(claims{AuthorizedParty: "airship-web"}, "airship-web") {
t.Fatal("azp should match")
}
if !audienceMatches(claims{Audience: []interface{}{"account", "airship-web"}}, "airship-web") {
t.Fatal("aud should match")
}
if audienceMatches(claims{AuthorizedParty: "other"}, "airship-web") {
t.Fatal("wrong client must not match")
}
}
func TestServerMovementUsesInputNotClientTransform(t *testing.T) {
state := newPlayerState("player-1", "Dave", 0, 0)
startX, startY := state.X, state.Y
input := inputState{Throttle: 1, Steer: 0.4, Seq: 7}
now := time.Unix(100, 0)
for i := 0; i < 30; i++ {
stepPlayer(&state, input, now.Add(time.Duration(i)*simTick), simTick.Seconds())
}
if state.InputSeq != 7 {
t.Fatalf("input seq=%d", state.InputSeq)
}
if state.Speed <= 0 || state.Speed > playerMaxSpeed {
t.Fatalf("speed=%v", state.Speed)
}
if state.X == startX && state.Y == startY {
t.Fatal("authoritative simulation did not move player")
}
if state.Angle == 0 {
t.Fatal("steer input did not change angle")
}
}
func TestDockedInputFreezesAuthoritativeShip(t *testing.T) {
state := newPlayerState("player-1", "Dave", 0, 0)
state.Speed = 150
x, y := state.X, state.Y
stepPlayer(&state, inputState{Docked: true, Seq: 1}, time.Now(), simTick.Seconds())
if state.Speed != 0 || state.X != x || state.Y != y {
t.Fatalf("docked ship moved: speed=%v x=%v y=%v", state.Speed, state.X, state.Y)
}
}
func TestBroadsideSpawnsThreeServerProjectilesAndEnforcesReload(t *testing.T) {
h := newHub("TEST")
c := &client{state: newPlayerState("one", "One", 0, 0), lastFire: map[string]time.Time{}}
h.clients[c.state.ID] = c
now := time.Unix(100, 0)
if !h.fire("one", "port", now) {
t.Fatal("first fire should succeed")
}
if len(h.projectiles) != 3 {
t.Fatalf("projectiles=%d", len(h.projectiles))
}
if h.fire("one", "port", now.Add(100*time.Millisecond)) {
t.Fatal("reload should reject early second broadside")
}
if !h.fire("one", "port", now.Add(broadsideReload)) {
t.Fatal("broadside should fire after reload")
}
if len(h.projectiles) != 6 {
t.Fatalf("projectiles after reload=%d", len(h.projectiles))
}
}
func TestServerProjectileAppliesDamageDeathAndKill(t *testing.T) {
h := newHub("TEST")
shooter := &client{state: newPlayerState("one", "One", 0, 0), lastFire: map[string]time.Time{}}
target := &client{state: newPlayerState("two", "Two", 0, 0), lastFire: map[string]time.Time{}}
shooter.state.X, shooter.state.Y = 1000, 1000
target.state.X, target.state.Y = 1050, 1000
target.state.Hull = shotDamage
h.clients[shooter.state.ID] = shooter
h.clients[target.state.ID] = target
now := time.Unix(100, 0)
h.projectiles["shot"] = projectileState{
ID: "shot", OwnerID: shooter.state.ID, X: target.state.X, Y: target.state.Y,
Damage: shotDamage, bornAt: now,
}
h.step(now.Add(simTick), simTick.Seconds())
if target.state.Alive {
t.Fatal("target should be dead")
}
if target.state.Deaths != 1 || shooter.state.Kills != 1 {
t.Fatalf("kills/deaths=%d/%d", shooter.state.Kills, target.state.Deaths)
}
if target.state.LastKiller != "One" {
t.Fatalf("killer=%q", target.state.LastKiller)
}
if _, ok := h.projectiles["shot"]; ok {
t.Fatal("hit projectile should be removed")
}
}
func TestServerRespawnsDestroyedPlayer(t *testing.T) {
state := newPlayerState("player-1", "Dave", 0, 1)
now := time.Unix(100, 0)
state.Alive = false
state.Hull = 0
state.Speed = 120
state.RespawnAt = now.Add(respawnDelay).UnixMilli()
stepPlayer(&state, inputState{}, now.Add(respawnDelay+time.Millisecond), simTick.Seconds())
if !state.Alive || state.Hull != playerMaxHull || state.Speed != 0 {
t.Fatalf("bad respawn: %+v", state)
}
}
func TestFakeCaptainsJoinSnapshotsWithoutSocketRecipients(t *testing.T) {
h := newHub("TEST", 3)
recipients, players, _ := h.snapshot()
if len(recipients) != 0 {
t.Fatalf("fake captains must not be websocket recipients: %d", len(recipients))
}
if len(players) != 3 {
t.Fatalf("fake captain count=%d", len(players))
}
for _, player := range players {
if !player.Bot || !strings.HasPrefix(player.ID, "bot:") {
t.Fatalf("expected bot snapshot state: %+v", player)
}
}
}
func TestFakeCaptainEngagesNearbyHumanWithAuthoritativeBroadside(t *testing.T) {
h := newHub("TEST", 1)
var bot *client
for _, candidate := range h.clients {
if candidate.state.Bot {
bot = candidate
break
}
}
if bot == nil {
t.Fatal("missing fake captain")
}
human := &client{
state: newPlayerState("human", "Human", 0, 0),
lastFire: map[string]time.Time{},
}
bot.state.X, bot.state.Y, bot.state.Angle = 1000, 1000, 0
human.state.X, human.state.Y = 1300, 1000
h.clients[human.state.ID] = human
now := time.Unix(200, 0)
h.step(now, simTick.Seconds())
if len(h.projectiles) != 3 {
t.Fatalf("expected fake captain broadside, projectiles=%d", len(h.projectiles))
}
if bot.input.Seq == 0 {
t.Fatal("fake captain input sequence did not advance")
}
}