mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-11 23:04:52 +09:00
271 lines
6.5 KiB
JavaScript
271 lines
6.5 KiB
JavaScript
import events from "node:events";
|
|
import http from "node:http";
|
|
import express from "express";
|
|
|
|
import * as lib from "@clusterio/lib";
|
|
import { User, UserManager, UserRecord, InstanceManager, ControllerHooks } from "@clusterio/controller";
|
|
import { HostHooks, InstanceHooks } from "@clusterio/host";
|
|
|
|
const addr = lib.Address.fromShorthand;
|
|
|
|
export class MockLogger {
|
|
child() { return this; }
|
|
|
|
fatal() { }
|
|
|
|
error() { }
|
|
|
|
warn() { }
|
|
|
|
audit() { }
|
|
|
|
info() { }
|
|
|
|
server() { }
|
|
|
|
verbose() { }
|
|
}
|
|
|
|
export class MockSocket {
|
|
constructor() {
|
|
this.sentMessages = [];
|
|
this.events = new Map();
|
|
}
|
|
|
|
send(message) {
|
|
this.sentMessages.push(message);
|
|
}
|
|
|
|
on(event, fn) {
|
|
this.events.set(event, fn);
|
|
}
|
|
|
|
/* eslint-disable accessor-pairs */
|
|
set onclose(fn) {
|
|
this.on("close", (code, reason) => fn({ code, reason }));
|
|
}
|
|
|
|
set onerror(fn) {
|
|
this.on("error", fn);
|
|
}
|
|
|
|
set onopen(fn) {
|
|
this.on("open", fn);
|
|
}
|
|
|
|
set onmessage(fn) {
|
|
this.on("message", (data) => fn({ data }));
|
|
}
|
|
|
|
terminate() {
|
|
this.terminateCalled = true;
|
|
}
|
|
|
|
close() {
|
|
this.closeCalled = true;
|
|
}
|
|
}
|
|
|
|
export class MockConnector extends lib.BaseConnector {
|
|
constructor(src, dst) {
|
|
super(src, dst);
|
|
|
|
this.valid = true;
|
|
this.connected = true;
|
|
this.hasSession = true;
|
|
this.sentMessages = [];
|
|
}
|
|
|
|
send(message) {
|
|
this.sentMessages.push(message);
|
|
setImmediate(() => this.emit("send", message));
|
|
}
|
|
|
|
sendDisconnectReady() {
|
|
this.send(new lib.MessageDisconnect("ready"));
|
|
}
|
|
}
|
|
|
|
export class MockServer extends events.EventEmitter {
|
|
constructor() {
|
|
super();
|
|
this.reset();
|
|
}
|
|
|
|
reset() {
|
|
this.rconCommands = [];
|
|
this.rconCommandResults = new Map();
|
|
}
|
|
|
|
async sendRcon(command) {
|
|
this.rconCommands.push(command);
|
|
let result = this.rconCommandResults.get(command);
|
|
if (!result) {
|
|
result = { response: "" };
|
|
}
|
|
if (result instanceof Error || typeof result === "string") {
|
|
result = { response: result };
|
|
}
|
|
if (result.time) {
|
|
await lib.wait(result.time);
|
|
}
|
|
if (result.response instanceof Error) {
|
|
throw result.response;
|
|
}
|
|
return result.response || "";
|
|
}
|
|
}
|
|
|
|
export class MockInstance extends lib.Link {
|
|
constructor() {
|
|
super(new MockConnector(addr({ instanceId: 7357 }), addr({ hostId: 1 })));
|
|
this.logger = new MockLogger();
|
|
this.server = new MockServer();
|
|
this.name = "test";
|
|
this.id = 7357;
|
|
this.status = "running";
|
|
this.hooks = new InstanceHooks(this.logger);
|
|
this.loadedPlugins = new Set();
|
|
this.mockConfigEntries = new Map([
|
|
["instance.id", 7357],
|
|
["factorio.enable_save_patching", true],
|
|
["factorio.enable_script_commands", true],
|
|
]);
|
|
this.config = {
|
|
get: (name) => {
|
|
if (this.mockConfigEntries.has(name)) {
|
|
return this.mockConfigEntries.get(name);
|
|
}
|
|
throw Error(`mock for field ${name} is not implemented`);
|
|
},
|
|
};
|
|
}
|
|
|
|
async sendRcon(command, expectEmpty, plugin) {
|
|
return await this.server.sendRcon(command);
|
|
}
|
|
}
|
|
|
|
export class MockHost extends lib.Link {
|
|
constructor() {
|
|
super(new MockConnector(addr({ hostId: 1 }), addr("controller")));
|
|
this.hooks = new HostHooks(new MockLogger());
|
|
this.loadedPlugins = new Set();
|
|
}
|
|
}
|
|
|
|
export class MockControl extends lib.Link { }
|
|
|
|
export class MockController {
|
|
constructor() {
|
|
this.app = express();
|
|
this.app.locals.controller = this;
|
|
this.app.locals.streams = new Map();
|
|
this.mockConfigEntries = new Map([
|
|
["controller.public_url", "test"],
|
|
["controller.auth_secret", "TestSecretDoNotUse"],
|
|
["controller.proxy_stream_timeout", 1],
|
|
["controller.metrics_timeout", 1],
|
|
]);
|
|
this.config = {
|
|
get: (name) => {
|
|
if (this.mockConfigEntries.has(name)) {
|
|
return this.mockConfigEntries.get(name);
|
|
}
|
|
throw Error(`mock for field ${name} is not implemented`);
|
|
},
|
|
};
|
|
|
|
const roleProvider = new lib.MemoryDatastoreProvider(new Map([
|
|
[0, lib.Role.fromJSON({ id: 0, name: "Admin", description: "admin", permissions: ["core.admin"] })],
|
|
[1, lib.Role.fromJSON({ id: 1, name: "Player", description: "player", permissions: [] })],
|
|
]));
|
|
|
|
this.roles = new lib.SubscribableDatastore(roleProvider, roleProvider.value);
|
|
this.roles.get(1).grantDefaultPermissions(); // Todo replace "core.admin" above with grantAdminPermissions
|
|
|
|
const userProvider = new lib.MemoryDatastoreProvider(new Map([
|
|
["test", UserRecord.fromJSON({ name: "test", roles: [0, 1] })],
|
|
["player", UserRecord.fromJSON({ name: "player", roles: [1] })],
|
|
]));
|
|
|
|
this.users = new UserManager(
|
|
new lib.SubscribableDatastore(userProvider, userProvider.value),
|
|
this.roles, this.config
|
|
);
|
|
|
|
this.instances = new InstanceManager(new lib.KeyValueDatastore(), this);
|
|
|
|
this.hosts = new lib.KeyValueDatastore();
|
|
this.handles = new Map();
|
|
this.hooks = new ControllerHooks(new MockLogger());
|
|
this.loadedPlugins = new Set();
|
|
this.wsServer = { hostConnections: new Map() };
|
|
}
|
|
|
|
get authSecret() {
|
|
return Buffer.from(this.config.get("controller.auth_secret"), "base64");
|
|
}
|
|
|
|
handle(eventClass, handler) {
|
|
this.handles.set(eventClass, handler);
|
|
}
|
|
|
|
getControllerUrl() {
|
|
return "http://controller.example/";
|
|
}
|
|
|
|
async startServer() {
|
|
this.server = http.createServer(this.app);
|
|
await new Promise(resolve => {
|
|
this.server.listen(0, "127.0.0.1", resolve);
|
|
});
|
|
let address = this.server.address();
|
|
return `http://127.0.0.1:${address.port}`;
|
|
}
|
|
|
|
async stopServer() {
|
|
if (this.server) {
|
|
this.server.close();
|
|
this.server.unref();
|
|
}
|
|
}
|
|
}
|
|
|
|
export async function createControllerPlugin(ControllerPluginClass, info) {
|
|
let controller = new MockController();
|
|
let metrics = {};
|
|
let logger = new MockLogger();
|
|
let plugin = new ControllerPluginClass(info, controller, metrics, logger);
|
|
await plugin.init();
|
|
return plugin;
|
|
}
|
|
|
|
export async function createInstancePlugin(InstancePluginClass, info) {
|
|
let instance = new MockInstance();
|
|
let host = new MockHost();
|
|
let plugin = new InstancePluginClass(info, instance, host, new MockLogger());
|
|
await plugin.init();
|
|
return plugin;
|
|
}
|
|
|
|
export async function loadControllerPlugin(entrypoint, info) {
|
|
const controller = new MockController();
|
|
await entrypoint({ controller, metrics: {}, logger: new MockLogger(), plugin: info });
|
|
return controller;
|
|
}
|
|
|
|
export async function loadInstancePlugin(entrypoint, info) {
|
|
const instance = new MockInstance();
|
|
const host = new MockHost();
|
|
await entrypoint({ instance, host, logger: new MockLogger(), plugin: info });
|
|
return { instance, host };
|
|
}
|
|
|
|
export function getHandler(link, Class) {
|
|
if (link instanceof MockController) {
|
|
return link.handles.get(Class);
|
|
}
|
|
return link._requestHandlers.get(Class) ?? link._eventHandlers.get(Class);
|
|
}
|