235 lines
10 KiB
Go
235 lines
10 KiB
Go
package gamemanagement
|
||
|
||
import (
|
||
"bytes"
|
||
"context"
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"strings"
|
||
"testing"
|
||
|
||
"hyapp-admin-server/internal/integration/activityclient"
|
||
"hyapp-admin-server/internal/integration/gameclient"
|
||
"hyapp-admin-server/internal/integration/userclient"
|
||
"hyapp-admin-server/internal/integration/walletclient"
|
||
"hyapp-admin-server/internal/middleware"
|
||
activityv1 "hyapp.local/api/proto/activity/v1"
|
||
gamev1 "hyapp.local/api/proto/game/v1"
|
||
walletv1 "hyapp.local/api/proto/wallet/v1"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
type fakeRobotProfileSource struct {
|
||
profiles []RobotProfile
|
||
requested int
|
||
}
|
||
|
||
func (s *fakeRobotProfileSource) RandomRobotProfiles(_ context.Context, count int) ([]RobotProfile, error) {
|
||
s.requested = count
|
||
return s.profiles[:count], nil
|
||
}
|
||
|
||
type fakeRobotUserClient struct {
|
||
userclient.Client
|
||
created []userclient.QuickCreateAccountRequest
|
||
}
|
||
|
||
func (c *fakeRobotUserClient) QuickCreateAccount(_ context.Context, req userclient.QuickCreateAccountRequest) (*userclient.QuickCreateAccountResult, error) {
|
||
c.created = append(c.created, req)
|
||
return &userclient.QuickCreateAccountResult{UserID: int64(9000 + len(c.created)), DisplayUserID: "robot"}, nil
|
||
}
|
||
|
||
func (c *fakeRobotUserClient) BatchGetUsers(_ context.Context, _ userclient.BatchGetUsersRequest) (map[int64]*userclient.User, error) {
|
||
users := make(map[int64]*userclient.User, len(c.created))
|
||
for index, created := range c.created {
|
||
userID := int64(9001 + index)
|
||
users[userID] = &userclient.User{UserID: userID, DisplayUserID: "robot", Username: created.Username, Avatar: created.Avatar}
|
||
}
|
||
return users, nil
|
||
}
|
||
|
||
type fakeRobotGameClient struct {
|
||
gameclient.Client
|
||
registered []int64
|
||
}
|
||
|
||
func (c *fakeRobotGameClient) RegisterDiceRobots(_ context.Context, req *gamev1.RegisterDiceRobotsRequest) (*gamev1.RegisterDiceRobotsResponse, error) {
|
||
c.registered = append(c.registered, req.GetUserIds()...)
|
||
robots := make([]*gamev1.DiceRobot, 0, len(req.GetUserIds()))
|
||
for _, userID := range req.GetUserIds() {
|
||
robots = append(robots, &gamev1.DiceRobot{AppCode: "lalu", GameId: "dice", UserId: userID, Status: "active"})
|
||
}
|
||
return &gamev1.RegisterDiceRobotsResponse{Robots: robots}, nil
|
||
}
|
||
|
||
type fakeRobotWalletClient struct {
|
||
walletclient.Client
|
||
listRequests []*walletv1.ListResourcesRequest
|
||
grants []int64
|
||
equips []int64
|
||
}
|
||
|
||
func (c *fakeRobotWalletClient) ListResources(_ context.Context, req *walletv1.ListResourcesRequest) (*walletv1.ListResourcesResponse, error) {
|
||
c.listRequests = append(c.listRequests, req)
|
||
switch req.GetResourceType() {
|
||
case "avatar_frame":
|
||
return &walletv1.ListResourcesResponse{Resources: []*walletv1.Resource{
|
||
{ResourceId: 165, ResourceType: "avatar_frame", Status: "active", ManagerGrantEnabled: false, CoinPrice: 1_000_000},
|
||
{ResourceId: 175, ResourceType: "avatar_frame", Status: "active", ManagerGrantEnabled: false, CoinPrice: 1_500_000},
|
||
{ResourceId: 101, ResourceType: "avatar_frame", Status: "active", ManagerGrantEnabled: false, CoinPrice: robotMaxAvatarFrameCoins},
|
||
{ResourceId: 176, ResourceType: "avatar_frame", Status: "active", ManagerGrantEnabled: false, CoinPrice: robotMaxAvatarFrameCoins + 1},
|
||
}}, nil
|
||
case "vehicle":
|
||
return &walletv1.ListResourcesResponse{Resources: []*walletv1.Resource{{ResourceId: 201, ResourceType: "vehicle", Status: "active", ManagerGrantEnabled: false}}}, nil
|
||
case "badge":
|
||
return &walletv1.ListResourcesResponse{Resources: []*walletv1.Resource{
|
||
{ResourceId: 301, ResourceCode: "event_host_strip", ResourceType: "badge", Name: "Host Badge", Status: "active", ManagerGrantEnabled: false, MetadataJson: `{"badge_form":"strip"}`},
|
||
{ResourceId: 302, ResourceCode: "activity_tile", ResourceType: "badge", Name: "Activity Badge", Status: "active", ManagerGrantEnabled: false, MetadataJson: `{"badge_form":"tile"}`},
|
||
{ResourceId: 303, ResourceCode: "VIP1_Long_Badge", ResourceType: "badge", Name: "VIP1_Long Badge", Status: "active", ManagerGrantEnabled: false, MetadataJson: `{"badge_form":"strip"}`},
|
||
{ResourceId: 304, ResourceCode: "vip1badge", ResourceType: "badge", Name: "VIP1badge", Status: "active", ManagerGrantEnabled: false, MetadataJson: `{"badge_form":"tile"}`},
|
||
}}, nil
|
||
default:
|
||
return &walletv1.ListResourcesResponse{}, nil
|
||
}
|
||
}
|
||
|
||
func (c *fakeRobotWalletClient) GrantResource(_ context.Context, req *walletv1.GrantResourceRequest) (*walletv1.ResourceGrantResponse, error) {
|
||
c.grants = append(c.grants, req.GetResourceId())
|
||
return &walletv1.ResourceGrantResponse{Grant: &walletv1.ResourceGrant{Items: []*walletv1.ResourceGrantItem{{
|
||
ResourceId: req.GetResourceId(),
|
||
EntitlementId: "entitlement",
|
||
}}}}, nil
|
||
}
|
||
|
||
func (c *fakeRobotWalletClient) EquipUserResource(_ context.Context, req *walletv1.EquipUserResourceRequest) (*walletv1.EquipUserResourceResponse, error) {
|
||
c.equips = append(c.equips, req.GetResourceId())
|
||
return &walletv1.EquipUserResourceResponse{}, nil
|
||
}
|
||
|
||
type fakeRobotActivityClient struct {
|
||
activityclient.Client
|
||
levels []*activityv1.SetUserLevelRequest
|
||
}
|
||
|
||
func (c *fakeRobotActivityClient) SetUserLevel(_ context.Context, req *activityv1.SetUserLevelRequest) (*activityv1.SetUserLevelResponse, error) {
|
||
c.levels = append(c.levels, req)
|
||
return &activityv1.SetUserLevelResponse{Status: "consumed", Track: req.GetTrack(), NewLevel: req.GetLevel()}, nil
|
||
}
|
||
|
||
func TestGenerateDiceRobotsUsesLikeiProfiles(t *testing.T) {
|
||
gin.SetMode(gin.TestMode)
|
||
profiles := &fakeRobotProfileSource{profiles: []RobotProfile{
|
||
{Nickname: "likei-one", Avatar: "https://media.haiyihy.com/avatar/one.jpg"},
|
||
{Nickname: "likei-two", Avatar: "https://media.haiyihy.com/avatar/two.jpg"},
|
||
}}
|
||
users := &fakeRobotUserClient{}
|
||
games := &fakeRobotGameClient{}
|
||
wallets := &fakeRobotWalletClient{}
|
||
activities := &fakeRobotActivityClient{}
|
||
handler := New(games, users, nil, WithRobotProfileSource(profiles), WithRobotAppearanceServices(wallets, activities))
|
||
|
||
recorder := httptest.NewRecorder()
|
||
c, _ := gin.CreateTestContext(recorder)
|
||
c.Set(middleware.ContextRequestID, "req-robot-test")
|
||
c.Set(middleware.ContextUserID, uint(7))
|
||
c.Request = httptest.NewRequest(http.MethodPost, "/api/v1/admin/games/self/dice/robots:generate", bytes.NewBufferString(`{
|
||
"count": 2,
|
||
"nicknameLanguage": "ar",
|
||
"avatarUrls": ["https://randomuser.me/api/portraits/men/1.jpg"],
|
||
"country": "SA"
|
||
}`))
|
||
c.Request.Header.Set("Content-Type", "application/json")
|
||
|
||
handler.GenerateDiceRobots(c)
|
||
|
||
if recorder.Code != http.StatusOK {
|
||
t.Fatalf("status = %d, body = %s", recorder.Code, recorder.Body.String())
|
||
}
|
||
if profiles.requested != 2 {
|
||
t.Fatalf("profile source requested %d profiles, want 2", profiles.requested)
|
||
}
|
||
if len(users.created) != 2 {
|
||
t.Fatalf("created users = %d, want 2", len(users.created))
|
||
}
|
||
if users.created[0].Username != "likei-one" || users.created[0].Avatar != "https://media.haiyihy.com/avatar/one.jpg" {
|
||
t.Fatalf("first robot profile = %#v", users.created[0])
|
||
}
|
||
if users.created[1].Username != "likei-two" || users.created[1].Avatar != "https://media.haiyihy.com/avatar/two.jpg" {
|
||
t.Fatalf("second robot profile = %#v", users.created[1])
|
||
}
|
||
if users.created[0].Language != "ar" || users.created[0].Source != "game_robot" || users.created[0].Country != "SA" {
|
||
t.Fatalf("robot account metadata = %#v", users.created[0])
|
||
}
|
||
if len(games.registered) != 2 || games.registered[0] != 9001 || games.registered[1] != 9002 {
|
||
t.Fatalf("registered user ids = %#v", games.registered)
|
||
}
|
||
if len(wallets.grants) != 8 || len(wallets.equips) != 4 || len(activities.levels) != 4 {
|
||
t.Fatalf("robot appearance init mismatch: grants=%d equips=%d levels=%d", len(wallets.grants), len(wallets.equips), len(activities.levels))
|
||
}
|
||
for _, req := range wallets.listRequests {
|
||
if req.GetManagerGrantOnly() {
|
||
t.Fatalf("robot appearance resource list must not require manager grant: %+v", req)
|
||
}
|
||
if !req.GetActiveOnly() {
|
||
t.Fatalf("robot appearance resource list must still require active resources: %+v", req)
|
||
}
|
||
}
|
||
for _, level := range activities.levels {
|
||
if level.GetLevel() < robotMinDisplayLevel || level.GetLevel() > robotMaxDisplayLevel {
|
||
t.Fatalf("robot level out of range: %+v", level)
|
||
}
|
||
}
|
||
for _, resourceID := range append(wallets.grants, wallets.equips...) {
|
||
// 165/175 是资源库里的有效头像框但属于运营保留资源;176 超过机器人头像框价格上限;
|
||
// 301/302 是非 VIP 徽章。创建机器人随机池必须同时排除这些资源,避免新账号继续带出旧规则。
|
||
switch resourceID {
|
||
case 165, 175, 176, 301, 302:
|
||
t.Fatalf("robot appearance must exclude resource %d from random grants/equips", resourceID)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestGenerateDiceRobotsFallsBackToRandomProfilesWhenLikeiMissing(t *testing.T) {
|
||
gin.SetMode(gin.TestMode)
|
||
users := &fakeRobotUserClient{}
|
||
games := &fakeRobotGameClient{}
|
||
handler := New(games, users, nil, WithRobotAppearanceServices(&fakeRobotWalletClient{}, &fakeRobotActivityClient{}))
|
||
|
||
recorder := httptest.NewRecorder()
|
||
c, _ := gin.CreateTestContext(recorder)
|
||
c.Set(middleware.ContextRequestID, "req-robot-random")
|
||
c.Set(middleware.ContextUserID, uint(7))
|
||
c.Request = httptest.NewRequest(http.MethodPost, "/api/v1/admin/games/self/dice/robots:generate", bytes.NewBufferString(`{
|
||
"count": 3,
|
||
"country": "TH"
|
||
}`))
|
||
c.Request.Header.Set("Content-Type", "application/json")
|
||
|
||
handler.GenerateDiceRobots(c)
|
||
|
||
if recorder.Code != http.StatusOK {
|
||
t.Fatalf("status = %d, body = %s", recorder.Code, recorder.Body.String())
|
||
}
|
||
if len(users.created) != 3 {
|
||
t.Fatalf("created users = %d, want 3", len(users.created))
|
||
}
|
||
for index, created := range users.created {
|
||
if !strings.HasPrefix(created.Username, "Robot ") {
|
||
t.Fatalf("created[%d] username should be generated, got %#v", index, created)
|
||
}
|
||
if !strings.HasPrefix(created.Avatar, "https://api.dicebear.com/9.x/adventurer/png?seed=hyapp-robot-") {
|
||
t.Fatalf("created[%d] avatar should be generated, got %#v", index, created)
|
||
}
|
||
if created.Gender != "male" && created.Gender != "female" {
|
||
t.Fatalf("created[%d] gender should be random male/female, got %#v", index, created)
|
||
}
|
||
if created.Country != "TH" || created.Source != "game_robot" {
|
||
t.Fatalf("created[%d] metadata = %#v", index, created)
|
||
}
|
||
}
|
||
if len(games.registered) != 3 || games.registered[0] != 9001 || games.registered[2] != 9003 {
|
||
t.Fatalf("registered user ids = %#v", games.registered)
|
||
}
|
||
}
|