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

246 lines
8.6 KiB
Lua

return function(game)
local U = dofile("tests/drivers/util.lua")
local PORT = tonumber(os.getenv("POKEPORT_C1_RELAY_PORT") or "") or 18411
local SERVER = os.getenv("POKESERVER_DIR") or "../pokeserver"
local failures = 0
local function check(cond, msg)
if cond then
U.log("ok ", msg)
else
failures = failures + 1
U.log("FAIL", msg)
end
end
local pidFile = "/tmp/pokeserver_c1_" .. PORT .. ".pid"
local function stopServer()
local handle = io.open(pidFile, "r")
if handle then
local pid = handle:read("*l")
handle:close()
if pid and pid:match("^%d+$") then
os.execute("kill " .. pid .. " >/dev/null 2>&1")
end
end
os.remove(pidFile)
end
local function finish()
stopServer()
U.log(failures == 0 and "online c1 pin relay passed"
or (failures .. " online c1 pin relay check(s) failed"))
love.event.quit(failures == 0 and 0 or 1)
while true do coroutine.yield() end
end
local probe = io.open(SERVER .. "/server.js", "r")
if not probe then
U.log("FAIL", SERVER .. "/server.js is not checked out")
love.event.quit(1)
return
end
probe:close()
os.execute(("(cd %q && PORT=%d HTTP_PORT=%d SYNC_ENABLED=0 CARTS_ENABLED=0 "
.. "STATS_ENABLED=0 GIFTS_ENABLED=0 CART_MIRROR_ENABLED=0 node server.js "
.. ">/tmp/pokeserver_c1_%d.log 2>&1 & echo $! > %q)")
:format(SERVER, PORT, PORT + 1, PORT, pidFile))
local Net = require("src.link.Net")
local function reachable()
local net = Net.new()
if not net:connectTCP("127.0.0.1:" .. PORT) then return false end
for _ = 1, 30 do
net:update()
if net.closed then
pcall(function() net:close() end)
return false
end
if not net.connecting then
pcall(function() net:close() end)
return true
end
end
pcall(function() net:close() end)
return false
end
local up = false
local startedAt = love.timer.getTime()
while love.timer.getTime() - startedAt < 20 do
up = reachable()
if up then break end
U.wait(2)
end
check(up, "the spawned pokeserver answered on 127.0.0.1:" .. PORT)
if not up then return finish() end
local function freshClient()
package.loaded["src.online.Client"] = nil
local C = require("src.online.Client")
C.reset()
C.configure({ relayAddress = "127.0.0.1:" .. PORT })
return C
end
local Host = freshClient()
local Guest = freshClient()
local OnlinePanel = require("src.import.OnlinePanel")
OnlinePanel._hooked = nil
OnlinePanel._events = {}
local Version = require("src.core.Version")
local PROFILE = {
engine = 1, version = "red", engineVersion = Version.engine,
apiVersion = Version.modApi, fingerprint = "c1-pin", rulesetId = "gen1_faithful",
kind = "vanilla", rule = { partySize = 1 },
}
local imp = { ready = { red = true }, activeSlot = {}, slots = {}, pulse = 0,
_pages = {}, _uiActions = {}, _actAt = {} }
local st = OnlinePanel.state(imp)
st.version, st.slotId, st.setupDone = "red", "slot1", true
st.team = { { where = "party", index = 1 } }
st.profiles["red|vanilla|-"] = { profile = PROFILE }
local SAVE = { party = { { species = "PIKACHU", level = 20, moves = {} } } }
st.slotRead = { key = "red|slot1|-",
data = { generation = 1, party = SAVE.party, save = SAVE } }
local function pump(n)
for _ = 1, n or 1 do
Host.update(1 / 60)
Guest.update(1 / 60)
OnlinePanel.update(imp, 1 / 60)
coroutine.yield()
end
end
local function waitFor(fn, frames, what)
for _ = 1, frames or 600 do
if fn() then return true end
pump(1)
end
check(false, "timed out waiting for " .. tostring(what))
return false
end
Host.connect({ name = "HOST#001", profiles = { PROFILE } })
Guest.connect({ name = "GUEST#002", profiles = { PROFILE } })
if not waitFor(function()
return Host.state() == "online" and Guest.state() == "online"
end, 600, "both clients online") then
U.log("host", Host.state(), tostring(Host.error()),
"guest", Guest.state(), tostring(Guest.error()))
return finish()
end
local made = Host.createRoom({ intent = "battle", profile = PROFILE,
playing = true, maxSpectators = 4, private = true, pin = "0427",
seats = 2, auto = false })
waitFor(function() return made.done end, 600, "the private room")
local hostRoom = Host.room()
check(hostRoom ~= nil and hostRoom.locked == true, "the host's room is locked")
local roomId = hostRoom and hostRoom.room
check(type(roomId) == "string" and roomId:match("^r%x+$") ~= nil,
"and has an opaque id, no code")
local row
waitFor(function()
OnlinePanel.invalidate(imp, "lobby")
OnlinePanel.refresh(imp)
for _, r in ipairs(OnlinePanel.cache(imp).rooms) do
if r.room == roomId then row = r end
end
return row ~= nil
end, 600, "the locked room in the Play list")
check(row and row.locked == true, "the Play list shows it with a lock")
check(row and row.code == nil, "and no code")
OnlinePanel.startJoin(imp, OnlinePanel.targetFor(row, "player"))
check(OnlinePanel.pinModal(imp) ~= nil, "Join opens the PIN modal")
OnlinePanel.fieldType(imp, OnlinePanel.PIN_FIELD, "1111")
check(OnlinePanel.pinSubmit(imp), "a wrong PIN is sent")
waitFor(function() return OnlinePanel.pinModal(imp) ~= nil end, 600,
"the wrong PIN answer")
local mo = OnlinePanel.pinModal(imp)
check(mo and mo.error and mo.error:find("didn't match", 1, true) ~= nil,
"the relay's bad_pin reopens the modal: " .. tostring(mo and mo.error))
check(Guest.room() == nil, "and the guest is not seated")
for _ = 1, 4 do OnlinePanel.pinBack(imp) end
OnlinePanel.fieldType(imp, OnlinePanel.PIN_FIELD, "0427")
check(OnlinePanel.pinSubmit(imp), "the right PIN is sent")
waitFor(function() local r = Guest.room() return r and r.room == roomId end,
600, "the guest to take the seat")
check(Guest.room() and Guest.room().room == roomId,
"the right PIN seats the guest")
check(OnlinePanel.screen(imp) == "room", "on the Room screen")
Guest.leaveRoom()
pump(30)
OnlinePanel.home(imp)
local tokenMsg
Host.on("invite_token", function(msg) tokenMsg = msg end)
Host.inviteToken(roomId)
waitFor(function() return tokenMsg ~= nil end, 600, "an invite token")
local token = tokenMsg and tokenMsg.token
check(type(token) == "string" and #token == 32, "the relay mints a token")
if token then
check(OnlinePanel.deepLink(imp, { invite = token }, "player"),
"a Discord join hands the token to the panel")
waitFor(function() local r = Guest.room() return r and r.room == roomId end,
600, "the token join")
check(Guest.room() and Guest.room().room == roomId,
"the token seats the guest with no PIN")
Guest.leaveRoom()
pump(30)
end
OnlinePanel.home(imp)
local guestId = Guest.you() and Guest.you().id
if type(Host.invite) == "function" and guestId then
Host.leaveRoom()
pump(30)
Host.invite(guestId, "battle_single", {}, PROFILE)
waitFor(function() return OnlinePanel.toast(imp) ~= nil end, 600,
"the invite toast")
local toast = OnlinePanel.toast(imp)
check(toast ~= nil, "an invite shows a toast")
check(toast and OnlinePanel.inviteLine(toast):find("HOST#001", 1, true) ~= nil,
"naming the sender")
OnlinePanel.toastAction(imp, "accept")
waitFor(function() return Guest.room() ~= nil end, 600, "the invite room")
local r = Guest.room()
check(r ~= nil and r.origin == "invite", "accepting lands in an invite room")
check(r ~= nil and r.listed ~= true, "which is not listed")
waitFor(function() return Host.room() ~= nil end, 600, "the inviter in the invite room")
Guest.leaveRoom()
Host.leaveRoom()
local quietFrom = love.timer.getTime()
while love.timer.getTime() - quietFrom < 1 do pump(1) end
end
local tourMade = Host.createTournament({ profile = PROFILE,
rule = { partySize = 1 }, playing = false, shotClock = 6,
maxSpectators = 8, public = false })
waitFor(function() return tourMade.done end, 600, "the private tournament")
local tour = Host.tournament()
local code = tour and tour.code
check(type(code) == "string" and #code == 6,
"a private tournament keeps a six-character code: " .. tostring(code)
.. " " .. tostring(tourMade.error or tourMade.reason))
if code then
st.tourCode = code:lower()
OnlinePanel.joinTournament(imp, { code = st.tourCode }, "spectator")
waitFor(function() return Guest.tournament() ~= nil end, 600,
"joining by the private code")
check(Guest.tournament() ~= nil, "the code lets a guest in")
end
Host.disconnect()
Guest.disconnect()
return finish()
end