package lobbyservice_test import ( "context" "encoding/json" "errors" "net/http" "net/http/httptest" "strings" "testing" "time" "galaxy/gateway/internal/downstream" "galaxy/gateway/internal/downstream/lobbyservice" lobbymodel "galaxy/model/lobby" "galaxy/transcoder" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestExecuteMyGamesListSuccess(t *testing.T) { t.Parallel() expectedResponse := lobbymodel.MyGamesListResponse{ Items: []lobbymodel.GameSummary{ { GameID: "game-1", GameName: "Nebula Clash", GameType: "private", Status: "draft", OwnerUserID: "user-1", MinPlayers: 2, MaxPlayers: 8, EnrollmentEndsAt: time.Date(2026, 5, 1, 12, 0, 0, 0, time.UTC), CreatedAt: time.Date(2026, 4, 28, 9, 0, 0, 0, time.UTC), UpdatedAt: time.Date(2026, 4, 28, 9, 5, 0, 0, time.UTC), }, }, } server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, http.MethodGet, r.Method) assert.Equal(t, "/api/v1/lobby/my/games", r.URL.Path) assert.Equal(t, "user-1", r.Header.Get("X-User-Id")) w.Header().Set("Content-Type", "application/json") require.NoError(t, json.NewEncoder(w).Encode(expectedResponse)) })) t.Cleanup(server.Close) client, err := lobbyservice.NewHTTPClient(server.URL) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, client.Close()) }) requestBytes, err := transcoder.MyGamesListRequestToPayload(&lobbymodel.MyGamesListRequest{}) require.NoError(t, err) result, err := client.ExecuteCommand(context.Background(), downstream.AuthenticatedCommand{ MessageType: lobbymodel.MessageTypeMyGamesList, UserID: "user-1", PayloadBytes: requestBytes, }) require.NoError(t, err) assert.Equal(t, "ok", result.ResultCode) decoded, err := transcoder.PayloadToMyGamesListResponse(result.PayloadBytes) require.NoError(t, err) require.Len(t, decoded.Items, 1) assert.Equal(t, expectedResponse.Items[0].GameID, decoded.Items[0].GameID) assert.Equal(t, expectedResponse.Items[0].OwnerUserID, decoded.Items[0].OwnerUserID) assert.Equal(t, expectedResponse.Items[0].MinPlayers, decoded.Items[0].MinPlayers) } func TestExecuteOpenEnrollmentSuccess(t *testing.T) { t.Parallel() server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, http.MethodPost, r.Method) assert.Equal(t, "/api/v1/lobby/games/game-77/open-enrollment", r.URL.Path) assert.Equal(t, "owner-1", r.Header.Get("X-User-Id")) w.Header().Set("Content-Type", "application/json") require.NoError(t, json.NewEncoder(w).Encode(map[string]any{ "game_id": "game-77", "status": "enrollment_open", })) })) t.Cleanup(server.Close) client, err := lobbyservice.NewHTTPClient(server.URL) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, client.Close()) }) requestBytes, err := transcoder.OpenEnrollmentRequestToPayload(&lobbymodel.OpenEnrollmentRequest{GameID: "game-77"}) require.NoError(t, err) result, err := client.ExecuteCommand(context.Background(), downstream.AuthenticatedCommand{ MessageType: lobbymodel.MessageTypeOpenEnrollment, UserID: "owner-1", PayloadBytes: requestBytes, }) require.NoError(t, err) assert.Equal(t, "ok", result.ResultCode) decoded, err := transcoder.PayloadToOpenEnrollmentResponse(result.PayloadBytes) require.NoError(t, err) assert.Equal(t, "game-77", decoded.GameID) assert.Equal(t, "enrollment_open", decoded.Status) } func TestExecuteOpenEnrollmentForbiddenProjectsErrorEnvelope(t *testing.T) { t.Parallel() server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusForbidden) require.NoError(t, json.NewEncoder(w).Encode(map[string]any{ "error": map[string]string{ "code": "forbidden", "message": "only the game owner may open enrollment", }, })) })) t.Cleanup(server.Close) client, err := lobbyservice.NewHTTPClient(server.URL) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, client.Close()) }) requestBytes, err := transcoder.OpenEnrollmentRequestToPayload(&lobbymodel.OpenEnrollmentRequest{GameID: "game-77"}) require.NoError(t, err) result, err := client.ExecuteCommand(context.Background(), downstream.AuthenticatedCommand{ MessageType: lobbymodel.MessageTypeOpenEnrollment, UserID: "non-owner", PayloadBytes: requestBytes, }) require.NoError(t, err) assert.Equal(t, "forbidden", result.ResultCode) decoded, err := transcoder.PayloadToLobbyErrorResponse(result.PayloadBytes) require.NoError(t, err) assert.Equal(t, "forbidden", decoded.Error.Code) assert.NotEmpty(t, decoded.Error.Message) } func TestExecuteCommandUnavailableProjectsErrUnavailable(t *testing.T) { t.Parallel() server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusServiceUnavailable) })) t.Cleanup(server.Close) client, err := lobbyservice.NewHTTPClient(server.URL) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, client.Close()) }) requestBytes, err := transcoder.MyGamesListRequestToPayload(&lobbymodel.MyGamesListRequest{}) require.NoError(t, err) _, err = client.ExecuteCommand(context.Background(), downstream.AuthenticatedCommand{ MessageType: lobbymodel.MessageTypeMyGamesList, UserID: "user-1", PayloadBytes: requestBytes, }) require.Error(t, err) assert.True(t, errors.Is(err, downstream.ErrDownstreamUnavailable)) } func TestExecuteCommandRejectsEmptyUserID(t *testing.T) { t.Parallel() client, err := lobbyservice.NewHTTPClient("http://127.0.0.1:1") require.NoError(t, err) t.Cleanup(func() { require.NoError(t, client.Close()) }) requestBytes, err := transcoder.MyGamesListRequestToPayload(&lobbymodel.MyGamesListRequest{}) require.NoError(t, err) _, err = client.ExecuteCommand(context.Background(), downstream.AuthenticatedCommand{ MessageType: lobbymodel.MessageTypeMyGamesList, UserID: "", PayloadBytes: requestBytes, }) require.Error(t, err) assert.True(t, strings.Contains(err.Error(), "user_id"), "error must mention user_id; got %q", err.Error()) } func TestNewRoutesReservesUnavailableClientWhenBaseURLEmpty(t *testing.T) { t.Parallel() routes, closeFn, err := lobbyservice.NewRoutes("") require.NoError(t, err) t.Cleanup(func() { require.NoError(t, closeFn()) }) require.Contains(t, routes, lobbymodel.MessageTypeMyGamesList) require.Contains(t, routes, lobbymodel.MessageTypeOpenEnrollment) requestBytes, err := transcoder.MyGamesListRequestToPayload(&lobbymodel.MyGamesListRequest{}) require.NoError(t, err) _, err = routes[lobbymodel.MessageTypeMyGamesList].ExecuteCommand( context.Background(), downstream.AuthenticatedCommand{ MessageType: lobbymodel.MessageTypeMyGamesList, UserID: "user-1", PayloadBytes: requestBytes, }, ) require.Error(t, err) assert.True(t, errors.Is(err, downstream.ErrDownstreamUnavailable)) }