Convert global_chat, research_sync and statistics_exporter to entrypoint functions

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bbassieandClaude Opus 5.5 committed 2026-10-08 05:15:00 +00:00
1 parent bd609c3c54
commit cfeb4b6a4a
7 files changed
+231 -237

No files matched your search

+4 -4
View File
@@ -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);
}
});
}
+22 -25
View File
@@ -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?
}
+25 -20
View File
@@ -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;
}
}
+62 -73
View File
@@ -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<string, Technology>;
technologiesDirty = true;
progressRateLimiter!: lib.RateLimiter;
progressBroadcastId!: ReturnType<typeof setInterval> | null;
progressToBroadcast!: Set<string>;
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<string>();
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<TechnologySync[]> {
controller.handle(SyncTechnologiesRequest, async (request: SyncTechnologiesRequest): Promise<TechnologySync[]> => {
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();
});
}
+39 -36
View File
@@ -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<unknown> = 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);
}
}
});
}
+65 -63
View File
@@ -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;
+14 -16
View File
@@ -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}"`)
);
});