feat: game lobby service

This commit is contained in:
Ilia Denisov
2026-04-25 23:20:55 +02:00
committed by GitHub
parent 32dc29359a
commit 48b0056b49
336 changed files with 57074 additions and 1418 deletions
@@ -0,0 +1,177 @@
package gmclient_test
import (
"context"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"testing"
"time"
"galaxy/lobby/internal/adapters/gmclient"
"galaxy/lobby/internal/domain/common"
"galaxy/lobby/internal/ports"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func validRequest() ports.RegisterGameRequest {
return ports.RegisterGameRequest{
GameID: common.GameID("game-1"),
ContainerID: "container-1",
EngineEndpoint: "engine.local:9000",
TargetEngineVersion: "v1.2.3",
TurnSchedule: "0 18 * * *",
}
}
func TestNewClientValidatesConfig(t *testing.T) {
_, err := gmclient.NewClient(gmclient.Config{Timeout: time.Second})
require.Error(t, err)
_, err = gmclient.NewClient(gmclient.Config{BaseURL: "http://gm.local"})
require.Error(t, err)
}
func TestRegisterGameSendsExpectedRequest(t *testing.T) {
var observed struct {
method string
path string
contentType string
body []byte
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
observed.method = r.Method
observed.path = r.URL.Path
observed.contentType = r.Header.Get("Content-Type")
observed.body, _ = io.ReadAll(r.Body)
w.WriteHeader(http.StatusOK)
}))
t.Cleanup(server.Close)
client, err := gmclient.NewClient(gmclient.Config{BaseURL: server.URL, Timeout: time.Second})
require.NoError(t, err)
require.NoError(t, client.RegisterGame(context.Background(), validRequest()))
assert.Equal(t, http.MethodPost, observed.method)
assert.Equal(t, "/api/v1/internal/games/game-1/register-runtime", observed.path)
assert.Equal(t, "application/json", observed.contentType)
var decoded map[string]string
require.NoError(t, json.Unmarshal(observed.body, &decoded))
assert.Equal(t, "container-1", decoded["container_id"])
assert.Equal(t, "engine.local:9000", decoded["engine_endpoint"])
assert.Equal(t, "v1.2.3", decoded["target_engine_version"])
assert.Equal(t, "0 18 * * *", decoded["turn_schedule"])
}
func TestRegisterGameWrapsServerErrorWithUnavailable(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}))
t.Cleanup(server.Close)
client, err := gmclient.NewClient(gmclient.Config{BaseURL: server.URL, Timeout: time.Second})
require.NoError(t, err)
err = client.RegisterGame(context.Background(), validRequest())
require.Error(t, err)
assert.ErrorIs(t, err, ports.ErrGMUnavailable)
}
func TestRegisterGameWrapsTimeoutWithUnavailable(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
select {
case <-r.Context().Done():
case <-time.After(200 * time.Millisecond):
}
w.WriteHeader(http.StatusOK)
}))
t.Cleanup(server.Close)
client, err := gmclient.NewClient(gmclient.Config{BaseURL: server.URL, Timeout: 10 * time.Millisecond})
require.NoError(t, err)
err = client.RegisterGame(context.Background(), validRequest())
require.Error(t, err)
assert.ErrorIs(t, err, ports.ErrGMUnavailable)
}
func TestPingHitsExpectedEndpoint(t *testing.T) {
var observed struct {
method string
path string
accept string
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
observed.method = r.Method
observed.path = r.URL.Path
observed.accept = r.Header.Get("Accept")
w.WriteHeader(http.StatusOK)
}))
t.Cleanup(server.Close)
client, err := gmclient.NewClient(gmclient.Config{BaseURL: server.URL, Timeout: time.Second})
require.NoError(t, err)
require.NoError(t, client.Ping(context.Background()))
assert.Equal(t, http.MethodGet, observed.method)
assert.Equal(t, "/api/v1/internal/healthz", observed.path)
assert.Equal(t, "application/json", observed.accept)
}
func TestPingWrapsServerErrorWithUnavailable(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusServiceUnavailable)
}))
t.Cleanup(server.Close)
client, err := gmclient.NewClient(gmclient.Config{BaseURL: server.URL, Timeout: time.Second})
require.NoError(t, err)
err = client.Ping(context.Background())
require.Error(t, err)
assert.ErrorIs(t, err, ports.ErrGMUnavailable)
}
func TestPingWrapsTimeoutWithUnavailable(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
select {
case <-r.Context().Done():
case <-time.After(200 * time.Millisecond):
}
w.WriteHeader(http.StatusOK)
}))
t.Cleanup(server.Close)
client, err := gmclient.NewClient(gmclient.Config{BaseURL: server.URL, Timeout: 10 * time.Millisecond})
require.NoError(t, err)
err = client.Ping(context.Background())
require.Error(t, err)
assert.ErrorIs(t, err, ports.ErrGMUnavailable)
}
func TestRegisterGameValidatesRequest(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
}))
t.Cleanup(server.Close)
client, err := gmclient.NewClient(gmclient.Config{BaseURL: server.URL, Timeout: time.Second})
require.NoError(t, err)
bad := validRequest()
bad.ContainerID = ""
err = client.RegisterGame(context.Background(), bad)
require.Error(t, err)
bad = validRequest()
bad.GameID = common.GameID("bogus")
err = client.RegisterGame(context.Background(), bad)
require.Error(t, err)
}