2026-06-24 18:56:14 +08:00

235 lines
10 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}
}