mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-12 09:13:42 +00:00
fix(multiplayer): reconcile authoritative initial connections
This commit is contained in:
@@ -70,6 +70,20 @@ type serverShutdownerSpy struct {
|
||||
err error
|
||||
}
|
||||
|
||||
type serverConnectionSpy struct {
|
||||
calls int
|
||||
binding domain.WorkloadBinding
|
||||
playerID string
|
||||
key string
|
||||
err error
|
||||
}
|
||||
|
||||
func (s *serverConnectionSpy) RecordPlayerConnected(_ context.Context, binding domain.WorkloadBinding, playerID, key string, _ time.Time) error {
|
||||
s.calls++
|
||||
s.binding, s.playerID, s.key = binding, playerID, key
|
||||
return s.err
|
||||
}
|
||||
|
||||
func (s *serverShutdownerSpy) ShutdownServer(_ context.Context, binding domain.WorkloadBinding, reason, key string, _ time.Time) error {
|
||||
s.calls++
|
||||
s.binding, s.reason, s.key = binding, reason, key
|
||||
@@ -1458,6 +1472,52 @@ func TestServerShutdownAPIRequiresBoundWorkloadAndDelegatesAcknowledgement(t *te
|
||||
response.Body.Close()
|
||||
}
|
||||
|
||||
func TestServerConnectionAPIRequiresBoundWorkloadAndOpaqueAssignedPlayer(t *testing.T) {
|
||||
now := time.Unix(1000, 0).UTC()
|
||||
binding := domain.WorkloadBinding{AllocationID: "allocation-123456", MatchID: "match-1234567890", ServerID: "server-123456789"}
|
||||
recorder := &serverConnectionSpy{}
|
||||
service := &Service{Now: func() time.Time { return now }, WorkloadVerify: func(token string, _ time.Time) (domain.WorkloadBinding, error) {
|
||||
if token != "workload-token" {
|
||||
return domain.WorkloadBinding{}, errors.New("bad token")
|
||||
}
|
||||
return binding, nil
|
||||
}, ServerConnections: recorder}
|
||||
server := httptest.NewServer(service.Handler())
|
||||
defer server.Close()
|
||||
|
||||
request := func(serverID, playerID, token, key string) int {
|
||||
body := fmt.Sprintf(`{"player_id":%q}`, playerID)
|
||||
req, _ := http.NewRequest(http.MethodPost, server.URL+"/v1/servers/"+serverID+"/connect", strings.NewReader(body))
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
req.Header.Set("Idempotency-Key", key)
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
response.Body.Close()
|
||||
return response.StatusCode
|
||||
}
|
||||
if got := request(binding.ServerID, "player-123456789", "workload-token", "connect-player-123456789"); got != http.StatusNoContent {
|
||||
t.Fatalf("connection status = %d", got)
|
||||
}
|
||||
if recorder.calls != 1 || recorder.binding != binding || recorder.playerID != "player-123456789" || recorder.key != "connect-player-123456789" {
|
||||
t.Fatalf("connection receipt = %+v", recorder)
|
||||
}
|
||||
if got := request("server-000000000", "player-123456789", "workload-token", "connect-player-123456789"); got != http.StatusUnauthorized {
|
||||
t.Fatalf("wrong server status = %d", got)
|
||||
}
|
||||
if got := request(binding.ServerID, "short", "workload-token", "connect-player-short-123"); got != http.StatusUnprocessableEntity {
|
||||
t.Fatalf("short player status = %d", got)
|
||||
}
|
||||
if recorder.calls != 1 {
|
||||
t.Fatalf("invalid receipts reached backend: %d", recorder.calls)
|
||||
}
|
||||
recorder.err = errors.New("database unavailable")
|
||||
if got := request(binding.ServerID, "player-123456789", "workload-token", "connect-player-retry-123"); got != http.StatusServiceUnavailable {
|
||||
t.Fatalf("recorder outage status = %d, want retryable 503", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricsEndpointExportsBoundedAPILatencyAndSkipsItsOwnScrape(t *testing.T) {
|
||||
metrics := observability.NewMetrics()
|
||||
service := &Service{Metrics: metrics, Now: time.Now}
|
||||
|
||||
Reference in New Issue
Block a user