通用机器人
This commit is contained in:
parent
ff0e42e3d3
commit
25a3855454
31
scripts/mysql/045_remove_default_self_games.sql
Normal file
31
scripts/mysql/045_remove_default_self_games.sql
Normal file
@ -0,0 +1,31 @@
|
||||
SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
USE hyapp_game;
|
||||
|
||||
-- 清理旧初始化脚本写入的自研游戏展示入口;不删除对局、钱包、outbox 和奖池流水,避免破坏历史业务事实。
|
||||
DELETE FROM game_display_rules
|
||||
WHERE app_code = 'lalu'
|
||||
AND game_id IN ('dice', 'rock');
|
||||
|
||||
DELETE FROM game_self_game_configs
|
||||
WHERE app_code = 'lalu'
|
||||
AND game_id IN ('dice', 'rock');
|
||||
|
||||
DELETE FROM game_self_game_pools
|
||||
WHERE app_code = 'lalu'
|
||||
AND game_id IN ('dice', 'rock');
|
||||
|
||||
DELETE FROM game_catalog
|
||||
WHERE app_code = 'lalu'
|
||||
AND game_id IN ('dice', 'rock');
|
||||
|
||||
DELETE p
|
||||
FROM game_platforms p
|
||||
WHERE p.app_code = 'lalu'
|
||||
AND p.platform_code = 'dice'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM game_catalog c
|
||||
WHERE c.app_code = p.app_code
|
||||
AND c.platform_code = p.platform_code
|
||||
);
|
||||
@ -178,7 +178,7 @@ func (h *Handler) GenerateDiceRobots(c *gin.Context) {
|
||||
response.ServerError(c, "获取机器人用户资料失败")
|
||||
return
|
||||
}
|
||||
h.auditLog(c, "generate-dice-robots", "game_self_game_robots", "dice", fmt.Sprintf("count=%d", len(userIDs)))
|
||||
h.auditLog(c, "generate-dice-robots", "game_self_game_robots", "universal", fmt.Sprintf("count=%d", len(userIDs)))
|
||||
response.OK(c, gin.H{"created": len(userIDs), "items": items})
|
||||
}
|
||||
|
||||
|
||||
@ -4,45 +4,5 @@ CREATE DATABASE IF NOT EXISTS hyapp_game DEFAULT CHARACTER SET utf8mb4 COLLATE u
|
||||
|
||||
USE hyapp_game;
|
||||
|
||||
-- 自研游戏数据单独维护;schema 初始化只建表,默认数据在这里保证 dice 和 rock 在新环境中同时可用。
|
||||
SET @now_ms := CAST(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000 AS UNSIGNED);
|
||||
SET @self_game_stakes := CAST('[{"StakeCoin":100,"Enabled":true,"SortOrder":10},{"StakeCoin":500,"Enabled":true,"SortOrder":20},{"StakeCoin":1000,"Enabled":true,"SortOrder":30},{"StakeCoin":8000,"Enabled":true,"SortOrder":40}]' AS JSON);
|
||||
|
||||
INSERT INTO game_platforms (
|
||||
app_code, platform_code, platform_name, status, api_base_url, adapter_type,
|
||||
callback_secret_ciphertext, callback_ip_whitelist, adapter_config,
|
||||
sort_order, created_at_ms, updated_at_ms
|
||||
) VALUES (
|
||||
'lalu', 'dice', 'Dice Internal', 'active', '', 'dice_internal',
|
||||
'', CAST('[]' AS JSON), CAST('{}' AS JSON),
|
||||
0, @now_ms, @now_ms
|
||||
) ON DUPLICATE KEY UPDATE updated_at_ms = updated_at_ms;
|
||||
|
||||
INSERT INTO game_catalog (
|
||||
app_code, game_id, platform_code, provider_game_id, game_name, category, icon_url, cover_url,
|
||||
launch_mode, orientation, safe_height, min_coin, status, sort_order, tags, created_at_ms, updated_at_ms
|
||||
) VALUES
|
||||
('lalu', 'dice', 'dice', 'dice', 'Dice', 'self', '', '', 'h5_popup', 'portrait', 0, 0, 'active', 0, CAST('["self_game"]' AS JSON), @now_ms, @now_ms),
|
||||
('lalu', 'rock', 'dice', 'rock', 'Rock Paper Scissors', 'self', '', '', 'h5_popup', 'portrait', 0, 0, 'active', 1, CAST('["self_game"]' AS JSON), @now_ms, @now_ms)
|
||||
ON DUPLICATE KEY UPDATE updated_at_ms = updated_at_ms;
|
||||
|
||||
INSERT INTO game_display_rules (
|
||||
app_code, rule_id, game_id, scene, region_id, language, platform, visible, enabled, sort_order, created_at_ms, updated_at_ms
|
||||
) VALUES
|
||||
('lalu', 'dice_voice_default', 'dice', 'voice_room', 0, '', '', 1, 1, 0, @now_ms, @now_ms),
|
||||
('lalu', 'rock_voice_default', 'rock', 'voice_room', 0, '', '', 1, 1, 1, @now_ms, @now_ms)
|
||||
ON DUPLICATE KEY UPDATE updated_at_ms = updated_at_ms;
|
||||
|
||||
INSERT INTO game_self_game_configs (
|
||||
app_code, game_id, status, stake_options_json, fee_bps, pool_bps,
|
||||
min_players, max_players, robot_enabled, robot_match_wait_ms, created_at_ms, updated_at_ms
|
||||
) VALUES
|
||||
('lalu', 'dice', 'active', @self_game_stakes, 500, 100, 2, 2, 1, 1000, @now_ms, @now_ms),
|
||||
('lalu', 'rock', 'active', @self_game_stakes, 500, 100, 2, 2, 1, 1000, @now_ms, @now_ms)
|
||||
ON DUPLICATE KEY UPDATE updated_at_ms = updated_at_ms;
|
||||
|
||||
INSERT INTO game_self_game_pools (app_code, game_id, balance_coin, created_at_ms, updated_at_ms)
|
||||
VALUES
|
||||
('lalu', 'dice', 0, @now_ms, @now_ms),
|
||||
('lalu', 'rock', 0, @now_ms, @now_ms)
|
||||
ON DUPLICATE KEY UPDATE updated_at_ms = updated_at_ms;
|
||||
-- 自研游戏不再随数据库初始化自动写入;后台需要开放某个自研游戏时,应通过后台配置显式创建。
|
||||
SET @no_default_self_games := 1;
|
||||
|
||||
@ -267,7 +267,9 @@ func (r *Repository) CancelDiceMatch(ctx context.Context, appCode string, matchI
|
||||
return r.GetDiceMatch(ctx, app, match.MatchID)
|
||||
}
|
||||
|
||||
// PickActiveDiceRobot 从当前游戏的可用机器人池里随机挑选一个未加入本局的机器人。
|
||||
// PickActiveDiceRobot 从当前 App 的通用机器人池里随机挑选一个未加入本局的机器人。
|
||||
// game_self_game_robots 仍保留 game_id 字段是为了兼容旧数据和后台接口,但机器人账号本身不再按 dice/rock 隔离;
|
||||
// 只要同一个 app_code 下存在 active 登记,这个真实用户就可以给任意自研游戏补位。
|
||||
func (r *Repository) PickActiveDiceRobot(ctx context.Context, appCode string, gameID string, matchID string) (dicedomain.Robot, error) {
|
||||
app := appcode.Normalize(appCode)
|
||||
gameID = normalizeDiceGameID(gameID)
|
||||
@ -275,13 +277,17 @@ func (r *Repository) PickActiveDiceRobot(ctx context.Context, appCode string, ga
|
||||
var candidateCount int64
|
||||
if err := r.db.QueryRowContext(ctx,
|
||||
`SELECT COUNT(*)
|
||||
FROM game_self_game_robots r
|
||||
WHERE r.app_code = ? AND r.game_id = ? AND r.status = ?
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM game_dice_participants p
|
||||
WHERE p.app_code = r.app_code AND p.match_id = ? AND p.user_id = r.user_id
|
||||
)`,
|
||||
app, gameID, dicedomain.RobotStatusActive, matchID,
|
||||
FROM (
|
||||
SELECT r.user_id
|
||||
FROM game_self_game_robots r
|
||||
WHERE r.app_code = ? AND r.status = ?
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM game_dice_participants p
|
||||
WHERE p.app_code = r.app_code AND p.match_id = ? AND p.user_id = r.user_id
|
||||
)
|
||||
GROUP BY r.user_id
|
||||
) candidates`,
|
||||
app, dicedomain.RobotStatusActive, matchID,
|
||||
).Scan(&candidateCount); err != nil {
|
||||
return dicedomain.Robot{}, err
|
||||
}
|
||||
@ -294,16 +300,18 @@ func (r *Repository) PickActiveDiceRobot(ctx context.Context, appCode string, ga
|
||||
}
|
||||
var robot dicedomain.Robot
|
||||
err = r.db.QueryRowContext(ctx,
|
||||
`SELECT r.app_code, r.game_id, r.user_id, r.status, r.created_by_admin_id, r.created_at_ms, r.updated_at_ms
|
||||
`SELECT r.app_code, ? AS game_id, r.user_id, ? AS status,
|
||||
MIN(r.created_by_admin_id), MIN(r.created_at_ms), MAX(r.updated_at_ms)
|
||||
FROM game_self_game_robots r
|
||||
WHERE r.app_code = ? AND r.game_id = ? AND r.status = ?
|
||||
WHERE r.app_code = ? AND r.status = ?
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM game_dice_participants p
|
||||
WHERE p.app_code = r.app_code AND p.match_id = ? AND p.user_id = r.user_id
|
||||
)
|
||||
ORDER BY r.updated_at_ms ASC, r.user_id ASC
|
||||
GROUP BY r.app_code, r.user_id
|
||||
ORDER BY MAX(r.updated_at_ms) ASC, r.user_id ASC
|
||||
LIMIT 1 OFFSET ?`,
|
||||
app, gameID, dicedomain.RobotStatusActive, matchID, randomOffset,
|
||||
gameID, dicedomain.RobotStatusActive, app, dicedomain.RobotStatusActive, matchID, randomOffset,
|
||||
).Scan(&robot.AppCode, &robot.GameID, &robot.UserID, &robot.Status, &robot.CreatedByAdminID, &robot.CreatedAtMS, &robot.UpdatedAtMS)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
@ -413,9 +421,9 @@ func (r *Repository) AdjustDicePool(ctx context.Context, adjustment dicedomain.P
|
||||
if _, err := tx.ExecContext(ctx, `
|
||||
INSERT INTO game_outbox (
|
||||
app_code, event_id, event_type, order_id, user_id, platform_code, game_id,
|
||||
op_type, coin_amount, payload_json, status, attempts, next_retry_at_ms,
|
||||
created_at_ms, updated_at_ms, published_at_ms
|
||||
) VALUES (?, ?, ?, '', ?, 'self', ?, '', 0, CAST(? AS JSON), 'pending', 0, 0, ?, ?, 0)
|
||||
op_type, coin_amount, payload_json, status, worker_id, lock_until_ms,
|
||||
retry_count, next_retry_at_ms, last_error, created_at_ms, updated_at_ms
|
||||
) VALUES (?, ?, ?, '', ?, 'self', ?, '', 0, CAST(? AS JSON), 'pending', '', 0, 0, 0, '', ?, ?)
|
||||
ON DUPLICATE KEY UPDATE updated_at_ms = VALUES(updated_at_ms)`,
|
||||
adjustment.AppCode, gamemq.EventTypeSelfGamePoolAdjusted+":"+adjustment.AdjustmentID, gamemq.EventTypeSelfGamePoolAdjusted,
|
||||
adjustment.UserID, adjustment.GameID, string(payload), adjustment.CreatedAtMS, adjustment.CreatedAtMS,
|
||||
@ -439,19 +447,27 @@ func (r *Repository) ListDiceRobots(ctx context.Context, appCode string, gameID
|
||||
if strings.TrimSpace(cursor) != "" {
|
||||
_, _ = fmt.Sscan(strings.TrimSpace(cursor), &cursorID)
|
||||
}
|
||||
args := []any{app, gameID}
|
||||
// 机器人后台展示的是“账号池”而不是“游戏池”;同一个 user_id 即使历史上登记过多个 game_id,也只展示一行。
|
||||
// 聚合状态按 active 优先,避免旧的 dice active + rock disabled 脏数据让同一个账号在列表里出现两种状态。
|
||||
args := []any{gameID, dicedomain.RobotStatusActive, dicedomain.RobotStatusActive, dicedomain.RobotStatusDisabled, app}
|
||||
query := `SELECT app_code, game_id, user_id, status, created_by_admin_id, created_at_ms, updated_at_ms
|
||||
FROM game_self_game_robots
|
||||
WHERE app_code = ? AND game_id = ?`
|
||||
if status != "" {
|
||||
query += ` AND status = ?`
|
||||
args = append(args, status)
|
||||
}
|
||||
FROM (
|
||||
SELECT app_code, ? AS game_id, user_id,
|
||||
IF(SUM(status = ?) > 0, ?, ?) AS status,
|
||||
MIN(created_by_admin_id) AS created_by_admin_id,
|
||||
MIN(created_at_ms) AS created_at_ms,
|
||||
MAX(updated_at_ms) AS updated_at_ms
|
||||
FROM game_self_game_robots
|
||||
WHERE app_code = ?`
|
||||
if cursorID > 0 {
|
||||
query += ` AND user_id > ?`
|
||||
args = append(args, cursorID)
|
||||
}
|
||||
query += ` ORDER BY user_id ASC LIMIT ?`
|
||||
query += ` GROUP BY app_code, user_id
|
||||
) robots
|
||||
WHERE (? = '' OR status = ?)
|
||||
ORDER BY user_id ASC LIMIT ?`
|
||||
args = append(args, status, status)
|
||||
args = append(args, int(pageSize)+1)
|
||||
rows, err := r.db.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
@ -581,7 +597,8 @@ func (r *Repository) ListExploreWinners(ctx context.Context, appCode string, pag
|
||||
|
||||
func (r *Repository) RegisterDiceRobots(ctx context.Context, appCode string, gameID string, userIDs []int64, adminID int64, nowMs int64) ([]dicedomain.Robot, error) {
|
||||
app := appcode.Normalize(appCode)
|
||||
gameID = normalizeDiceGameID(gameID)
|
||||
// 新生成的机器人只写入默认 game_id 行;匹配和后台管理都已经按 app_code + user_id 聚合,这行只是兼容当前表主键。
|
||||
gameID = dicedomain.DefaultGameID
|
||||
tx, err := r.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -601,7 +618,7 @@ func (r *Repository) RegisterDiceRobots(ctx context.Context, appCode string, gam
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
robots, _, err := r.ListDiceRobots(ctx, app, gameID, "", 200, "")
|
||||
robots, _, err := r.ListDiceRobots(ctx, app, "", "", 200, "")
|
||||
return robots, err
|
||||
}
|
||||
|
||||
@ -615,8 +632,8 @@ func (r *Repository) SetDiceRobotStatus(ctx context.Context, appCode string, gam
|
||||
res, err := r.db.ExecContext(ctx,
|
||||
`UPDATE game_self_game_robots
|
||||
SET status = ?, updated_at_ms = ?
|
||||
WHERE app_code = ? AND game_id = ? AND user_id = ?`,
|
||||
status, nowMs, app, gameID, userID,
|
||||
WHERE app_code = ? AND user_id = ?`,
|
||||
status, nowMs, app, userID,
|
||||
)
|
||||
if err != nil {
|
||||
return dicedomain.Robot{}, err
|
||||
@ -625,24 +642,17 @@ func (r *Repository) SetDiceRobotStatus(ctx context.Context, appCode string, gam
|
||||
if affected == 0 {
|
||||
return dicedomain.Robot{}, xerr.New(xerr.NotFound, "dice robot not found")
|
||||
}
|
||||
var robot dicedomain.Robot
|
||||
err = r.db.QueryRowContext(ctx,
|
||||
`SELECT app_code, game_id, user_id, status, created_by_admin_id, created_at_ms, updated_at_ms
|
||||
FROM game_self_game_robots
|
||||
WHERE app_code = ? AND game_id = ? AND user_id = ?`,
|
||||
app, gameID, userID,
|
||||
).Scan(&robot.AppCode, &robot.GameID, &robot.UserID, &robot.Status, &robot.CreatedByAdminID, &robot.CreatedAtMS, &robot.UpdatedAtMS)
|
||||
return robot, err
|
||||
return r.getUniversalDiceRobot(ctx, app, gameID, userID)
|
||||
}
|
||||
|
||||
// DeleteDiceRobot 只删除机器人登记关系,不删除 user-service 主账号,避免历史对局和钱包流水失去用户归属。
|
||||
// 删除按 user_id 清理所有历史 game_id 行,保证后台“不区分 dice/rock”的操作不会留下另一个游戏的残余登记。
|
||||
func (r *Repository) DeleteDiceRobot(ctx context.Context, appCode string, gameID string, userID int64) error {
|
||||
app := appcode.Normalize(appCode)
|
||||
gameID = normalizeDiceGameID(gameID)
|
||||
res, err := r.db.ExecContext(ctx,
|
||||
`DELETE FROM game_self_game_robots
|
||||
WHERE app_code = ? AND game_id = ? AND user_id = ?`,
|
||||
app, gameID, userID,
|
||||
WHERE app_code = ? AND user_id = ?`,
|
||||
app, userID,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -654,6 +664,28 @@ func (r *Repository) DeleteDiceRobot(ctx context.Context, appCode string, gameID
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) getUniversalDiceRobot(ctx context.Context, appCode string, gameID string, userID int64) (dicedomain.Robot, error) {
|
||||
app := appcode.Normalize(appCode)
|
||||
gameID = normalizeDiceGameID(gameID)
|
||||
var robot dicedomain.Robot
|
||||
err := r.db.QueryRowContext(ctx,
|
||||
`SELECT app_code, ? AS game_id, user_id,
|
||||
IF(SUM(status = ?) > 0, ?, ?) AS status,
|
||||
MIN(created_by_admin_id), MIN(created_at_ms), MAX(updated_at_ms)
|
||||
FROM game_self_game_robots
|
||||
WHERE app_code = ? AND user_id = ?
|
||||
GROUP BY app_code, user_id`,
|
||||
gameID, dicedomain.RobotStatusActive, dicedomain.RobotStatusActive, dicedomain.RobotStatusDisabled, app, userID,
|
||||
).Scan(&robot.AppCode, &robot.GameID, &robot.UserID, &robot.Status, &robot.CreatedByAdminID, &robot.CreatedAtMS, &robot.UpdatedAtMS)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return dicedomain.Robot{}, xerr.New(xerr.NotFound, "dice robot not found")
|
||||
}
|
||||
return dicedomain.Robot{}, err
|
||||
}
|
||||
return robot, nil
|
||||
}
|
||||
|
||||
func insertDiceParticipantLocked(ctx context.Context, tx *sql.Tx, match dicedomain.Match, userID int64, participantType string, rpsGesture string, nowMs int64) (dicedomain.Match, error) {
|
||||
var seatNo int32
|
||||
if err := tx.QueryRowContext(ctx,
|
||||
|
||||
@ -216,6 +216,133 @@ func TestPickActiveDiceRobotSelectsRandomCandidateInsteadOfFixedFirstRow(t *test
|
||||
if len(picked) < 2 {
|
||||
t.Fatalf("robot picking still behaves like fixed first-row selection, got picked user ids: %+v", picked)
|
||||
}
|
||||
|
||||
rockRobot, err := repo.PickActiveDiceRobot(ctx, "lalu", dicedomain.SelfGameIDRock, "match_random_robot_rock")
|
||||
if err != nil {
|
||||
t.Fatalf("rock should reuse the app-wide robot pool: %v", err)
|
||||
}
|
||||
if rockRobot.GameID != dicedomain.SelfGameIDRock || !containsInt64(userIDs, rockRobot.UserID) {
|
||||
t.Fatalf("rock robot should come from the shared app robot pool, got %+v", rockRobot)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUniversalDiceRobotManagementIgnoresGameID(t *testing.T) {
|
||||
caller := mysqlschema.CallerFile(t, 1)
|
||||
schema := mysqlschema.New(t, mysqlschema.Config{
|
||||
InitDBPath: mysqlschema.InitDBPath(t, caller, "../../../deploy/mysql/initdb/001_game_service.sql"),
|
||||
DatabasePrefix: "hy_game_test",
|
||||
})
|
||||
|
||||
ctx := appcode.WithContext(context.Background(), "lalu")
|
||||
repo, err := Open(ctx, schema.DSN)
|
||||
if err != nil {
|
||||
t.Fatalf("open repository failed: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { _ = repo.Close() })
|
||||
|
||||
nowMS := int64(1780542672000)
|
||||
rows := []struct {
|
||||
gameID string
|
||||
userID int64
|
||||
status string
|
||||
}{
|
||||
{gameID: dicedomain.DefaultGameID, userID: 201, status: dicedomain.RobotStatusActive},
|
||||
{gameID: dicedomain.SelfGameIDRock, userID: 201, status: dicedomain.RobotStatusDisabled},
|
||||
{gameID: dicedomain.SelfGameIDRock, userID: 202, status: dicedomain.RobotStatusDisabled},
|
||||
}
|
||||
for index, row := range rows {
|
||||
if _, err := repo.db.ExecContext(ctx,
|
||||
`INSERT INTO game_self_game_robots (
|
||||
app_code, game_id, user_id, status, created_by_admin_id, created_at_ms, updated_at_ms
|
||||
) VALUES (?, ?, ?, ?, 7, ?, ?)`,
|
||||
"lalu", row.gameID, row.userID, row.status, nowMS+int64(index), nowMS+int64(index),
|
||||
); err != nil {
|
||||
t.Fatalf("insert robot row %+v failed: %v", row, err)
|
||||
}
|
||||
}
|
||||
|
||||
active, _, err := repo.ListDiceRobots(ctx, "lalu", "", dicedomain.RobotStatusActive, 20, "")
|
||||
if err != nil {
|
||||
t.Fatalf("list active robots failed: %v", err)
|
||||
}
|
||||
if len(active) != 1 || active[0].UserID != 201 || active[0].Status != dicedomain.RobotStatusActive {
|
||||
t.Fatalf("active list should aggregate user 201 once, got %+v", active)
|
||||
}
|
||||
disabled, _, err := repo.ListDiceRobots(ctx, "lalu", "", dicedomain.RobotStatusDisabled, 20, "")
|
||||
if err != nil {
|
||||
t.Fatalf("list disabled robots failed: %v", err)
|
||||
}
|
||||
if len(disabled) != 1 || disabled[0].UserID != 202 || disabled[0].Status != dicedomain.RobotStatusDisabled {
|
||||
t.Fatalf("disabled list should only include fully disabled users, got %+v", disabled)
|
||||
}
|
||||
|
||||
updated, err := repo.SetDiceRobotStatus(ctx, "lalu", dicedomain.SelfGameIDRock, 201, dicedomain.RobotStatusDisabled, nowMS+100)
|
||||
if err != nil {
|
||||
t.Fatalf("set universal robot status failed: %v", err)
|
||||
}
|
||||
if updated.GameID != dicedomain.SelfGameIDRock || updated.Status != dicedomain.RobotStatusDisabled {
|
||||
t.Fatalf("status response should keep requested game context while updating shared rows, got %+v", updated)
|
||||
}
|
||||
var activeRows int
|
||||
if err := repo.db.QueryRowContext(ctx,
|
||||
`SELECT COUNT(*) FROM game_self_game_robots WHERE app_code = ? AND user_id = ? AND status = ?`,
|
||||
"lalu", int64(201), dicedomain.RobotStatusActive,
|
||||
).Scan(&activeRows); err != nil {
|
||||
t.Fatalf("query active rows failed: %v", err)
|
||||
}
|
||||
if activeRows != 0 {
|
||||
t.Fatalf("status update should affect all game_id rows for user 201, active rows=%d", activeRows)
|
||||
}
|
||||
|
||||
if err := repo.DeleteDiceRobot(ctx, "lalu", dicedomain.DefaultGameID, 201); err != nil {
|
||||
t.Fatalf("delete universal robot failed: %v", err)
|
||||
}
|
||||
var remainingRows int
|
||||
if err := repo.db.QueryRowContext(ctx,
|
||||
`SELECT COUNT(*) FROM game_self_game_robots WHERE app_code = ? AND user_id = ?`,
|
||||
"lalu", int64(201),
|
||||
).Scan(&remainingRows); err != nil {
|
||||
t.Fatalf("query remaining rows failed: %v", err)
|
||||
}
|
||||
if remainingRows != 0 {
|
||||
t.Fatalf("delete should remove every game_id row for user 201, rows=%d", remainingRows)
|
||||
}
|
||||
|
||||
registered, err := repo.RegisterDiceRobots(ctx, "lalu", dicedomain.SelfGameIDRock, []int64{203}, 7, nowMS+200)
|
||||
if err != nil {
|
||||
t.Fatalf("register universal robot failed: %v", err)
|
||||
}
|
||||
if !robotListContains(registered, 203, dicedomain.RobotStatusActive) {
|
||||
t.Fatalf("registered robot should be returned from shared list, got %+v", registered)
|
||||
}
|
||||
var registeredGameID string
|
||||
if err := repo.db.QueryRowContext(ctx,
|
||||
`SELECT game_id FROM game_self_game_robots WHERE app_code = ? AND user_id = ?`,
|
||||
"lalu", int64(203),
|
||||
).Scan(®isteredGameID); err != nil {
|
||||
t.Fatalf("query registered robot failed: %v", err)
|
||||
}
|
||||
if registeredGameID != dicedomain.DefaultGameID {
|
||||
t.Fatalf("new universal robots should use default compatibility game_id, got %s", registeredGameID)
|
||||
}
|
||||
}
|
||||
|
||||
func containsInt64(values []int64, target int64) bool {
|
||||
for _, value := range values {
|
||||
if value == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func robotListContains(robots []dicedomain.Robot, userID int64, status string) bool {
|
||||
for _, robot := range robots {
|
||||
if robot.UserID == userID && robot.Status == status {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func TestUpsertCatalogCreatesDefaultVoiceRoomDisplayRule(t *testing.T) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user