From 1a43a342eaf3fb72e232bbd219b6a51391e2108a Mon Sep 17 00:00:00 2001 From: Josh Creek <8179928+jcreek@users.noreply.github.com> Date: Mon, 31 Aug 2026 22:18:59 +0100 Subject: [PATCH] test: verify persistent queue API delegation --- server/api/service_test.go | 61 +++++++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 7 deletions(-) diff --git a/server/api/service_test.go b/server/api/service_test.go index 10afa3d6..5d848eb7 100644 --- a/server/api/service_test.go +++ b/server/api/service_test.go @@ -12,20 +12,23 @@ import ( "github.com/cosmic-clash/cosmic-clash/server/domain" ) -type queueBackendSpy struct{ createCalls int } +type queueBackendSpy struct{ createCalls, heartbeatCalls, cancelCalls, getCalls int } func (b *queueBackendSpy) Create(_ context.Context, playerID, ticketID, _ string, spec domain.QueueSpec, now time.Time) (domain.QueueTicket, error) { b.createCalls++ return domain.QueueTicket{TicketID: ticketID, PlayerID: playerID, Playlist: spec.Playlist, State: domain.Queued, EnqueuedAt: now, ExpiresAt: now.Add(domain.QueueExpiryWindow)}, nil } -func (*queueBackendSpy) Heartbeat(context.Context, string, string, string, uint64, time.Time) (domain.QueueTicket, error) { - return domain.QueueTicket{}, nil +func (b *queueBackendSpy) Heartbeat(_ context.Context, playerID, ticketID, _ string, revision uint64, now time.Time) (domain.QueueTicket, error) { + b.heartbeatCalls++ + return domain.QueueTicket{TicketID: ticketID, PlayerID: playerID, State: domain.Queued, Revision: revision + 1, EnqueuedAt: now, ExpiresAt: now.Add(domain.QueueExpiryWindow)}, nil } -func (*queueBackendSpy) Cancel(context.Context, string, string, string, uint64, time.Time) (domain.QueueTicket, error) { - return domain.QueueTicket{}, nil +func (b *queueBackendSpy) Cancel(_ context.Context, playerID, ticketID, _ string, revision uint64, now time.Time) (domain.QueueTicket, error) { + b.cancelCalls++ + return domain.QueueTicket{TicketID: ticketID, PlayerID: playerID, State: domain.Cancelled, Revision: revision + 1, EnqueuedAt: now, ExpiresAt: now}, nil } -func (*queueBackendSpy) Get(context.Context, string, string, time.Time) (domain.QueueTicket, error) { - return domain.QueueTicket{}, nil +func (b *queueBackendSpy) Get(_ context.Context, playerID, ticketID string, now time.Time) (domain.QueueTicket, error) { + b.getCalls++ + return domain.QueueTicket{TicketID: ticketID, PlayerID: playerID, State: domain.Queued, EnqueuedAt: now, ExpiresAt: now.Add(domain.QueueExpiryWindow)}, nil } func TestAuthenticatedQueueAPIUsesServerCandidateAndRevisionedMutations(t *testing.T) { @@ -222,6 +225,50 @@ func TestQueueAPIUsesInjectedPersistentBackendWithoutCandidateProvider(t *testin } } +func TestQueueAPIDelegatesAllMutationsAndRecoveryToBackend(t *testing.T) { + now := time.Unix(1000, 0).UTC() + sessions := domain.NewSessionStore() + session, token, _ := sessions.Issue("player-1", time.Hour, now) + backend := &queueBackendSpy{} + service := &Service{Sessions: sessions, QueueBackend: backend, Now: func() time.Time { return now }} + server := httptest.NewServer(service.Handler()) + defer server.Close() + auth := "Bearer " + session.SessionID + ":" + token + request := func(method, path, body, key, revision string) *http.Response { + req, _ := http.NewRequest(method, server.URL+path, strings.NewReader(body)) + req.Header.Set("Authorization", auth) + if key != "" { + req.Header.Set("Idempotency-Key", key) + } + if revision != "" { + req.Header.Set("If-Match-Revision", revision) + } + response, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatal(err) + } + return response + } + response := request(http.MethodGet, "/v1/queue/ticket-1", "", "", "") + if response.StatusCode != http.StatusOK { + t.Fatalf("get status = %d", response.StatusCode) + } + response.Body.Close() + response = request(http.MethodPost, "/v1/queue/ticket-1/heartbeat", `{}`, "heartbeat-key-123456", "0") + if response.StatusCode != http.StatusOK { + t.Fatalf("heartbeat status = %d", response.StatusCode) + } + response.Body.Close() + response = request(http.MethodPost, "/v1/queue/ticket-1/cancel", `{}`, "cancel-key-123456", "1") + if response.StatusCode != http.StatusOK { + t.Fatalf("cancel status = %d", response.StatusCode) + } + response.Body.Close() + if backend.getCalls != 1 || backend.heartbeatCalls != 1 || backend.cancelCalls != 1 { + t.Fatalf("backend calls = %+v", backend) + } +} + func TestQueueRecoveryAPIIsAuthenticatedOwnerOnlyAndExpiresStaleTickets(t *testing.T) { now := time.Unix(1000, 0).UTC() sessions := domain.NewSessionStore()