feat: gamemaster
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
package rtmclient
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"galaxy/gamemaster/internal/ports"
|
||||
)
|
||||
|
||||
func newTestClient(t *testing.T, baseURL string, timeout time.Duration) *Client {
|
||||
t.Helper()
|
||||
client, err := NewClient(Config{BaseURL: baseURL, RequestTimeout: timeout})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { _ = client.Close() })
|
||||
return client
|
||||
}
|
||||
|
||||
func TestNewClientValidatesConfig(t *testing.T) {
|
||||
cases := map[string]Config{
|
||||
"empty base url": {BaseURL: "", RequestTimeout: time.Second},
|
||||
"non-absolute": {BaseURL: "rtm:8096", RequestTimeout: time.Second},
|
||||
"zero timeout": {BaseURL: "http://rtm:8096", RequestTimeout: 0},
|
||||
"negative timeout": {BaseURL: "http://rtm:8096", RequestTimeout: -time.Second},
|
||||
}
|
||||
for name, cfg := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
_, err := NewClient(cfg)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStopHappyPath(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(t, http.MethodPost, r.Method)
|
||||
require.Equal(t, "/api/v1/internal/runtimes/game-1/stop", r.URL.Path)
|
||||
require.Equal(t, "application/json", r.Header.Get("Content-Type"))
|
||||
body, err := io.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
var got stopRequestEnvelope
|
||||
require.NoError(t, json.Unmarshal(body, &got))
|
||||
assert.Equal(t, "admin_request", got.Reason)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"game_id":"game-1","status":"stopped"}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := newTestClient(t, server.URL, time.Second)
|
||||
require.NoError(t, client.Stop(context.Background(), "game-1", "admin_request"))
|
||||
}
|
||||
|
||||
func TestStopRejectsBadInput(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
t.Fatal("must not contact rtm on bad input")
|
||||
}))
|
||||
defer server.Close()
|
||||
client := newTestClient(t, server.URL, time.Second)
|
||||
|
||||
require.Error(t, client.Stop(context.Background(), " ", "admin_request"))
|
||||
require.Error(t, client.Stop(context.Background(), "g", " "))
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
err := client.Stop(ctx, "g", "admin_request")
|
||||
require.Error(t, err)
|
||||
assert.True(t, errors.Is(err, context.Canceled))
|
||||
}
|
||||
|
||||
func TestStopInternalError(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = w.Write([]byte(`{"error":{"code":"internal_error","message":"boom"}}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := newTestClient(t, server.URL, time.Second)
|
||||
err := client.Stop(context.Background(), "g", "admin_request")
|
||||
require.Error(t, err)
|
||||
assert.True(t, errors.Is(err, ports.ErrRTMUnavailable))
|
||||
assert.Contains(t, err.Error(), "internal_error")
|
||||
}
|
||||
|
||||
func TestStopTimeoutMapsToUnavailable(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
time.Sleep(120 * time.Millisecond)
|
||||
_, _ = w.Write([]byte(`{}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := newTestClient(t, server.URL, 30*time.Millisecond)
|
||||
err := client.Stop(context.Background(), "g", "admin_request")
|
||||
require.Error(t, err)
|
||||
assert.True(t, errors.Is(err, ports.ErrRTMUnavailable))
|
||||
}
|
||||
|
||||
func TestPatchHappyPath(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(t, http.MethodPost, r.Method)
|
||||
require.Equal(t, "/api/v1/internal/runtimes/g/patch", r.URL.Path)
|
||||
body, err := io.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
var got patchRequestEnvelope
|
||||
require.NoError(t, json.Unmarshal(body, &got))
|
||||
assert.Equal(t, "galaxy/game:1.2.4", got.ImageRef)
|
||||
_, _ = w.Write([]byte(`{"game_id":"g","status":"running"}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := newTestClient(t, server.URL, time.Second)
|
||||
require.NoError(t, client.Patch(context.Background(), "g", "galaxy/game:1.2.4"))
|
||||
}
|
||||
|
||||
func TestPatchSemverConflictMapsToUnavailable(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusConflict)
|
||||
_, _ = w.Write([]byte(`{"error":{"code":"semver_patch_only","message":"cross-major patch not allowed"}}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := newTestClient(t, server.URL, time.Second)
|
||||
err := client.Patch(context.Background(), "g", "galaxy/game:2.0.0")
|
||||
require.Error(t, err)
|
||||
assert.True(t, errors.Is(err, ports.ErrRTMUnavailable))
|
||||
assert.Contains(t, err.Error(), "semver_patch_only")
|
||||
}
|
||||
|
||||
func TestPatchRejectsBadInput(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
t.Fatal("must not contact rtm on bad input")
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := newTestClient(t, server.URL, time.Second)
|
||||
require.Error(t, client.Patch(context.Background(), " ", "galaxy/game:1.0.0"))
|
||||
require.Error(t, client.Patch(context.Background(), "g", " "))
|
||||
}
|
||||
|
||||
func TestCloseIsIdempotent(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
_, _ = w.Write([]byte(`{}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
client := newTestClient(t, server.URL, time.Second)
|
||||
require.NoError(t, client.Stop(context.Background(), "g", "admin_request"))
|
||||
require.NoError(t, client.Close())
|
||||
require.NoError(t, client.Close())
|
||||
}
|
||||
Reference in New Issue
Block a user