2026-04-28 18:05:13 +08:00

392 lines
11 KiB
Go

package gameopen
import (
"chatapp3-golang/internal/config"
"chatapp3-golang/internal/integration"
"context"
"testing"
)
type stubGateway struct {
credential integration.UserCredential
token string
authToken string
profile integration.UserProfile
accountMap map[string]integration.UserProfile
balance int64
exists bool
cmd integration.GoldReceiptCommand
}
func (s *stubGateway) AuthenticateToken(_ context.Context, token string) (integration.UserCredential, error) {
s.authToken = token
return s.credential, nil
}
func (s *stubGateway) CreateIfAbsentUserCredentialToken(context.Context, int64) (string, error) {
return s.token, nil
}
func (s *stubGateway) GetUserProfile(context.Context, int64) (integration.UserProfile, error) {
return s.profile, nil
}
func (s *stubGateway) GetUserProfileByAccount(_ context.Context, _ string, account string) (integration.UserProfile, error) {
if profile, ok := s.accountMap[account]; ok {
return profile, nil
}
return integration.UserProfile{}, context.Canceled
}
func (s *stubGateway) GetUserProfileByAccountOne(_ context.Context, account string) (integration.UserProfile, error) {
if profile, ok := s.accountMap[account]; ok {
return profile, nil
}
return integration.UserProfile{}, context.Canceled
}
func (s *stubGateway) MapGoldBalance(context.Context, []int64) (map[int64]int64, error) {
result := map[int64]int64{}
if int64(s.credential.UserID) > 0 {
result[int64(s.credential.UserID)] = s.balance
}
if int64(s.profile.ID) > 0 {
result[int64(s.profile.ID)] = s.balance
}
for _, profile := range s.accountMap {
if int64(profile.ID) > 0 {
result[int64(profile.ID)] = s.balance
}
}
return result, nil
}
func (s *stubGateway) ExistsGoldEvent(context.Context, string) (bool, error) {
return s.exists, nil
}
func (s *stubGateway) ChangeGoldBalance(_ context.Context, cmd integration.GoldReceiptCommand) error {
s.cmd = cmd
return nil
}
func TestHandleQueryUser(t *testing.T) {
gateway := &stubGateway{
credential: integration.UserCredential{UserID: 1234567, SysOrigin: "LIKEI"},
profile: integration.UserProfile{
ID: 1234567,
Account: "1234567",
UserNickname: "tester",
UserAvatar: "https://example.com/avatar.png",
},
balance: 88,
}
service := NewGameOpenService(config.Config{
GameOpen: config.GameOpenConfig{AppKey: "game-open-test-key"},
}, nil, gateway)
req := QueryUserRequest{
GameID: "101",
UID: "1234567",
Token: "token",
RoomID: "room-1",
}
req.Sign = buildQueryUserSign(req, "game-open-test-key")
resp := service.HandleQueryUser(context.Background(), req, "{}")
if resp.ErrorCode != 0 {
t.Fatalf("HandleQueryUser() error = %+v", resp)
}
data, ok := resp.Data.(map[string]any)
if !ok {
t.Fatalf("HandleQueryUser() data type = %T", resp.Data)
}
if got := data["coin"]; got != int64(88) {
t.Fatalf("HandleQueryUser() coin = %v, want 88", got)
}
}
func TestHandleQueryUserNormalizesCallbackTokenAfterSignVerify(t *testing.T) {
tests := []struct {
name string
token string
want string
}{
{name: "missing_padding", token: "token-sign.YWJjZA", want: "token-sign.YWJjZA=="},
{name: "encoded_padding", token: "token-sign.YWJjZA%3D%3D", want: "token-sign.YWJjZA=="},
{name: "encoded_bearer_missing_padding", token: "Bearer%20token-sign.YWJjZA", want: "token-sign.YWJjZA=="},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gateway := &stubGateway{
credential: integration.UserCredential{UserID: 1234567, SysOrigin: "LIKEI"},
profile: integration.UserProfile{
ID: 1234567,
Account: "1234567",
},
balance: 88,
}
service := NewGameOpenService(config.Config{
GameOpen: config.GameOpenConfig{AppKey: "game-open-test-key"},
}, nil, gateway)
req := QueryUserRequest{
GameID: "101",
UID: "1234567",
Token: tt.token,
RoomID: "room-1",
}
req.Sign = buildQueryUserSign(req, "game-open-test-key")
resp := service.HandleQueryUser(context.Background(), req, "{}")
if resp.ErrorCode != 0 {
t.Fatalf("HandleQueryUser() error = %+v", resp)
}
if gateway.authToken != tt.want {
t.Fatalf("AuthenticateToken() token = %q, want %q", gateway.authToken, tt.want)
}
})
}
}
func TestHandleUpdateCoin(t *testing.T) {
gateway := &stubGateway{
credential: integration.UserCredential{UserID: 1234567, SysOrigin: "LIKEI"},
profile: integration.UserProfile{
ID: 1234567,
Account: "1234567",
},
balance: 99,
}
service := NewGameOpenService(config.Config{
GameOpen: config.GameOpenConfig{AppKey: "game-open-test-key"},
}, nil, gateway)
req := UpdateCoinRequest{
OrderID: "order-1",
GameID: "101",
RoundID: "round-1",
UID: "1234567",
Coin: 15,
Type: 2,
RewardType: 2,
Token: "token",
WinID: "win-1",
RoomID: "room-1",
}
req.Sign = buildUpdateCoinSign(req, "game-open-test-key")
resp := service.HandleUpdateCoin(context.Background(), req, "{}")
if resp.ErrorCode != 0 {
t.Fatalf("HandleUpdateCoin() error = %+v", resp)
}
if gateway.cmd.EventID != "GAME_OPEN:order-1" {
t.Fatalf("HandleUpdateCoin() event id = %q", gateway.cmd.EventID)
}
if gateway.cmd.ReceiptType != "INCOME" {
t.Fatalf("HandleUpdateCoin() receipt type = %q", gateway.cmd.ReceiptType)
}
}
func TestHandleUpdateCoinNormalizesCallbackTokenAfterSignVerify(t *testing.T) {
gateway := &stubGateway{
credential: integration.UserCredential{UserID: 1234567, SysOrigin: "LIKEI"},
profile: integration.UserProfile{
ID: 1234567,
Account: "1234567",
},
balance: 99,
}
service := NewGameOpenService(config.Config{
GameOpen: config.GameOpenConfig{AppKey: "game-open-test-key"},
}, nil, gateway)
req := UpdateCoinRequest{
OrderID: "order-1",
GameID: "101",
RoundID: "round-1",
UID: "1234567",
Coin: 15,
Type: 2,
RewardType: 2,
Token: "Bearer%20token-sign.YWJjZA",
WinID: "win-1",
RoomID: "room-1",
}
req.Sign = buildUpdateCoinSign(req, "game-open-test-key")
resp := service.HandleUpdateCoin(context.Background(), req, "{}")
if resp.ErrorCode != 0 {
t.Fatalf("HandleUpdateCoin() error = %+v", resp)
}
if gateway.authToken != "token-sign.YWJjZA==" {
t.Fatalf("AuthenticateToken() token = %q, want token-sign.YWJjZA==", gateway.authToken)
}
}
func TestHandleUpdateCoinDuplicateOrderReturns10003(t *testing.T) {
gateway := &stubGateway{
credential: integration.UserCredential{UserID: 1234567, SysOrigin: "LIKEI"},
profile: integration.UserProfile{
ID: 1234567,
Account: "1234567",
},
balance: 99,
exists: true,
}
service := NewGameOpenService(config.Config{
GameOpen: config.GameOpenConfig{AppKey: "game-open-test-key"},
}, nil, gateway)
req := UpdateCoinRequest{
OrderID: "27822d8ba4a34d0e8db3e049716d427d",
GameID: "101",
RoundID: "round-1",
UID: "1234567",
Coin: 15,
Type: 2,
RewardType: 2,
Token: "token",
WinID: "win-1",
RoomID: "room-1",
}
req.Sign = buildUpdateCoinSign(req, "game-open-test-key")
resp := service.HandleUpdateCoin(context.Background(), req, "{}")
if resp.ErrorCode != gameOpenCodeDuplicateOrder {
t.Fatalf("HandleUpdateCoin() errorCode = %d, want %d", resp.ErrorCode, gameOpenCodeDuplicateOrder)
}
if resp.ErrorMsg != "duplicate orderId" {
t.Fatalf("HandleUpdateCoin() errorMsg = %q", resp.ErrorMsg)
}
if gateway.cmd.EventID != "" {
t.Fatalf("HandleUpdateCoin() should not change balance for duplicate order, got event id %q", gateway.cmd.EventID)
}
}
func TestHandleQueryUserAcceptsAccountUID(t *testing.T) {
gateway := &stubGateway{
credential: integration.UserCredential{UserID: 4569421795454615552, SysOrigin: "LIKEI"},
profile: integration.UserProfile{
ID: 4569421795454615552,
Account: "1234567",
UserNickname: "tester",
UserAvatar: "https://example.com/avatar.png",
},
accountMap: map[string]integration.UserProfile{
"1234567": {
ID: 4569421795454615552,
Account: "1234567",
UserNickname: "tester",
UserAvatar: "https://example.com/avatar.png",
},
},
balance: 88,
}
service := NewGameOpenService(config.Config{
GameOpen: config.GameOpenConfig{AppKey: "game-open-test-key"},
}, nil, gateway)
req := QueryUserRequest{
GameID: "101",
UID: "1234567",
Token: "token",
RoomID: "room-1",
}
req.Sign = buildQueryUserSign(req, "game-open-test-key")
resp := service.HandleQueryUser(context.Background(), req, "{}")
if resp.ErrorCode != 0 {
t.Fatalf("HandleQueryUser() error = %+v", resp)
}
}
func TestHandleUpdateCoinAcceptsAccountUID(t *testing.T) {
gateway := &stubGateway{
credential: integration.UserCredential{UserID: 4569421795454615552, SysOrigin: "LIKEI"},
accountMap: map[string]integration.UserProfile{
"1234567": {
ID: 4569421795454615552,
Account: "1234567",
},
},
balance: 99,
}
service := NewGameOpenService(config.Config{
GameOpen: config.GameOpenConfig{AppKey: "game-open-test-key"},
}, nil, gateway)
req := UpdateCoinRequest{
OrderID: "order-1",
GameID: "101",
RoundID: "round-1",
UID: "1234567",
Coin: 15,
Type: 2,
RewardType: 2,
Token: "token",
WinID: "win-1",
RoomID: "room-1",
}
req.Sign = buildUpdateCoinSign(req, "game-open-test-key")
resp := service.HandleUpdateCoin(context.Background(), req, "{}")
if resp.ErrorCode != 0 {
t.Fatalf("HandleUpdateCoin() error = %+v", resp)
}
}
func TestHandleSupplementAcceptsAccountUID(t *testing.T) {
gateway := &stubGateway{
accountMap: map[string]integration.UserProfile{
"1234567": {
ID: 4569421795454615552,
Account: "1234567",
},
},
balance: 66,
}
service := NewGameOpenService(config.Config{
GameOpen: config.GameOpenConfig{AppKey: "game-open-test-key"},
}, nil, gateway)
req := SupplementRequest{
OrderID: "order-1",
GameID: "101",
RoundID: "round-1",
UID: "1234567",
Coin: 1,
RewardType: 1,
WinID: "win-1",
RoomID: "room-1",
}
req.Sign = buildSupplementSign(req, "game-open-test-key")
resp := service.HandleSupplement(context.Background(), req, "{}")
if resp.ErrorCode != 0 {
t.Fatalf("HandleSupplement() error = %+v", resp)
}
data, ok := resp.Data.(map[string]any)
if !ok {
t.Fatalf("HandleSupplement() data type = %T", resp.Data)
}
if got := data["coin"]; got != int64(66) {
t.Fatalf("HandleSupplement() coin = %v, want 66", got)
}
}
func TestGetIntegrationInfo(t *testing.T) {
gateway := &stubGateway{token: "token-123"}
service := NewGameOpenService(config.Config{
GameOpen: config.GameOpenConfig{
AppKey: "game-open-test-key",
TestUserID: 1234567,
TestAccount: "1234567",
TestPassword: "1234567",
},
}, nil, gateway)
info, err := service.GetIntegrationInfo(context.Background(), "https://jvapi.haiyihy.com")
if err != nil {
t.Fatalf("GetIntegrationInfo() error = %v", err)
}
if info.QueryUser != "https://jvapi.haiyihy.com/game/open/user-info" {
t.Fatalf("GetIntegrationInfo() query url = %q", info.QueryUser)
}
if info.Token != "token-123" {
t.Fatalf("GetIntegrationInfo() token = %q", info.Token)
}
}