feat: add authenticated queue HTTP API

This commit is contained in:
Josh Creek
2026-08-31 21:13:10 +01:00
parent caa875f15c
commit cd30098eca
3 changed files with 305 additions and 1 deletions
+102
View File
@@ -0,0 +1,102 @@
package api
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/cosmic-clash/cosmic-clash/server/domain"
)
func TestAuthenticatedQueueAPIUsesServerCandidateAndRevisionedMutations(t *testing.T) {
now := time.Unix(1000, 0).UTC()
sessions := domain.NewSessionStore()
session, token, err := sessions.Issue("player-1", time.Hour, now)
if err != nil {
t.Fatal(err)
}
queue := domain.NewQueue()
service := &Service{Sessions: sessions, Queue: queue, Now: func() time.Time { return now }, Candidate: func(playerID, ticketID string) (domain.Candidate, error) {
return domain.Candidate{PlayerID: playerID, TicketID: ticketID, EnqueuedAt: now, PredictedRTT: map[string]float64{"EU": 20}}, nil
}}
server := httptest.NewServer(service.Handler())
defer server.Close()
request := func(method, path, body string, headers map[string]string) *http.Response {
req, _ := http.NewRequest(method, server.URL+path, strings.NewReader(body))
for key, value := range headers {
req.Header.Set(key, value)
}
response, requestErr := http.DefaultClient.Do(req)
if requestErr != nil {
t.Fatal(requestErr)
}
return response
}
headers := map[string]string{"Authorization": "Bearer " + session.SessionID + ":" + token, "Idempotency-Key": "create-key-123456"}
response := request(http.MethodPost, "/v1/queue", `{"ticket_id":"ticket-1"}`, headers)
if response.StatusCode != http.StatusCreated {
t.Fatalf("create status = %d", response.StatusCode)
}
var created queueResponse
if err := json.NewDecoder(response.Body).Decode(&created); err != nil {
t.Fatal(err)
}
_ = response.Body.Close()
if created.PlayerID != "player-1" || created.State != "QUEUED" || created.Revision != 0 {
t.Fatalf("created = %+v", created)
}
response = request(http.MethodPost, "/v1/queue/ticket-1/heartbeat", `{}`, map[string]string{"Authorization": headers["Authorization"], "Idempotency-Key": "heartbeat-key-123456", "If-Match-Revision": "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", `{}`, map[string]string{"Authorization": headers["Authorization"], "Idempotency-Key": "cancel-key-123456", "If-Match-Revision": "0"})
if response.StatusCode != http.StatusConflict {
t.Fatalf("stale cancel status = %d", response.StatusCode)
}
_ = response.Body.Close()
}
func TestQueueAPIRejectsUnauthenticatedUnknownAndOversizedInput(t *testing.T) {
service := &Service{Sessions: domain.NewSessionStore(), Queue: domain.NewQueue(), Candidate: func(string, string) (domain.Candidate, error) { return domain.Candidate{}, nil }}
server := httptest.NewServer(service.Handler())
defer server.Close()
request, _ := http.NewRequest(http.MethodPost, server.URL+"/v1/queue", strings.NewReader(`{"ticket_id":"ticket-1","player_id":"attacker"}`))
request.Header.Set("Idempotency-Key", "create-key-123456")
response, err := http.DefaultClient.Do(request)
if err != nil {
t.Fatal(err)
}
if response.StatusCode != http.StatusUnauthorized {
t.Fatalf("unauthenticated status = %d", response.StatusCode)
}
_ = response.Body.Close()
sessionStore := domain.NewSessionStore()
session, token, _ := sessionStore.Issue("player-1", time.Hour, time.Now())
service.Sessions = sessionStore
request, _ = http.NewRequest(http.MethodPost, server.URL+"/v1/queue", strings.NewReader(`{"ticket_id":"ticket-1","unknown":true}`))
request.Header.Set("Authorization", "Bearer "+session.SessionID+":"+token)
request.Header.Set("Idempotency-Key", "create-key-123456")
response, err = http.DefaultClient.Do(request)
if err != nil {
t.Fatal(err)
}
if response.StatusCode != http.StatusBadRequest {
t.Fatalf("unknown field status = %d", response.StatusCode)
}
_ = response.Body.Close()
request, _ = http.NewRequest(http.MethodPost, server.URL+"/v1/queue", strings.NewReader(`{"ticket_id":`))
request.Header.Set("Authorization", "Bearer "+session.SessionID+":"+token)
request.Header.Set("Idempotency-Key", "create-key-654321")
response, err = http.DefaultClient.Do(request)
if err != nil {
t.Fatal(err)
}
if response.StatusCode != http.StatusBadRequest {
t.Fatalf("malformed body status = %d", response.StatusCode)
}
_ = response.Body.Close()
}