From 1902084523163baf85ee27f8dcd33a617a9a9471 Mon Sep 17 00:00:00 2001 From: Josh Creek <8179928+jcreek@users.noreply.github.com> Date: Mon, 31 Aug 2026 20:59:29 +0100 Subject: [PATCH] test: add multiplayer control-plane fuzz targets --- multiplayer-todo.md | 2 +- server/domain/fuzz_test.go | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 server/domain/fuzz_test.go diff --git a/multiplayer-todo.md b/multiplayer-todo.md index 431cbd7d..bed1485e 100644 --- a/multiplayer-todo.md +++ b/multiplayer-todo.md @@ -1238,7 +1238,7 @@ the local/CI/community transport, not a silent production fallback. |---|---|---| | 8.44 `[D:8.3,8.4,8.28,8.31]` | Propagate queue/proposal/match/server IDs and process-ready/assignment-ready through logs, metrics, traces and replay metadata; redact credentials | One ID traces queue→result across components and automated secret-canary tests find no auth/relay ticket | | 8.45 `[D:8.2,8.44]` | Dashboards/alerts for wait/MMR/RTT, proposals, allocation/Ready/image pull, connect/no-show, tick/crash/flood, result conflict/lag, abandons and cost | Each SLO and security/cost signal has an exercised alert and runbook | -| 8.46 `[D:8.5,8.7,8.9,8.10,8.14,8.18,8.21,8.23,8.25]` | Go unit, race, fuzz, property, migration and concurrency suites | CI covers auth/join replay-reclaim, stale revisions, durable matcher fencing with lost Redis ack, rollover, result conflict/delivery retry and PostgreSQL retry | +| 8.46 `[D:8.5,8.7,8.9,8.10,8.14,8.18,8.21,8.23,8.25]` | **IN PROGRESS.** Go unit/race coverage spans the current domain/store/supervisor policies, and fuzz targets now exercise queue input, result payload hashing and revision events | `server/domain/*_test.go`, `server/store/*_test.go`, `server/supervisor/*_test.go` and `server/domain/fuzz_test.go` pass normal/race suites; `go test -fuzz`, PostgreSQL concurrency/migration execution, fake Steam/allocator and full lost-Redis/transaction fixtures remain | | 8.47 `[D:8.7,8.30]` | Fake Steam verifier and fake allocator for deterministic CI | Normal CI needs no Steam/cloud secret or internet access and can force every success/failure deterministically | | 8.48 `[D:8.10,8.14,8.17,8.18,8.27,8.31,8.35,8.47]` | Second Compose flow: fake backend → queue/proposal → process-ready/allocation/assignment-ready → ENet roster → result ack → shutdown; do not edit Phase 6 fixture | Both server models have independent green gates; existing Make invocations remain unchanged | | 8.49 `[D:8.25,8.26,8.28,8.29,8.30,8.31,8.35,8.36]` | Disposable `kind` + Agones integration gate | CI covers dynamic ports, both readiness stages, roster/no-show, races, multi-match node, result-pending reconciliation, drain and rollback | diff --git a/server/domain/fuzz_test.go b/server/domain/fuzz_test.go new file mode 100644 index 00000000..0064f447 --- /dev/null +++ b/server/domain/fuzz_test.go @@ -0,0 +1,34 @@ +package domain + +import ( + "testing" + "time" +) + +func FuzzQueueCreateDoesNotPanic(f *testing.F) { + f.Add("player-1", "ticket-1", "key-1234567890123456", 1500.0, "EU", 20.0) + f.Fuzz(func(t *testing.T, playerID, ticketID, key string, rating float64, region string, rtt float64) { + q := NewQueue() + now := time.Unix(1000, 0) + candidate := Candidate{PlayerID: playerID, TicketID: ticketID, Rating: rating, EnqueuedAt: now, PredictedRTT: map[string]float64{region: rtt}} + _, _ = q.Create(playerID, ticketID, key, candidate, now) + }) +} + +func FuzzResultDigestIsDeterministic(f *testing.F) { + f.Add("match-1", "server-1", "nonce-1234567890", 3, 2, string(IntegrityCertified)) + f.Fuzz(func(t *testing.T, matchID, serverID, nonce string, team0, team1 int, integrity string) { + result := MatchResult{MatchID: matchID, ServerID: serverID, ResultNonce: nonce, Team0Score: team0, Team1Score: team1, IntegrityState: IntegrityState(integrity)} + if resultDigest(result) != resultDigest(result) { + t.Fatal("digest is not deterministic") + } + }) +} + +func FuzzSyncEventApplicationDoesNotPanic(f *testing.F) { + f.Add(uint64(1), "QUEUED") + f.Fuzz(func(t *testing.T, revision uint64, state string) { + r, _ := NewReplicaResource(ResourceQueueTicket, "ticket-1", Queued) + _ = r.ApplyEvent(SyncEvent{Kind: ResourceQueueTicket, ResourceID: "ticket-1", Revision: revision, State: State(state)}) + }) +}