mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-11 08:23:45 +00:00
feat: add authenticated matchmaking event stream
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
@@ -161,6 +166,97 @@ func TestDocumentedContractRoutesAdaptToServiceAPI(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthenticatedWebSocketDeliversOnlyTargetedRevisionedEvents(t *testing.T) {
|
||||
service := &Service{SessionBackend: &sessionBackendSpy{}}
|
||||
server := httptest.NewServer(service.Handler())
|
||||
defer server.Close()
|
||||
invalid, err := http.NewRequest(http.MethodGet, server.URL+"/v1/events", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
invalid.Header.Set("Upgrade", "websocket")
|
||||
invalid.Header.Set("Connection", "Upgrade")
|
||||
invalid.Header.Set("Sec-WebSocket-Key", "not-a-websocket-key")
|
||||
invalid.Header.Set("Authorization", "Bearer session-1:token-1")
|
||||
invalidResponse, err := server.Client().Do(invalid)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_ = invalidResponse.Body.Close()
|
||||
if invalidResponse.StatusCode != http.StatusBadRequest {
|
||||
t.Fatalf("invalid handshake status = %d", invalidResponse.StatusCode)
|
||||
}
|
||||
connection, err := net.Dial("tcp", strings.TrimPrefix(server.URL, "http://"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer connection.Close()
|
||||
_, err = io.WriteString(connection, "GET /v1/events HTTP/1.1\r\nHost: localhost\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nAuthorization: Bearer session-1:token-1\r\n\r\n")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
reader := bufio.NewReader(connection)
|
||||
status, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(status, "101 Switching Protocols") {
|
||||
t.Fatalf("handshake status = %q", status)
|
||||
}
|
||||
for {
|
||||
line, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if line == "\r\n" {
|
||||
break
|
||||
}
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
if err := service.PublishControlPlaneEvent(ControlPlaneEvent{Event: "state_changed", Revision: 1, ResourceID: "ticket-1234567890123456", OccurredAt: time.Unix(1000, 0).UTC(), State: "QUEUED", PlayerID: "player-1"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := service.PublishControlPlaneEvent(ControlPlaneEvent{Event: "state_changed", Revision: 2, ResourceID: "ticket-1234567890123456", OccurredAt: time.Unix(1001, 0).UTC(), State: "PROPOSED", PlayerID: "player-2"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
first, err := readServerWebSocketFrame(reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var event ControlPlaneEvent
|
||||
if err := json.Unmarshal(first, &event); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if event.PlayerID != "" || event.Revision != 1 || event.ResourceID != "ticket-1234567890123456" || event.State != "QUEUED" {
|
||||
t.Fatalf("event = %+v", event)
|
||||
}
|
||||
}
|
||||
|
||||
func readServerWebSocketFrame(reader *bufio.Reader) ([]byte, error) {
|
||||
first, err := reader.ReadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
second, err := reader.ReadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if first&0x0f != 0x1 || second&0x80 != 0 {
|
||||
return nil, errors.New("unexpected server websocket frame")
|
||||
}
|
||||
length := int(second & 0x7f)
|
||||
if length == 126 {
|
||||
var extended uint16
|
||||
if err := binary.Read(reader, binary.BigEndian, &extended); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
length = int(extended)
|
||||
}
|
||||
payload := make([]byte, length)
|
||||
_, err = io.ReadFull(reader, payload)
|
||||
return payload, err
|
||||
}
|
||||
|
||||
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())
|
||||
|
||||
Reference in New Issue
Block a user