test(multiplayer): verify two-player proposal round trip

This commit is contained in:
Josh Creek
2026-09-01 15:17:53 +01:00
parent 1e1db3525e
commit aa93aeec95
9 changed files with 369 additions and 3 deletions
+1
View File
@@ -112,6 +112,7 @@ func main() {
}
return domain.PrepareProposal(id, playlist, formation, nil, domain.RankedArena{}, at)
},
OnError: func(err error) { log.Printf("matcher pass: %v", err) },
}
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
+45 -2
View File
@@ -16,6 +16,7 @@ import (
"crypto/sha256"
"database/sql"
"encoding/hex"
"encoding/json"
"flag"
"fmt"
"net"
@@ -54,7 +55,7 @@ func main() {
if err := migrations.Apply(startupCtx, db, *migrationDir); err != nil {
fatalf("apply migrations: %v", err)
}
handler := (&api.Service{
service := &api.Service{
SessionBackend: store.PostgresSessions{DB: db},
SessionIssuer: store.PostgresSessions{DB: db},
SteamLogin: fakeSteamLogin{db: db},
@@ -68,7 +69,8 @@ func main() {
ProbeRecorder: store.PostgresQueue{DB: db},
WorkloadVerify: api.WorkloadVerifierFromSignedToken([]byte(*workloadSecret), db),
Now: func() time.Time { return time.Now().UTC() },
}).Handler()
}
handler := service.Handler()
listener, err := net.Listen("tcp", *listen)
if err != nil {
fatalf("listen: %v", err)
@@ -79,6 +81,7 @@ func main() {
go func() { serveErr <- server.Serve(listener) }()
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
go dispatchProposalOutbox(ctx, db, service)
select {
case err := <-serveErr:
if err != nil && err != http.ErrServerClosed {
@@ -91,6 +94,46 @@ func main() {
}
}
func dispatchProposalOutbox(ctx context.Context, db *sql.DB, service *api.Service) {
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
events, err := store.ReadUnpublishedOutbox(ctx, db, 100)
if err != nil {
continue
}
for _, event := range events {
if event.EventType != "proposal_changed" {
continue
}
var controlEvent api.ControlPlaneEvent
var envelope struct {
Event string `json:"event"`
Revision uint64 `json:"revision"`
ResourceID string `json:"resource_id"`
OccurredAt time.Time `json:"occurred_at"`
State string `json:"state"`
PlayerIDs []string `json:"player_ids"`
}
if err := json.Unmarshal(event.Payload, &envelope); err != nil {
continue
}
for _, playerID := range envelope.PlayerIDs {
controlEvent = api.ControlPlaneEvent{Event: envelope.Event, Revision: envelope.Revision, ResourceID: envelope.ResourceID, OccurredAt: envelope.OccurredAt, State: envelope.State, PlayerID: playerID}
if err := service.PublishControlPlaneEvent(controlEvent); err != nil {
continue
}
}
_ = store.MarkOutboxPublished(ctx, db, event.EventID, time.Now().UTC())
}
}
}
}
// fakeSteamLogin derives a deterministic identity from the ticket string
// itself (never a real Steam Web API ticket in this binary) and ensures its
// identities row exists so session issuance's foreign key is satisfied.