package http import ( "bytes" "context" "encoding/json" "net/http" "net/http/httptest" "testing" gamev1 "hyapp.local/api/proto/game/v1" "hyapp/services/gateway-service/internal/auth" "hyapp/services/gateway-service/internal/transport/http/httpkit" ) func TestListGamesUsesAuthenticatedUserRegionAndReturnsEnvelope(t *testing.T) { gameClient := &fakeGatewayGameClient{listResp: &gamev1.ListGamesResponse{ Games: []*gamev1.AppGame{{ GameId: "demo_rocket_001", PlatformCode: "demo", NameKey: "game.demo_rocket_001.name", Name: "Rocket", LaunchMode: "h5_popup", Orientation: "portrait", Enabled: true, }}, ServerTimeMs: 1700000000000, }} handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{regionID: 1001}) handler.SetGameClient(gameClient) router := handler.Routes(auth.NewVerifier("secret")) request := httptest.NewRequest(http.MethodGet, "/api/v1/games?scene=voice_room&room_id=room-1", nil) request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) request.Header.Set("X-App-Language", "en") recorder := httptest.NewRecorder() router.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) } if gameClient.lastList.GetUserId() != 42 || gameClient.lastList.GetRegionId() != 1001 || gameClient.lastList.GetScene() != "voice_room" || gameClient.lastList.GetRoomId() != "room-1" { t.Fatalf("ListGames request mismatch: %+v", gameClient.lastList) } var envelope httpkit.ResponseEnvelope if err := json.NewDecoder(recorder.Body).Decode(&envelope); err != nil { t.Fatalf("decode response failed: %v", err) } data := envelope.Data.(map[string]any) games := data["games"].([]any) first := games[0].(map[string]any) if first["platform_code"] != "demo" || first["game_id"] != "demo_rocket_001" { t.Fatalf("game response mismatch: %+v", first) } } func TestLaunchGameForwardsDisplayUserID(t *testing.T) { gameClient := &fakeGatewayGameClient{launchResp: &gamev1.LaunchGameResponse{ SessionId: "game_sess_1", LaunchUrl: "https://provider.example/h5?session_token=token", ExpiresAtMs: 1700000900000, Orientation: "portrait", }} handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) handler.SetGameClient(gameClient) router := handler.Routes(auth.NewVerifier("secret")) accessToken := signGatewayToken(t, "secret", 42) request := httptest.NewRequest(http.MethodPost, "/api/v1/games/demo_rocket_001/launch", bytes.NewReader([]byte(`{"scene":"voice_room","room_id":"room-1"}`))) request.Header.Set("Authorization", "Bearer "+accessToken) recorder := httptest.NewRecorder() router.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) } if gameClient.lastLaunch.GetGameId() != "demo_rocket_001" || gameClient.lastLaunch.GetDisplayUserId() != "100001" || gameClient.lastLaunch.GetRoomId() != "room-1" || gameClient.lastLaunch.GetAccessToken() != accessToken { t.Fatalf("LaunchGame request mismatch: %+v", gameClient.lastLaunch) } } func TestGameCallbackIsRawPublicResponse(t *testing.T) { gameClient := &fakeGatewayGameClient{callbackResp: &gamev1.CallbackResponse{RawBody: []byte(`{"code":0}`), ContentType: "application/json"}} handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) handler.SetGameClient(gameClient) router := handler.Routes(auth.NewVerifier("secret")) request := httptest.NewRequest(http.MethodPost, "/api/v1/game-callbacks/demo/change_coin?nonce=1", bytes.NewReader([]byte(`{"provider_order_id":"p1"}`))) request.Header.Set("X-App-Code", "lalu") recorder := httptest.NewRecorder() router.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK || recorder.Body.String() != `{"code":0}` { t.Fatalf("callback response must not use app envelope: status=%d body=%s", recorder.Code, recorder.Body.String()) } if gameClient.lastCallback.GetPlatformCode() != "demo" || gameClient.lastCallback.GetOperation() != "change_coin" || string(gameClient.lastCallback.GetRawBody()) != `{"provider_order_id":"p1"}` || gameClient.lastCallback.GetQuery()["nonce"] != "1" { t.Fatalf("callback request mismatch: %+v", gameClient.lastCallback) } } type fakeGatewayGameClient struct { listResp *gamev1.ListGamesResponse launchResp *gamev1.LaunchGameResponse callbackResp *gamev1.CallbackResponse lastList *gamev1.ListGamesRequest lastLaunch *gamev1.LaunchGameRequest lastCallback *gamev1.CallbackRequest } func (f *fakeGatewayGameClient) ListGames(_ context.Context, req *gamev1.ListGamesRequest) (*gamev1.ListGamesResponse, error) { f.lastList = req if f.listResp != nil { return f.listResp, nil } return &gamev1.ListGamesResponse{}, nil } func (f *fakeGatewayGameClient) LaunchGame(_ context.Context, req *gamev1.LaunchGameRequest) (*gamev1.LaunchGameResponse, error) { f.lastLaunch = req if f.launchResp != nil { return f.launchResp, nil } return &gamev1.LaunchGameResponse{}, nil } func (f *fakeGatewayGameClient) HandleCallback(_ context.Context, req *gamev1.CallbackRequest) (*gamev1.CallbackResponse, error) { f.lastCallback = req if f.callbackResp != nil { return f.callbackResp, nil } return &gamev1.CallbackResponse{RawBody: []byte(`{"code":0}`), ContentType: "application/json"}, nil }