feat: add player-scoped assignment recovery

This commit is contained in:
Josh Creek
2026-08-31 22:54:16 +01:00
parent 99680dbf6f
commit 69f1ef6be1
6 changed files with 181 additions and 1 deletions
+58
View File
@@ -600,3 +600,61 @@ func TestProposalRecoveryIsParticipantScopedAndExpiresAtReadBoundary(t *testing.
t.Fatalf("expired recovery status=%d body=%+v", status, recovered)
}
}
func TestAssignmentRecoveryIsPlayerScopedAndRejectsExpiredOrMismatchedViews(t *testing.T) {
now := time.Unix(1000, 0).UTC()
sessions := domain.NewSessionStore()
session, token, err := sessions.Issue("player-a", time.Hour, now)
if err != nil {
t.Fatal(err)
}
other, otherToken, err := sessions.Issue("player-z", time.Hour, now)
if err != nil {
t.Fatal(err)
}
current := now
service := &Service{Sessions: sessions, Now: func() time.Time { return current }, Assignment: func(_ context.Context, _ string, matchID string, _ time.Time) (AssignmentView, error) {
return AssignmentView{MatchID: matchID, ServerID: "server-1", PlayerID: "player-a", Slot: 2, ExpiresAt: now.Add(time.Minute), ProtocolVersion: 1, Transport: "enet", JoinAuthorisation: "signed-join"}, nil
}}
server := httptest.NewServer(service.Handler())
defer server.Close()
get := func(path string) (int, AssignmentView) {
req, _ := http.NewRequest(http.MethodGet, server.URL+path, nil)
req.Header.Set("Authorization", "Bearer "+session.SessionID+":"+token)
response, requestErr := http.DefaultClient.Do(req)
if requestErr != nil {
t.Fatal(requestErr)
}
defer response.Body.Close()
var view AssignmentView
if response.StatusCode == http.StatusOK {
if err := json.NewDecoder(response.Body).Decode(&view); err != nil {
t.Fatal(err)
}
}
return response.StatusCode, view
}
status, view := get("/v1/assignments/match-1")
if status != http.StatusOK || view.PlayerID != "player-a" || view.Slot != 2 {
t.Fatalf("assignment status=%d view=%+v", status, view)
}
req, _ := http.NewRequest(http.MethodGet, server.URL+"/v1/assignments/match-1", nil)
req.Header.Set("Authorization", "Bearer "+other.SessionID+":"+otherToken)
response, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
if response.StatusCode != http.StatusNotFound {
t.Fatalf("misbound assignment status=%d, want 404", response.StatusCode)
}
response.Body.Close()
status, _ = get("/v1/assignments/")
if status != http.StatusNotFound {
t.Fatalf("malformed assignment path status=%d", status)
}
current = now.Add(time.Minute)
status, _ = get("/v1/assignments/match-1")
if status != http.StatusServiceUnavailable {
t.Fatalf("expired assignment status=%d", status)
}
}