diff --git a/plugins/global_chat/ctl.ts b/plugins/global_chat/ctl.ts index cbb0bbca..6244b676 100644 --- a/plugins/global_chat/ctl.ts +++ b/plugins/global_chat/ctl.ts @@ -1,5 +1,5 @@ import { CommandTree, Command } from "@clusterio/lib"; -import { BaseCtlPlugin } from "@clusterio/ctl"; +import type { CtlPluginContext } from "@clusterio/ctl"; import { ChatEvent } from "./messages.js"; const globalChatCommands = new CommandTree({ @@ -14,8 +14,8 @@ globalChatCommands.add(new Command({ }, })); -export class CtlPlugin extends BaseCtlPlugin { - async addCommands(rootCommand: CommandTree) { +export default async function(context: CtlPluginContext) { + context.hooks.addCommands.attach(context.plugin.name, async (rootCommand) => { rootCommand.add(globalChatCommands); - } + }); } diff --git a/plugins/global_chat/instance.ts b/plugins/global_chat/instance.ts index 22cf1dda..c7ba9615 100644 --- a/plugins/global_chat/instance.ts +++ b/plugins/global_chat/instance.ts @@ -1,5 +1,5 @@ import * as lib from "@clusterio/lib"; -import { BaseInstancePlugin } from "@clusterio/host"; +import type { InstancePluginContext } from "@clusterio/host"; import { ChatEvent } from "./messages.js"; /** @@ -12,41 +12,38 @@ function removeTags(content: string): string { return content.replace(/\[(?:gps|special-item|train|train-stop)=\S*?\]/gm, ""); } -export class InstancePlugin extends BaseInstancePlugin { - messageQueue: string[] = []; +export default async function(context: InstancePluginContext) { + const { instance, host, plugin } = context; + let messageQueue: string[] = []; - async init() { - this.instance.handle(ChatEvent, this.handleChatEvent.bind(this)); + function sendChat(message: string) { + instance.sendTo("allInstances", new ChatEvent(instance.name, message)); } - onControllerConnectionEvent(event: string) { - if (event === "connect") { - for (let message of this.messageQueue) { - this.sendChat(message); - } - this.messageQueue = []; - } - } - - async handleChatEvent(event: ChatEvent) { + instance.handle(ChatEvent, async (event: ChatEvent) => { // TODO check if cross server chat is enabled let content = `[${event.instanceName}] ${removeTags(event.content)}`; - await this.sendRcon(`/sc game.print('${lib.escapeString(content)}')`, true); - } + await instance.sendRcon(`/sc game.print('${lib.escapeString(content)}')`, true, plugin.name); + }); - sendChat(message: string) { - this.instance.sendTo("allInstances", new ChatEvent(this.instance.name, message)); - } + instance.hooks.controllerConnectionEvent.attach(plugin.name, (event) => { + if (event === "connect") { + for (let message of messageQueue) { + sendChat(message); + } + messageQueue = []; + } + }); - async onOutput(output: lib.ParsedFactorioOutput) { + instance.hooks.output.attach(plugin.name, async (output) => { if (output.type === "action" && output.action === "CHAT") { - if (this.host.connector.connected) { - this.sendChat(output.message); + if (host.connector.connected) { + sendChat(output.message); } else { - this.messageQueue.push(output.message); + messageQueue.push(output.message); } } - } + }); // TODO implement info command in lua? } diff --git a/plugins/global_chat/test/plugin.js b/plugins/global_chat/test/plugin.js index 0b3b1a1e..7b41041e 100644 --- a/plugins/global_chat/test/plugin.js +++ b/plugins/global_chat/test/plugin.js @@ -1,4 +1,5 @@ import assert from "node:assert/strict"; +import * as lib from "@clusterio/lib"; import * as mock from "../../../test/mock.js"; import * as lines from "../../../test/lib/factorio/lines.js"; @@ -31,40 +32,44 @@ describe("global_chat plugin", function() { }); }); - describe("class InstancePlugin", function() { - let instancePlugin; + describe("instance entrypoint", function() { + let mockInstance; before(async function() { - instancePlugin = new instance.InstancePlugin(info, new mock.MockInstance(), new mock.MockHost()); - await instancePlugin.init(); + try { + lib.registerPluginMessages([info]); + } catch (err) { + // Already registered by the full test suite + } + ({ instance: mockInstance } = await mock.loadInstancePlugin(instance.default, info)); }); - describe(".handleChatEvent()", function() { + describe("ChatEvent handler", function() { it("should send received chat as command", async function() { - instancePlugin.instance.server.rconCommands = []; - await instancePlugin.handleChatEvent(new ChatEvent("test", "User: message")); + mockInstance.server.rconCommands = []; + await mock.getHandler(mockInstance, ChatEvent)(new ChatEvent("test", "User: message")); assert.deepEqual( - instancePlugin.instance.server.rconCommands, + mockInstance.server.rconCommands, ["/sc game.print('[test] User: message')"], ); }); it("should filter server-specific tags from received chat", async function() { - instancePlugin.instance.server.rconCommands = []; - await instancePlugin.handleChatEvent(new ChatEvent("test", "Train [train=1235]")); + mockInstance.server.rconCommands = []; + await mock.getHandler(mockInstance, ChatEvent)(new ChatEvent("test", "Train [train=1235]")); assert.deepEqual( - instancePlugin.instance.server.rconCommands, + mockInstance.server.rconCommands, ["/sc game.print('[test] Train ')"], ); }); }); - describe(".onOutput()", function() { + describe("output hook", function() { it("should forward chat", async function() { let count = 0; - for (let [_, output] of lines.testLines) { + for (let [line, output] of lines.testLines) { if (output.type === "action" && output.action === "CHAT") { - instancePlugin.instance.connector.sentMessages = []; - await instancePlugin.onOutput(output); - assert(instancePlugin.instance.connector.sentMessages.length, "message was not sent"); + mockInstance.connector.sentMessages = []; + await mockInstance.hooks.output.invoke(output, line); + assert(mockInstance.connector.sentMessages.length, "message was not sent"); count += 1; } } @@ -72,11 +77,11 @@ describe("global_chat plugin", function() { }); it("should ignore regular output", async function() { let count = 0; - for (let [_, output] of lines.testLines) { + for (let [line, output] of lines.testLines) { if (output.type !== "action" || output.action !== "CHAT") { - instancePlugin.instance.connector.sentMessages = []; - await instancePlugin.onOutput(output); - assert(!instancePlugin.instance.connector.sentMessages.length, "message was sent"); + mockInstance.connector.sentMessages = []; + await mockInstance.hooks.output.invoke(output, line); + assert(!mockInstance.connector.sentMessages.length, "message was sent"); count += 1; } } diff --git a/plugins/research_sync/controller.ts b/plugins/research_sync/controller.ts index 954e5f70..ba198dc0 100644 --- a/plugins/research_sync/controller.ts +++ b/plugins/research_sync/controller.ts @@ -1,6 +1,6 @@ import fs from "node:fs/promises"; import path from "path"; -import { BaseControllerPlugin } from "@clusterio/controller"; +import type { ControllerPluginContext } from "@clusterio/controller"; import { Static } from "@sinclair/typebox"; import * as lib from "@clusterio/lib"; @@ -49,61 +49,39 @@ async function saveTechnologies( await lib.safeOutputFile(filePath, JSON.stringify([...technologies.entries()], null, "\t")); } -export class ControllerPlugin extends BaseControllerPlugin { - technologies!: Map; - technologiesDirty = true; - progressRateLimiter!: lib.RateLimiter; - progressBroadcastId!: ReturnType | null; - progressToBroadcast!: Set; +export default async function(context: ControllerPluginContext) { + const { controller, logger, plugin } = context; + const technologies = await loadTechnologies(controller.config, logger); + let technologiesDirty = true; + const progressToBroadcast = new Set(); - async init() { - this.technologies = await loadTechnologies(this.controller.config, this.logger); - this.progressRateLimiter = new RateLimiter({ - maxRate: 1, - action: () => this.broadcastProgress(), - }); - - this.progressBroadcastId = null; - this.progressToBroadcast = new Set(); - - this.controller.handle(ContributionEvent, this.handleContributionEvent.bind(this)); - this.controller.handle(FinishedEvent, this.handleFinishedEvent.bind(this)); - this.controller.handle(SyncTechnologiesRequest, this.handleSyncTechnologiesRequest.bind(this)); - } - - async onSaveData() { - if (this.technologiesDirty) { - this.technologiesDirty = false; - await saveTechnologies(this.controller.config, this.technologies, this.logger); - } - } - - async onShutdown() { - this.progressRateLimiter.cancel(); - } - - broadcastProgress() { + function broadcastProgress() { let techs = []; - for (let name of this.progressToBroadcast) { - let tech = this.technologies.get(name); + for (let name of progressToBroadcast) { + let tech = technologies.get(name); if (tech && tech.progress) { techs.push(new TechnologyProgress(name, tech.level, tech.progress)); } } - this.progressToBroadcast.clear(); + progressToBroadcast.clear(); if (techs.length) { - this.controller.sendTo("allInstances", new ProgressEvent(techs)); + controller.sendTo("allInstances", new ProgressEvent(techs)); } } - async handleContributionEvent(event: ContributionEvent) { + const progressRateLimiter = new RateLimiter({ + maxRate: 1, + action: broadcastProgress, + }); + + controller.handle(ContributionEvent, async (event: ContributionEvent) => { let { name, level, contribution } = event; - let tech = this.technologies.get(name); + let tech = technologies.get(name); if (!tech) { tech = { level, progress: 0, researched: false }; - this.technologies.set(name, tech); - this.technologiesDirty = true; + technologies.set(name, tech); + technologiesDirty = true; // Ignore contribution to already researched technologies } else if (tech.level > level || tech.level === level && tech.researched) { @@ -124,31 +102,31 @@ export class ControllerPlugin extends BaseControllerPlugin { let newProgress = tech.progress! + contribution; if (newProgress < 1) { tech.progress = newProgress; - this.progressToBroadcast.add(name); - this.progressRateLimiter!.activate(); + progressToBroadcast.add(name); + progressRateLimiter.activate(); } else { tech.researched = true; tech.progress = null; - this.progressToBroadcast.delete(name); + progressToBroadcast.delete(name); - this.controller.sendTo("allInstances", new FinishedEvent(name, tech.level)); + controller.sendTo("allInstances", new FinishedEvent(name, tech.level)); } - this.technologiesDirty = true; - } + technologiesDirty = true; + }); - async handleFinishedEvent(event: FinishedEvent) { + controller.handle(FinishedEvent, async (event: FinishedEvent) => { let { name, level } = event; - let tech = this.technologies.get(name); + let tech = technologies.get(name); if (!tech || tech.level <= level) { - this.controller.sendTo("allInstances", event); - this.progressToBroadcast.delete(name); - this.technologies.set(name, { level, progress: null, researched: true }); - this.technologiesDirty = true; + controller.sendTo("allInstances", event); + progressToBroadcast.delete(name); + technologies.set(name, { level, progress: null, researched: true }); + technologiesDirty = true; } - } + }); - async handleSyncTechnologiesRequest(request: SyncTechnologiesRequest): Promise { + controller.handle(SyncTechnologiesRequest, async (request: SyncTechnologiesRequest): Promise => { function baseLevel(name: string): number { let match = /-(\d+)$/.exec(name); if (!match) { @@ -159,14 +137,14 @@ export class ControllerPlugin extends BaseControllerPlugin { for (let instanceTech of request.technologies) { let { name, level, progress, researched } = instanceTech; - let tech = this.technologies.get(name); + let tech = technologies.get(name); if (!tech) { - this.technologies.set(name, { level, progress, researched }); - this.technologiesDirty = true; + technologies.set(name, { level, progress, researched }); + technologiesDirty = true; if (progress) { - this.progressToBroadcast.add(name); + progressToBroadcast.add(name); } else if (researched || baseLevel(name) !== level) { - this.controller.sendTo("allInstances", new FinishedEvent(name, level)); + controller.sendTo("allInstances", new FinishedEvent(name, level)); } } else { @@ -177,33 +155,44 @@ export class ControllerPlugin extends BaseControllerPlugin { if (tech.level < level || researched) { // Send update if the unlocked level is greater if (level - Number(!researched) > tech.level - Number(!tech.researched)) { - this.controller.sendTo("allInstances", new FinishedEvent(name, level - Number(!researched))); + controller.sendTo("allInstances", new FinishedEvent(name, level - Number(!researched))); } tech.level = level; tech.progress = progress; tech.researched = researched; if (progress) { - this.progressToBroadcast.add(name); + progressToBroadcast.add(name); } else { - this.progressToBroadcast.delete(name); + progressToBroadcast.delete(name); } - this.technologiesDirty = true; + technologiesDirty = true; } else if (tech.progress && progress && tech.progress < progress) { tech.progress = progress; - this.progressToBroadcast.add(name); - this.technologiesDirty = true; + progressToBroadcast.add(name); + technologiesDirty = true; } } } - this.progressRateLimiter.activate(); + progressRateLimiter.activate(); - let technologies = []; - for (let [name, tech] of this.technologies) { - technologies.push(new TechnologySync(name, tech.level, tech.progress, tech.researched)); + let result = []; + for (let [name, tech] of technologies) { + result.push(new TechnologySync(name, tech.level, tech.progress, tech.researched)); } - return technologies; - } + return result; + }); + + controller.hooks.save.attach(plugin.name, async () => { + if (technologiesDirty) { + technologiesDirty = false; + await saveTechnologies(controller.config, technologies, logger); + } + }); + + controller.hooks.shutdown.attach(plugin.name, async () => { + progressRateLimiter.cancel(); + }); } diff --git a/plugins/research_sync/instance.ts b/plugins/research_sync/instance.ts index 5e254d86..77200f7d 100644 --- a/plugins/research_sync/instance.ts +++ b/plugins/research_sync/instance.ts @@ -1,5 +1,5 @@ import * as lib from "@clusterio/lib"; -import { BaseInstancePlugin } from "@clusterio/host"; +import type { InstancePluginContext } from "@clusterio/host"; import { ContributionEvent, ProgressEvent, @@ -19,54 +19,57 @@ type IpcFinished = { level: number, }; -export class InstancePlugin extends BaseInstancePlugin { - syncStarted!: boolean; +export default async function(context: InstancePluginContext) { + const { instance, logger, plugin } = context; + let syncStarted = false; - unexpectedError(err: Error) { - this.logger.error(`Unexpected error:\n${err.stack}`); + // Commands are sent one at a time so they execute in order + let rconQueue: Promise = Promise.resolve(); + function sendOrderedRcon(message: string, expectEmpty = false) { + const result = rconQueue.then(() => instance.sendRcon(message, expectEmpty, plugin.name)); + rconQueue = result.catch(() => {}); + return result; } - async init() { - this.instance.server.on("ipc-research_sync:contribution", (tech: IpcContribution) => { - this.researchContribution(tech).catch(err => this.unexpectedError(err)); - }); - this.instance.server.on("ipc-research_sync:finished", (tech: IpcFinished) => { - this.researchFinished(tech).catch(err => this.unexpectedError(err)); - }); - - this.syncStarted = false; - this.instance.handle(ProgressEvent, this.handleProgressEvent.bind(this)); - this.instance.handle(FinishedEvent, this.handleFinishedEvent.bind(this)); + function unexpectedError(err: Error) { + logger.error(`Unexpected error:\n${err.stack}`); } - async researchContribution(tech: IpcContribution) { - this.instance.sendTo("controller", new ContributionEvent(tech.name, tech.level, tech.contribution)); + async function researchContribution(tech: IpcContribution) { + instance.sendTo("controller", new ContributionEvent(tech.name, tech.level, tech.contribution)); } - async handleProgressEvent(event: ProgressEvent) { - if (!this.syncStarted || !["starting", "running"].includes(this.instance.status)) { + async function researchFinished(tech: IpcFinished) { + instance.sendTo("controller", new FinishedEvent(tech.name, tech.level)); + } + + instance.server.on("ipc-research_sync:contribution", (tech: IpcContribution) => { + researchContribution(tech).catch(err => unexpectedError(err)); + }); + instance.server.on("ipc-research_sync:finished", (tech: IpcFinished) => { + researchFinished(tech).catch(err => unexpectedError(err)); + }); + + instance.handle(ProgressEvent, async (event: ProgressEvent) => { + if (!syncStarted || !["starting", "running"].includes(instance.status)) { return; } let techsJson = lib.escapeString(JSON.stringify(event.technologies)); - await this.sendOrderedRcon(`/sc research_sync.update_progress("${techsJson}")`, true); - } + await sendOrderedRcon(`/sc research_sync.update_progress("${techsJson}")`, true); + }); - async researchFinished(tech: IpcFinished) { - this.instance.sendTo("controller", new FinishedEvent(tech.name, tech.level)); - } - - async handleFinishedEvent(event: FinishedEvent) { - if (!this.syncStarted || !["starting", "running"].includes(this.instance.status)) { + instance.handle(FinishedEvent, async (event: FinishedEvent) => { + if (!syncStarted || !["starting", "running"].includes(instance.status)) { return; } let { name, level } = event; - await this.sendOrderedRcon( + await sendOrderedRcon( `/sc research_sync.research_technology("${lib.escapeString(name)}", ${level})`, true ); - } + }); - async onStart() { - let dumpJson = await this.sendOrderedRcon("/sc research_sync.dump_technologies()"); + instance.hooks.start.attach(plugin.name, async () => { + let dumpJson = await sendOrderedRcon("/sc research_sync.dump_technologies()"); let techsToSend = []; let instanceTechs = new Map(); for (let tech of JSON.parse(dumpJson)) { @@ -79,8 +82,8 @@ export class InstancePlugin extends BaseInstancePlugin { instanceTechs.set(tech.name, tech); } - let controllerTechs = await this.instance.sendTo("controller", new SyncTechnologiesRequest(techsToSend)); - this.syncStarted = true; + let controllerTechs = await instance.sendTo("controller", new SyncTechnologiesRequest(techsToSend)); + syncStarted = true; let techsToSync = []; for (let controllerTech of controllerTechs) { let { name, level, progress, researched } = controllerTech; @@ -97,7 +100,7 @@ export class InstancePlugin extends BaseInstancePlugin { if (techsToSync.length) { let syncJson = lib.escapeString(JSON.stringify(techsToSync)); - await this.sendOrderedRcon(`/sc research_sync.sync_technologies("${syncJson}")`, true); + await sendOrderedRcon(`/sc research_sync.sync_technologies("${syncJson}")`, true); } - } + }); } diff --git a/plugins/statistics_exporter/instance.ts b/plugins/statistics_exporter/instance.ts index 82382ada..74ac0003 100644 --- a/plugins/statistics_exporter/instance.ts +++ b/plugins/statistics_exporter/instance.ts @@ -1,5 +1,5 @@ import * as lib from "@clusterio/lib"; -import { BaseInstancePlugin } from "@clusterio/host"; +import type { Instance, InstancePluginContext } from "@clusterio/host"; import { Gauge } from "@clusterio/lib"; @@ -103,79 +103,81 @@ function setForceFlowStatistic( } -export class InstancePlugin extends BaseInstancePlugin { - async gatherMetrics() { - let string = await this.sendRcon("/sc statistics_exporter.export()"); - let stats: IpcStats; - try { - stats = JSON.parse(string); - } catch (err: any) { - throw new Error(`Error parsing statistics JSON: ${err.message}, content "${string}"`); - } +export async function gatherMetrics(instance: Instance, pluginName: string) { + let string = await instance.sendRcon("/sc statistics_exporter.export()", false, pluginName); + let stats: IpcStats; + try { + stats = JSON.parse(string); + } catch (err: any) { + throw new Error(`Error parsing statistics JSON: ${err.message}, content "${string}"`); + } - let instanceId = this.instance.id; - instanceGameTicksTotal.labels(String(instanceId)).set(stats.game_tick); - instancePlayerCount.labels(String(instanceId)).set(stats.player_count); + let instanceId = instance.id; + instanceGameTicksTotal.labels(String(instanceId)).set(stats.game_tick); + instancePlayerCount.labels(String(instanceId)).set(stats.player_count); - // Remove existing platform mappings and stats for this instance - instancePlatformMapping.removeAll({ instance_id: String(instanceId) }); - instancePlatformSpeed.removeAll({ instance_id: String(instanceId) }); - instancePlatformWeight.removeAll({ instance_id: String(instanceId) }); + // Remove existing platform mappings and stats for this instance + instancePlatformMapping.removeAll({ instance_id: String(instanceId) }); + instancePlatformSpeed.removeAll({ instance_id: String(instanceId) }); + instancePlatformWeight.removeAll({ instance_id: String(instanceId) }); - // Set new platform mappings and stats - for (let [platformName, platform] of Object.entries(stats.platforms)) { - instancePlatformMapping.labels( - String(instanceId), - platformName, - platform.force, - platform.surface - ).set(1); + // Set new platform mappings and stats + for (let [platformName, platform] of Object.entries(stats.platforms)) { + instancePlatformMapping.labels( + String(instanceId), + platformName, + platform.force, + platform.surface + ).set(1); - instancePlatformSpeed.labels( - String(instanceId), - platform.force, - platform.surface - ).set(platform.speed); + instancePlatformSpeed.labels( + String(instanceId), + platform.force, + platform.surface + ).set(platform.speed); - instancePlatformWeight.labels( - String(instanceId), - platform.force, - platform.surface - ).set(platform.weight); - } + instancePlatformWeight.labels( + String(instanceId), + platform.force, + platform.surface + ).set(platform.weight); + } - for (let [surfaceName, surfaceStats] of Object.entries(stats.surface_statistics)) { - for (let [forceName, flowStatistics] of Object.entries(surfaceStats.force_flow_statistics)) { - for (let [statisticName, statistic] of Object.entries(flowStatistics)) { - for (let [direction, counts] of Object.entries(statistic)) { - // eslint-disable-next-line max-depth - for (let [item, value] of Object.entries(counts)) { - setForceFlowStatistic( - instanceId, - surfaceName, - forceName, - statisticName, - direction, - item, - value - ); - } + for (let [surfaceName, surfaceStats] of Object.entries(stats.surface_statistics)) { + for (let [forceName, flowStatistics] of Object.entries(surfaceStats.force_flow_statistics)) { + for (let [statisticName, statistic] of Object.entries(flowStatistics)) { + for (let [direction, counts] of Object.entries(statistic)) { + // eslint-disable-next-line max-depth + for (let [item, value] of Object.entries(counts)) { + setForceFlowStatistic( + instanceId, + surfaceName, + forceName, + statisticName, + direction, + item, + value + ); } } } + } - for (let [direction, counts] of Object.entries(surfaceStats.game_flow_statistics.pollution_statistics)) { - for (let [item, value] of Object.entries(counts)) { - instanceGameFlowStatistics.labels( - String(instanceId), surfaceName, "pollution_statistics", direction, item - ).set(value); - } + for (let [direction, counts] of Object.entries(surfaceStats.game_flow_statistics.pollution_statistics)) { + for (let [item, value] of Object.entries(counts)) { + instanceGameFlowStatistics.labels( + String(instanceId), surfaceName, "pollution_statistics", direction, item + ).set(value); } } } +} - async onMetrics() { - if (this.instance.status !== "running") { +export default async function(context: InstancePluginContext) { + const { instance, plugin } = context; + + instance.hooks.metrics.attach(plugin.name, async () => { + if (instance.status !== "running") { return; } @@ -183,9 +185,9 @@ export class InstancePlugin extends BaseInstancePlugin { // take a long time for the command to go through if the command // stream is overloaded. Should the timeout be exceeded the // previous values for the metrics will end up being sent to controller. - let timeoutMs = this.instance.config.get("statistics_exporter.command_timeout") * 1000; - await lib.timeout(this.gatherMetrics(), timeoutMs, undefined); - } + let timeoutMs = instance.config.get("statistics_exporter.command_timeout") * 1000; + await lib.timeout(gatherMetrics(instance, plugin.name), timeoutMs, undefined); + }); } export const _instancePlayerCount = instancePlayerCount; diff --git a/plugins/statistics_exporter/test/plugin.js b/plugins/statistics_exporter/test/plugin.js index 49f1439e..170ace2b 100644 --- a/plugins/statistics_exporter/test/plugin.js +++ b/plugins/statistics_exporter/test/plugin.js @@ -6,25 +6,23 @@ import { plugin as info } from "../dist/node/index.js"; describe("statistics_exporter plugin", function() { - describe("class InstancePlugin", function() { - let instancePlugin; - let mockInstance = new mock.MockInstance(); - mockInstance.mockConfigEntries.set("statistics_exporter.command_timeout", 1); + describe("instance entrypoint", function() { + let mockInstance; before(async function() { - instancePlugin = new instance.InstancePlugin(info, mockInstance); - await instancePlugin.init(); + ({ instance: mockInstance } = await mock.loadInstancePlugin(instance.default, info)); + mockInstance.mockConfigEntries.set("statistics_exporter.command_timeout", 1); }); - describe(".onMetrics()", function() { + describe("metrics hook", function() { it("should not send commands before running", async function() { - instancePlugin.instance.server.rconCommands.length = 0; - instancePlugin.instance.status = "starting"; - await instancePlugin.onMetrics(); - assert(instancePlugin.instance.server.rconCommands.length === 0, "commands were run"); + mockInstance.server.rconCommands.length = 0; + mockInstance.status = "starting"; + await mockInstance.hooks.metrics.invoke(); + assert(mockInstance.server.rconCommands.length === 0, "commands were run"); }); it("should record statistics", async function() { - instancePlugin.instance.status = "running"; - instancePlugin.instance.server.rconCommandResults.set( + mockInstance.status = "running"; + mockInstance.server.rconCommandResults.set( "/sc statistics_exporter.export()", JSON.stringify({ game_tick: 100, @@ -58,7 +56,7 @@ describe("statistics_exporter plugin", function() { }) ); - await instancePlugin.onMetrics(); + await mockInstance.hooks.metrics.invoke(); assert.equal(instance._instancePlayerCount.labels("7357").get(), 3); assert.equal(instance._instanceGameTicksTotal.labels("7357").get(), 100); @@ -90,7 +88,7 @@ describe("statistics_exporter plugin", function() { }); it("should pass on JSON parse errors", async function() { let string = "An error occured\n"; - instancePlugin.instance.server.rconCommandResults.set( + mockInstance.server.rconCommandResults.set( "/sc statistics_exporter.export()", string ); @@ -102,7 +100,7 @@ describe("statistics_exporter plugin", function() { } await assert.rejects( - instancePlugin.onMetrics(), + instance.gatherMetrics(mockInstance, info.name), new Error(`Error parsing statistics JSON: ${errorMessage}, content "${string}"`) ); });