Files
gen1recomp/tests/game3_link_client_relay_test.lua
2026-09-24 05:17:32 -04:00

225 lines
8.5 KiB
Lua

#!/usr/bin/env luajit
package.path = "./?.lua;./?/init.lua;" .. package.path
require("tests.fixture_data.game3_items").install()
local failed = 0
local function check(cond, msg)
if cond then
print("[ok] " .. msg)
else
failed = failed + 1
print("[FAIL] " .. msg)
end
end
local function eq(a, b, msg)
check(a == b, string.format("%s (%s == %s)", msg, tostring(a), tostring(b)))
end
local okR, Relay = pcall(require, "tests.support.fake_relay")
if not okR then
print("[skip] tests/support/fake_relay.lua is not in this tree: " .. tostring(Relay))
os.exit(0)
end
local function newClientModule()
package.loaded["src.online.Client"] = nil
local Client = require("src.online.Client")
Client.reset()
return Client
end
local PROFILE = {
engine = 3, version = "firered", engineVersion = "0.0.0-dev", apiVersion = "2",
fingerprint = "g3fingerprint", rulesetId = "g3_link", kind = "vanilla", rule = {},
}
local relay = Relay.new()
local seatA = relay:seat("a0000001", "RED")
local seatB = relay:seat("b0000002", "BLUE")
local ClientB = newClientModule()
ClientB.configure({ relayAddress = "fake:1", connect = function() return seatB.transport end })
local ClientA = newClientModule()
ClientA.configure({ relayAddress = "fake:1", connect = function() return seatA.transport end })
local function step(n)
for _ = 1, n or 3 do
relay:pump()
ClientA.update(0)
ClientB.update(0)
end
end
ClientA.connect({ name = "RED", profiles = { PROFILE } })
ClientB.connect({ name = "BLUE", profiles = { PROFILE } })
step()
eq(ClientA.state(), "online", "A is online on the fake relay")
eq(ClientB.state(), "online", "B is online on the fake relay")
local session = { name = "RED", trainerId = 0x1234, gender = 0, party = {} }
package.loaded["src.core.game3.runtime"] = {
getSession = function() return session end,
isActive = function() return true end,
}
local RelayTransport = require("src.core.game3.link.relay_transport")
local Game3Link = require("src.link.Game3Link")
local game = { data = {}, save = { player = { name = "RED" }, options = {} } }
print("[test] 1. two AUTO queues pair into a relay room born battling")
ClientA.queueDirect({ activity = "trade", ruleset = "g3_link", auto = true, profile = PROFILE,
avatar = { name = "RED", trainerId = 1, gender = 0, version = "firered" }, preview = { 1 } })
step()
ClientB.queueDirect({ activity = "trade", ruleset = "g3_link", auto = true, profile = PROFILE,
avatar = { name = "BLUE", trainerId = 2, gender = 0, version = "leafgreen" }, preview = { 4 } })
step()
local roomA = ClientA.room()
check(roomA ~= nil, "A has a room")
eq(roomA and roomA.stage, "battling", "born battling")
check(roomA and roomA.match ~= nil, "with a match token")
eq(ClientA.seat(), 0, "A queued first: seat 0")
eq(ClientB.seat(), 1, "B: seat 1")
print("[test] 2. Game3Link handshakes over the real Client room sessions")
local tA = RelayTransport.new(ClientA.roomSession(), { client = ClientA })
local tB = RelayTransport.new(ClientB.roomSession(), { client = ClientB })
eq(tA:seat(), 0, "transport A is seat 0")
eq(tB:seat(), 1, "transport B is seat 1")
eq(tA:seed(), roomA.seed, "the relay seed")
local lA = Game3Link.attach(tA, { game = game })
local lB = Game3Link.attach(tB, { game = game, hello = Game3Link.hello(game,
Game3Link.LINKTYPE.TRADE, { name = "BLUE", trainerId = 2, gender = 0 }) })
for _ = 1, 4 do
step(1)
lA:update(0)
lB:update(0)
end
check(lA:isReady(), "A reached ready")
check(lB:isReady(), "B reached ready")
eq(lA:peerName(), "BLUE", "A names BLUE")
eq(lB:peerName(), "RED", "B names RED")
eq(lA.peerHellos[1] and lA.peerHellos[1].seat, 1, "the hello arrived tagged with seat 1")
print("[test] 3. messages cross the relay seat-tagged, the relay barrier commits")
lA:send({ type = "game3_trade_cmd", cmd = 0xAABB, cursor = 2 })
step(1)
lB:update(0)
local cmd = lB:take("game3_trade_cmd")
eq(cmd and cmd.cursor, 2, "B received the trade command")
eq(cmd and cmd.seat, 0, "from seat 0")
lA:send({ type = "game3_trade_confirm", digest = "0123456789abcdef" })
lB:send({ type = "game3_trade_confirm", digest = "0123456789abcdef" })
step(2)
lA:update(0)
local commit = lA:take("trade_commit")
check(commit ~= nil, "A receives the relay's trade_commit")
eq(commit and commit.seat, -1, "relay-authored")
print("[test] 4. closing the link keeps the room; leaving it drops the peer's link")
lA:close("done")
step(1)
check(ClientA.room() ~= nil, "A's link closed but A is still seated")
lA:leave()
step(2)
eq(ClientA.room(), nil, "A left the room")
lB:update(0)
lB:update(0)
check(not lB:isOpen(), "B's link closed")
check(lB.reason == "peer_left" or lB.reason == "peer_dropped", "because the peer left")
print("[test] 5. an invite from the launcher lands in the Union Room")
local store = { flags = {}, vars = {} }
session.store = store
session.map = "FR_UNION_ROOM"
local ctx = { specialVars = {}, stringVars = {} }
package.loaded["src.core.game3.runtime"]._game = { data = { maps = {} }, session = session,
input = { wasPressed = function() return false end }, save = game.save }
local romBundleEarly = require("tests.game3_cache").bundle()
package.loaded["src.core.game3.scripting.space"] = {
store = store, mapId = "FR_UNION_ROOM", vm = { ctx = ctx, adapters = { log = function() end } },
ensureBundle = function() return romBundleEarly end,
}
require("src.core.game3.link.union_room")._avatars = require("tests.g3link_fake_relay").avatars()
package.loaded["src.core.game3.objects"] = {
addObject = function() return true end, removeObject = function() return true end,
refreshGraphics = function() return 0 end,
}
package.loaded["src.core.game3.player"] = { cellX = 7, cellY = 11, facing = "down" }
package.loaded["src.online.ArenaData"] = {
liveProfile3 = function(_, rulesetId)
local p = {}
for k, v in pairs(PROFILE) do p[k] = v end
p.rulesetId = rulesetId
return p
end,
}
package.loaded["src.online.Client"] = ClientA
local romBundle = require("tests.game3_cache").bundle()
if not romBundle then
package.loaded["src.core.game3.rom_text"] = {
plain = function(key) return key end, box = function(key) return key end,
ascii = function(key) return key end, has = function() return true end,
ir = function(key) return { { t = "text", s = key } } end,
key = function(n, i, j) return j and (n .. "[" .. i .. "][" .. j .. "]") or (n .. "[" .. i .. "]") end,
at = function(n, i, j) return j and (n .. "[" .. i .. "][" .. j .. "]") or (n .. "[" .. i .. "]") end,
count = function() return 0 end, list = function() return {} end,
lazy = function(map) return setmetatable({}, { __index = function(_, k) return map[k] end }) end,
}
end
local Link = require("src.core.game3.link")
local Union = require("src.core.game3.link.union_room")
check(Link.adapterConnected(), "A's adapter reads connected")
Union.run(ctx)
eq(Union.relay, true, "the Union Room runs over the relay")
step()
check(ClientA.plaza() ~= nil, "A is in the union plaza")
seatB.presence.where = "launcher"
ClientB.invite("a0000001", "chat", {}, PROFILE)
step()
Union.update(1 / 60)
Union.pollIncoming()
eq(Union.state, "player_contacted_you", "B's invite reaches the Union Room")
eq(Union._requestName, "BLUE", "from BLUE")
Union.answerRequest(true)
step()
for _ = 1, 3 do
Link.update(1 / 60)
step(1)
end
eq(Union.state, "in_activity", "the chat began over the relay")
eq(Link.link, nil, "the chat talks to the room session, no Game3Link handshake")
eq(ClientA.seat(), 1, "the invited player sits in seat 1")
local Chat = require("src.core.game3.link.chat")
check(Chat.isActive(), "chat is live")
local rsB = ClientB.roomSession()
step(1)
local join = rsB:take("game3_union_hello")
eq(join and join.name, "RED", "A announced itself to the chat")
rsB:send({ type = "game3_union_hello", name = "BLUE", gender = 0, trainerId = 2, activity = 0x45 })
rsB:send({ type = "game3_chat_line", name = "BLUE", text = "HI RED" })
step(2)
Link.update(1 / 60)
eq(#Chat.lines, 2, "BLUE joined and spoke")
check(Chat.lines[2] and Chat.lines[2].text:find("HI RED", 1, true) ~= nil, "BLUE's line is in the log")
eq(Chat.lines[2] and Chat.lines[2].seat, 0, "in BLUE's seat colour")
Chat.stop("left")
for _ = 1, 3 do
Link.update(1 / 60)
step(1)
end
eq(Union.state, "main", "back in the Union Room")
eq(ClientA.room(), nil, "the private room was left")
local bye = rsB:take("game3_chat_bye")
check(bye ~= nil, "BLUE saw A leave the chat")
Union.stop("test")
step()
eq(ClientA.plaza(), nil, "leaving the Union Room leaves the plaza")
Link.reset()
if failed == 0 then
print("[pass] link over the Client")
os.exit(0)
end
print("[fail] link over the Client: " .. failed)
os.exit(1)