From 4b0238b64b3878d9d016ac7afb75b74a9624c5ee Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Wed, 9 Sep 2026 22:05:13 +0000 Subject: [PATCH] Cover UDP error paths and sendUdp in tests Codecov flagged the sendUdp methods on Instance and BaseInstancePlugin, the bind, socket error, malformed packet and send failure paths in FactorioServer, and the --enable-lua-udp branch of startScenario. Add unit tests for each and start the integration scenario test with UDP enabled on Factorio 2.0.59+. Co-Authored-By: Claude Fable 5.1 --- test/host/BaseInstancePlugin.js | 6 +++ test/host/Instance.js | 12 ++++++ test/host/server.js | 65 +++++++++++++++++++++++++++++++++ test/integration/server.js | 3 ++ test/mock.js | 9 +++++ 5 files changed, 95 insertions(+) diff --git a/test/host/BaseInstancePlugin.js b/test/host/BaseInstancePlugin.js index 10808b40..9e7dbd30 100644 --- a/test/host/BaseInstancePlugin.js +++ b/test/host/BaseInstancePlugin.js @@ -66,6 +66,12 @@ describe("host/src/BaseInstancePlugin", function() { await assert.rejects(instancePlugin.sendRcon("a"), new Error("ref")); }); }); + describe("sendUdp", function() { + it("should send message to the instance", async function() { + await instancePlugin.sendUdp("a"); + assert.deepEqual(instancePlugin.instance.server.udpMessages, ["a"]); + }); + }); describe("sendOrderedRcon", function() { it("should send commands in order", async function() { instancePlugin.instance.server.rconCommandResults.set("a", { time: 100, response: "a" }); diff --git a/test/host/Instance.js b/test/host/Instance.js index f396c105..9d4562a5 100644 --- a/test/host/Instance.js +++ b/test/host/Instance.js @@ -240,6 +240,18 @@ describe("class Instance", function() { }); }); + describe(".sendUdp()", function() { + it("should throw when Lua UDP is disabled", async function() { + await assert.rejects(instance.sendUdp("data"), /Lua UDP is disabled/); + assert.deepEqual(instance.server.udpMessages, []); + }); + it("should send to the server when enabled", async function() { + instance.config.set("factorio.enable_lua_udp", true); + await instance.sendUdp("data"); + assert.deepEqual(instance.server.udpMessages, ["data"]); + }); + }); + describe("._recordPlayerJoin()", function() { it("should add player to playersOnline", function() { instance._recordPlayerJoin("player"); diff --git a/test/host/server.js b/test/host/server.js index 75b5d9c7..b6cc542d 100644 --- a/test/host/server.js +++ b/test/host/server.js @@ -399,6 +399,71 @@ describe("host/server", function() { assert.equal(server._udpSocket, null); assert.equal(server.hostUdpPort, undefined); }); + it("should warn on packets not in IPC format", async function() { + server.luaUdpPort = udpServer.address().port; + await server._bindUdpSocket(); + const logger = server._logger; + let warnings = []; + server._logger = { warn: msg => warnings.push(msg) }; + try { + udpServer.send(Buffer.from("not ipc"), server.hostUdpPort, "127.0.0.1"); + while (warnings.length < 1) { + await wait(1); + } + assert.equal(warnings[0], "Ignoring UDP packet not in IPC format: not ipc"); + } finally { + server._logger = logger; + server._resetState(); + } + }); + it("should emit error on malformed IPC packets", async function() { + server.luaUdpPort = udpServer.address().port; + await server._bindUdpSocket(); + try { + let waiter = events.once(server, "error"); + udpServer.send(Buffer.from("\f$ipc:no_separator"), server.hostUdpPort, "127.0.0.1"); + let [err] = await waiter; + assert.equal(err.message, 'Malformed IPC line "\f$ipc:no_separator"'); + } finally { + server._resetState(); + } + }); + it("should forward socket errors", async function() { + await server._bindUdpSocket(); + try { + let waiter = events.once(server, "error"); + server._udpSocket.emit("error", new Error("socket failed")); + let [err] = await waiter; + assert.equal(err.message, "socket failed"); + } finally { + server._resetState(); + } + }); + it("should reject sendUdp when sending fails", async function() { + server.luaUdpPort = udpServer.address().port; + await server._bindUdpSocket(); + server._state = "running"; + try { + // Larger than the maximum size of a UDP datagram + await assert.rejects(server.sendUdp(Buffer.alloc(70000)), { code: "EMSGSIZE" }); + } finally { + server._resetState(); + } + }); + it("should close the socket if binding fails", async function() { + const createSocket = dgram.createSocket; + dgram.createSocket = (...args) => { + const socket = createSocket(...args); + socket.bind = () => process.nextTick(() => socket.emit("error", new Error("bind failed"))); + return socket; + }; + try { + await assert.rejects(server._bindUdpSocket(), new Error("bind failed")); + } finally { + dgram.createSocket = createSocket; + } + assert.equal(server._udpSocket, null); + }); }); describe(".stop()", function() { diff --git a/test/integration/server.js b/test/integration/server.js index b74af0e9..e4e9ade2 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -179,6 +179,8 @@ describe("Integration of host/src/server", function() { slowTest(this); log(".startScenario()"); + // --enable-lua-udp was added in Factorio 2.0.59 + server.enableLuaUdp = lib.integerFullVersion(server.version) >= lib.integerFullVersion("2.0.59"); let pass = false; function filter(output) { if (output.message === "test_scenario init") { @@ -191,6 +193,7 @@ describe("Integration of host/src/server", function() { log(".stop()"); await server.stop(); + server.enableLuaUdp = false; server.off("output", filter); assert(pass, "server did not output line from test scenario"); diff --git a/test/mock.js b/test/mock.js index 265e869a..9b2d9ef6 100644 --- a/test/mock.js +++ b/test/mock.js @@ -95,6 +95,11 @@ export class MockServer extends events.EventEmitter { reset() { this.rconCommands = []; this.rconCommandResults = new Map(); + this.udpMessages = []; + } + + async sendUdp(message) { + this.udpMessages.push(message); } async sendRcon(command) { @@ -144,6 +149,10 @@ export class MockInstance extends lib.Link { async sendRcon(command, expectEmpty, plugin) { return await this.server.sendRcon(command); } + + async sendUdp(message) { + await this.server.sendUdp(message); + } } export class MockHost extends lib.Link {