157 lines
5.3 KiB
Go
157 lines
5.3 KiB
Go
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())
|
|
}
|