refactor: enhance test framework with automated resource tracking and scripted error capture capabilities

This commit is contained in:
2026-06-26 09:40:17 +08:00
parent 948a99cf90
commit 00f9d98f4b
58 changed files with 3594 additions and 1289 deletions
+124 -1
View File
@@ -227,6 +227,7 @@ function admin.rpc_admin_clear_global_chat(context, payload)
if channelId == "" then
error("channel_id is required. Pass the channel ID from the client.")
end
channelId = utils.resolve_channel_id(channelId)
local deleted = 0
local cursor = ""
@@ -319,6 +320,121 @@ function admin.rpc_admin_delete_users(context, payload)
return nk.json_encode({ success = true, deleted = deleted, failed = failed })
end
function admin.rpc_admin_get_user_detail(context, payload)
utils.require_admin(context)
local request = nk.json_decode(payload or "{}")
local userId = request.user_id or ""
if userId == "" then error("user_id is required") end
local account = nk.account_get_id(userId)
local metadata = {}
if account.user.metadata then
local s, m = pcall(nk.json_decode, account.user.metadata)
if s and m then metadata = m end
end
local friends = {}
local okFriends, friendResult = pcall(nk.friends_list, userId, nil, 100, "")
if okFriends and friendResult and friendResult.friends then
for _, f in pairs(friendResult.friends) do
table.insert(friends, {
user_id = f.user.id,
username = f.user.username,
display_name = f.user.display_name,
state = f.state
})
end
end
local purchaseHistory = {}
local okReceipts, receiptResult = pcall(nk.storage_list, userId, "receipts", 100, "")
if okReceipts and receiptResult and receiptResult.objects then
for _, obj in pairs(receiptResult.objects) do
table.insert(purchaseHistory, { key = obj.key, value = obj.value, update_time = obj.update_time })
end
end
local collections = request.collections or {"profiles", "stats", "inventory", "receipts", "history", "matches", "inbox"}
local storage = {}
for _, collection in ipairs(collections) do
local okStorage, storageResult = pcall(nk.storage_list, userId, collection, 100, "")
storage[collection] = {}
if okStorage and storageResult and storageResult.objects then
for _, obj in pairs(storageResult.objects) do
table.insert(storage[collection], { key = obj.key, value = obj.value, version = obj.version, update_time = obj.update_time })
end
end
end
local walletLedger = {}
local okLedger, ledgerResult = pcall(nk.wallet_ledger_list, userId, 50)
if okLedger and ledgerResult then walletLedger = ledgerResult.items or {} end
return nk.json_encode({
user = {
user_id = account.user.id,
username = account.user.username or "",
display_name = account.user.display_name or "",
avatar_url = account.user.avatar_url or "",
lang_tag = account.user.lang_tag or "",
location = account.user.location or "",
timezone = account.user.timezone or "",
create_time = account.user.create_time or "",
update_time = account.user.update_time or "",
metadata = metadata,
wallet = account.wallet or {},
email = account.email or "",
email_verified = account.email_verified or false
},
friends = friends,
purchases = purchaseHistory,
wallet_ledger = walletLedger,
storage = storage,
subscription = metadata.subscription or metadata.subscriptions or {}
})
end
function admin.rpc_admin_update_user_identity(context, payload)
utils.require_admin(context)
local request = nk.json_decode(payload or "{}")
local userId = request.user_id or ""
if userId == "" then error("user_id is required") end
local account = nk.account_get_id(userId)
local metadata = {}
if account.user.metadata then
local s, m = pcall(nk.json_decode, account.user.metadata)
if s and m then metadata = m end
end
if request.metadata and type(request.metadata) == "table" then
for k, v in pairs(request.metadata) do metadata[k] = v end
end
nk.account_update_id(
userId,
request.username or account.user.username,
request.display_name or account.user.display_name,
request.timezone or account.user.timezone,
request.location or account.user.location,
request.lang_tag or account.user.lang_tag,
request.avatar_url or account.user.avatar_url,
nk.json_encode(metadata)
)
return nk.json_encode({ success = true })
end
function admin.rpc_admin_set_user_password(context, payload)
utils.require_admin(context)
local request = nk.json_decode(payload or "{}")
local userId = request.user_id or ""
local password = request.password or ""
if userId == "" or password == "" then error("user_id and password are required") end
local account = nk.account_get_id(userId)
if not account.email or account.email == "" then error("User has no email credential") end
nk.account_update_id(userId, nil, nil, nil, nil, nil, nil, nil, account.email, password)
return nk.json_encode({ success = true })
end
function admin.rpc_admin_get_player_list(context, payload)
local request = nk.json_decode(payload)
utils.require_admin_or_host(context, request.match_id)
@@ -386,6 +502,7 @@ function admin.rpc_admin_purge_old_messages(context, payload)
if channelId == "" then
error("channel_id is required")
end
channelId = utils.resolve_channel_id(channelId)
if maxAgeDays <= 0 then
error("max_age_days must be > 0")
end
@@ -441,6 +558,7 @@ function admin.rpc_admin_list_channel_messages(context, payload)
error("channel_id is required")
end
channelId = utils.resolve_channel_id(channelId)
local status, result = pcall(nk.channel_messages_list, channelId, limit, forward, cursor)
if not status then
error("Failed to list messages: " .. tostring(result))
@@ -448,7 +566,7 @@ function admin.rpc_admin_list_channel_messages(context, payload)
local msgs = {}
if result and result.messages then
for _, msg in ipairs(result.messages) do
for _, msg in pairs(result.messages) do
table.insert(msgs, {
message_id = msg.message_id,
sender_id = msg.sender_id,
@@ -463,6 +581,7 @@ function admin.rpc_admin_list_channel_messages(context, payload)
return nk.json_encode({
messages = msgs,
channel_id = channelId,
next_cursor = result.next_cursor or "",
cache_cursor = result.cache_cursor or ""
})
@@ -478,6 +597,7 @@ function admin.rpc_admin_delete_channel_message(context, payload)
error("channel_id and message_id are required")
end
channelId = utils.resolve_channel_id(channelId)
local status, err = pcall(nk.channel_message_remove, channelId, messageId)
if not status then
error("Failed to delete message: " .. tostring(err))
@@ -500,6 +620,9 @@ nk.register_rpc(admin.rpc_admin_topup_gold, "admin_topup_gold")
nk.register_rpc(admin.rpc_admin_clear_global_chat, "admin_clear_global_chat")
nk.register_rpc(admin.rpc_admin_list_users, "admin_list_users")
nk.register_rpc(admin.rpc_admin_delete_users, "admin_delete_users")
nk.register_rpc(admin.rpc_admin_get_user_detail, "admin_get_user_detail")
nk.register_rpc(admin.rpc_admin_update_user_identity, "admin_update_user_identity")
nk.register_rpc(admin.rpc_admin_set_user_password, "admin_set_user_password")
nk.register_rpc(admin.rpc_admin_get_chat_config, "admin_get_chat_config")
nk.register_rpc(admin.rpc_admin_set_chat_config, "admin_set_chat_config")
nk.register_rpc(admin.rpc_admin_purge_old_messages, "admin_purge_old_messages")
+1 -1
View File
@@ -14,7 +14,7 @@ function leaderboard.rpc_get_leaderboard_stats(context, payload)
local leaderboardData = {}
local ownerRecords = records_or_err.records or {}
for _, record in ipairs(ownerRecords) do
for _, record in pairs(ownerRecords) do
local metadata = {}
if record.metadata then
local s, m = pcall(nk.json_decode, record.metadata)
+30
View File
@@ -50,4 +50,34 @@ function utils.require_admin_or_host(context, match_id)
end
end
-- Channel type constants for nk.channel_id_build (Nakama Lua runtime).
-- NOTE: these differ from the Godot client's NakamaSocket.ChannelType enum.
utils.CHANNEL_TYPE_ROOM = 1
utils.CHANNEL_TYPE_DIRECT = 2
utils.CHANNEL_TYPE_GROUP = 3
-- Resolve a chat channel identifier for admin chat RPCs.
--
-- The client may send either an already-hashed Nakama channel ID, or a friendly
-- room name (e.g. "social_global"). A raw room name is NOT a valid channel ID for
-- nk.channel_messages_list, so we build the canonical hashed ID via the
-- authoritative nk.channel_id_build API (system user as sender → global room).
-- Returns the resolved channel ID, or the original value if it already looks
-- hashed / can't be built.
function utils.resolve_channel_id(channel_id)
if not channel_id or channel_id == "" then
return ""
end
-- A hashed Nakama channel ID contains a '.' separator; a plain room name does
-- not. Only treat dot-free values as room names needing resolution.
if string.find(channel_id, "%.") then
return channel_id
end
local status, built = pcall(nk.channel_id_build, "", channel_id, utils.CHANNEL_TYPE_ROOM)
if status and built and built ~= "" then
return built
end
return channel_id
end
return utils