mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-13 15:14:52 +09:00
Merge pull request #868 from Hornwitser/small-things-16
Small things 16
This commit is contained in:
17 files changed
+330
-301
No files matched your search
@@ -32,6 +32,7 @@ export default class BaseConnection extends lib.Link {
|
||||
this.handle(lib.ModPackGetRequest, this.handleModPackGetRequest.bind(this));
|
||||
this.handle(lib.ModPackGetDefaultRequest, this.handleModPackGetDefaultRequest.bind(this));
|
||||
this.handle(lib.ModDownloadRequest, this.handleModDownloadRequest.bind(this));
|
||||
this.handle(lib.FactorioVersionsRequest, this.handleFactorioVersionsRequest.bind(this));
|
||||
|
||||
this.handle(lib.SubscriptionRequest, this.handleSubscriptionRequest.bind(this));
|
||||
this.connector.on("close", () => {
|
||||
@@ -106,4 +107,8 @@ export default class BaseConnection extends lib.Link {
|
||||
async handleSubscriptionRequest(request: lib.SubscriptionRequest, src: lib.Address, dst: lib.Address) {
|
||||
return await this._controller.subscriptions.handleRequest(this, request, src, dst);
|
||||
}
|
||||
|
||||
async handleFactorioVersionsRequest(request: lib.FactorioVersionsRequest) {
|
||||
return await this._controller.factorioVersions.get(request.maxAgeMs);
|
||||
}
|
||||
}
|
||||
@@ -127,7 +127,6 @@ export default class ControlConnection extends BaseConnection {
|
||||
this.handle(lib.PluginUpdateRequest, this.handlePluginUpdateRequest.bind(this));
|
||||
this.handle(lib.PluginInstallRequest, this.handlePluginInstallRequest.bind(this));
|
||||
this.handle(lib.DebugDumpWsRequest, this.handleDebugDumpWsRequest.bind(this));
|
||||
this.handle(lib.FactorioVersionsRequest, this.handleFactorioVersionsRequest.bind(this));
|
||||
}
|
||||
|
||||
validateIngress(message: lib.MessageRequest | lib.MessageEvent) {
|
||||
@@ -1240,10 +1239,6 @@ export default class ControlConnection extends BaseConnection {
|
||||
this.connector._socket!.clusterio_ignore_dump = true;
|
||||
this._controller.debugEvents.on("message", this.ws_dumper);
|
||||
}
|
||||
|
||||
async handleFactorioVersionsRequest(request: lib.FactorioVersionsRequest) {
|
||||
return await this._controller.factorioVersions.get(request.maxAgeMs);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ControlConnection;
|
||||
@@ -112,7 +112,14 @@ export default class Controller {
|
||||
// Cache for mod portal requests
|
||||
modPortalCache = new Map<string, { timestamp: number, data: any[] }>();
|
||||
// Cache for factorio versions
|
||||
factorioVersions = new lib.ValueCache(lib.fetchFactorioVersions);
|
||||
factorioVersions = new lib.ValueCache(async () => {
|
||||
try {
|
||||
return await lib.fetchFactorioVersions();
|
||||
} catch (err: any) {
|
||||
logger.warn(`Failed to retrieve Factorio versions ${err.message}`);
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
static async bootstrap(config: lib.ControllerConfig) {
|
||||
let databaseDirectory = config.get("controller.database_directory");
|
||||
|
||||
@@ -81,7 +81,6 @@ export default class HostConnection extends BaseConnection {
|
||||
this.handle(lib.InstanceSaveDetailsUpdatesEvent, this.handleInstanceSaveDetailsUpdatesEvent.bind(this));
|
||||
this.handle(lib.LogMessageEvent, this.handleLogMessageEvent.bind(this));
|
||||
this.handle(lib.InstancePlayerUpdateEvent, this.handleInstancePlayerUpdateEvent.bind(this));
|
||||
this.handle(lib.FactorioVersionsRequest, this.handleFactorioVersionsRequest.bind(this));
|
||||
this.snoopEvent(lib.InstanceAdminlistUpdateEvent, this.snoopInstanceAdminlistUpdateEvent.bind(this));
|
||||
this.snoopEvent(lib.InstanceBanlistUpdateEvent, this.snoopInstanceBanlistUpdateEvent.bind(this));
|
||||
this.snoopEvent(lib.InstanceWhitelistUpdateEvent, this.snoopInstanceWhitelistUpdateEvent.bind(this));
|
||||
@@ -359,10 +358,6 @@ export default class HostConnection extends BaseConnection {
|
||||
});
|
||||
}
|
||||
|
||||
async handleFactorioVersionsRequest(request: lib.FactorioVersionsRequest) {
|
||||
return await this._controller.factorioVersions.get(request.maxAgeMs);
|
||||
}
|
||||
|
||||
async snoopInstanceAdminlistUpdateEvent(event: lib.InstanceAdminlistUpdateEvent, src: lib.Address) {
|
||||
if (src.type !== lib.Address.instance) {
|
||||
return;
|
||||
|
||||
@@ -74,6 +74,10 @@ async function discoverInstances(instancesDir: string) {
|
||||
return instances;
|
||||
}
|
||||
|
||||
// Requests that wake up instances are responsible for making sure the
|
||||
// instance status is correctly updated even if they fail with an error. This
|
||||
// means they should typically use try catch on anything that can throw
|
||||
// and call notifyExit upon any errors.
|
||||
const instanceStartingMessages = new Set([
|
||||
lib.InstanceStartRequest.name,
|
||||
lib.InstanceLoadScenarioRequest.name,
|
||||
|
||||
@@ -637,9 +637,10 @@ end`.replace(/\r?\n/g, " ");
|
||||
}
|
||||
|
||||
async init(pluginInfos: lib.PluginNodeEnvInfo[]) {
|
||||
// After changing the status this function is responsible for calling notifyExit if it throws.
|
||||
this.notifyStatus("starting");
|
||||
const factorioVersions = await this.sendTo("controller", new lib.FactorioVersionsRequest());
|
||||
try {
|
||||
const factorioVersions = await this.sendTo("controller", new lib.FactorioVersionsRequest());
|
||||
await this._loadStats();
|
||||
await this.server.checkForUpdates(factorioVersions);
|
||||
await this.server.init();
|
||||
|
||||
@@ -978,6 +978,10 @@ export class FactorioServer extends events.EventEmitter<FactorioServerEvents> {
|
||||
.sort((v1, v2) => versionOrder(v1.version, v2.version))
|
||||
[0];
|
||||
|
||||
if (!latestVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
const installedVersions = await listFactorioVersions(this._factorioDir);
|
||||
if (!installedVersions.versions.has(latestVersion.version)) {
|
||||
const v = latestVersion.version;
|
||||
|
||||
@@ -290,18 +290,50 @@ export class KeyValueDatastore<
|
||||
export class SubscribableDatastore<
|
||||
V extends SubscribableValue,
|
||||
> extends Datastore<V["id"], V> {
|
||||
/** Last time an update occurred */
|
||||
lastUpdatedAtMs: number = 0;
|
||||
|
||||
private static maxUpdatedAtMs(values: Iterable<SubscribableValue>) {
|
||||
let max = 0;
|
||||
for (const value of values) {
|
||||
// Prevent one NaN, undefined, +Inf or similar from breaking the datastore.
|
||||
if (Number.isFinite(value.updatedAtMs)) {
|
||||
max = Math.max(max, value.updatedAtMs);
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
private incrementLastUpdatedMs() {
|
||||
this.lastUpdatedAtMs = Math.max(Date.now(), this.lastUpdatedAtMs + 1);
|
||||
}
|
||||
|
||||
constructor(
|
||||
provider: DatastoreProvider<V["id"], V> = new MemoryDatastoreProvider(),
|
||||
data = new Map<V["id"], V>(),
|
||||
) {
|
||||
super(provider, data);
|
||||
this.lastUpdatedAtMs = SubscribableDatastore.maxUpdatedAtMs(this.data.values());
|
||||
}
|
||||
|
||||
async load() {
|
||||
await super.load();
|
||||
this.lastUpdatedAtMs = SubscribableDatastore.maxUpdatedAtMs(this.data.values());
|
||||
}
|
||||
|
||||
// Set the value in the datastore, be careful of race conditions if you await any functions before calling set
|
||||
set(value: V) {
|
||||
this.incrementLastUpdatedMs();
|
||||
this.data.set(value.id, value);
|
||||
value.updatedAtMs = Date.now();
|
||||
value.updatedAtMs = this.lastUpdatedAtMs;
|
||||
this.touch([value]);
|
||||
}
|
||||
|
||||
// Set many values at once from an array of key value pairs
|
||||
setMany(values: V[]) {
|
||||
const nowMs = Date.now();
|
||||
this.incrementLastUpdatedMs();
|
||||
for (const value of values) {
|
||||
value.updatedAtMs = nowMs;
|
||||
value.updatedAtMs = this.lastUpdatedAtMs;
|
||||
this.data.set(value.id, value);
|
||||
}
|
||||
this.touch(values);
|
||||
@@ -309,18 +341,19 @@ export class SubscribableDatastore<
|
||||
|
||||
// Delete a value in the datastore
|
||||
delete(value: V) {
|
||||
this.incrementLastUpdatedMs();
|
||||
this.data.delete(value.id);
|
||||
value.updatedAtMs = Date.now();
|
||||
value.updatedAtMs = this.lastUpdatedAtMs;
|
||||
value.isDeleted = true;
|
||||
this.touch([value]);
|
||||
}
|
||||
|
||||
// Delete many values at once from an array of values
|
||||
deleteMany(values: V[]) {
|
||||
const nowMs = Date.now();
|
||||
this.incrementLastUpdatedMs();
|
||||
for (const value of values) {
|
||||
value.isDeleted = true;
|
||||
value.updatedAtMs = nowMs;
|
||||
value.updatedAtMs = this.lastUpdatedAtMs;
|
||||
this.data.delete(value.id);
|
||||
}
|
||||
this.touch(values);
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import { Static, Type } from "@sinclair/typebox";
|
||||
import { FullVersionSchema, integerFullVersion, isFullVersion } from "../data";
|
||||
import { FullVersionSchema, integerFullVersion, isFullVersion } from "../data/version";
|
||||
|
||||
const ARCHIVE_URL = "https://factorio.com/download/archive";
|
||||
const ARCHIVE_URL = "https://factorio.com/download/archive/";
|
||||
|
||||
/**
|
||||
* Represents the parsed result of a factorio version
|
||||
|
||||
@@ -272,18 +272,31 @@ function labelsToKey(labels: LabelValues, metricLabels: string[]) {
|
||||
function keyToLabels(key: string) {
|
||||
let labels = new Map<string, string>();
|
||||
|
||||
if (key !== "") {
|
||||
for (let pair of key.split(",")) {
|
||||
let [name, value] = pair.split("=", 2);
|
||||
if (!name || value === undefined) {
|
||||
continue;
|
||||
let pos = 0;
|
||||
let matchLabel = /([^=]+)="(([^\\"]|\\["n\\])+)"/y;
|
||||
while (pos < key.length) {
|
||||
if (pos !== 0) {
|
||||
/* istanbul ignore if */
|
||||
if (key[pos] !== ",") {
|
||||
// Should be unreachable
|
||||
throw Error(`Expected , at pos ${pos} in "${key}"`);
|
||||
}
|
||||
pos += 1;
|
||||
}
|
||||
matchLabel.lastIndex = pos;
|
||||
const match = matchLabel.exec(key);
|
||||
/* istanbul ignore else */
|
||||
if (match) {
|
||||
let [, name, value] = match;
|
||||
labels.set(name, value
|
||||
.slice(1, -1)
|
||||
.replace(/\\"/g, "\"")
|
||||
.replace(/\\n/g, "\n")
|
||||
.replace(/\\\\/g, "\\")
|
||||
);
|
||||
pos += match[0].length;
|
||||
} else {
|
||||
// Should be unreachable
|
||||
throw new Error(`Failed to parse label at pos ${pos} in "${key}"`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,11 @@ function NpmPluginInstall({ setApplyAction, form, target }: NpmActionProps) {
|
||||
</>;
|
||||
}
|
||||
|
||||
export function NpmButton(props: { target: lib.AddressShorthand, canRestart?: boolean, disabled?: boolean }) {
|
||||
export function NpmButton(props: {
|
||||
target: "controller" | { hostId: number },
|
||||
canRestart?: boolean,
|
||||
disabled?: boolean
|
||||
}) {
|
||||
const control = useContext(ControlContext);
|
||||
const account = useAccount();
|
||||
const [open, setOpen] = useState(false);
|
||||
@@ -93,6 +97,7 @@ export function NpmButton(props: { target: lib.AddressShorthand, canRestart?: bo
|
||||
remoteUpdates: true, pluginUpdates: true, pluginInstall: true,
|
||||
});
|
||||
|
||||
const hostId = props.target === "controller" ? undefined : props.target.hostId;
|
||||
useEffect(() => {
|
||||
if (props.target === "controller") {
|
||||
if (!account.hasPermission("core.controller.get_config")) {
|
||||
@@ -121,7 +126,7 @@ export function NpmButton(props: { target: lib.AddressShorthand, canRestart?: bo
|
||||
});
|
||||
}).catch(notifyErrorHandler("Failed to fetch host config for remote updates"));
|
||||
}
|
||||
}, [props.target]);
|
||||
}, [hostId]);
|
||||
|
||||
function onValuesChange({ action } : { action?: string }) {
|
||||
if (action) {
|
||||
|
||||
@@ -252,6 +252,28 @@ describe("controller/src/Controller", function() {
|
||||
assert.equal(controller.config.restartRequired, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe(".factorioVersions", function() {
|
||||
const _fetch = global.fetch;
|
||||
const fetchCalls = [];
|
||||
before(function() {
|
||||
global.fetch = async (url) => {
|
||||
fetchCalls.push(url);
|
||||
return { ok: false, status: 503, statusText: "Service Unavailable" };
|
||||
};
|
||||
});
|
||||
it("should handle fetch failure", async function() {
|
||||
await assert.deepEqual(
|
||||
await controller.factorioVersions.get(0),
|
||||
[],
|
||||
);
|
||||
assert(fetchCalls.length === 1, "expected one fetch call");
|
||||
assert(fetchCalls[0].indexOf("/download") !== -1, "fetch call to be to a URL with /download in it");
|
||||
});
|
||||
after(function() {
|
||||
global.fetch = _fetch;
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("finalise", function() {
|
||||
describe(".finaliseHosts()", function() {
|
||||
|
||||
@@ -37,6 +37,12 @@ describe("controller/User", function () {
|
||||
user.saveRecord();
|
||||
assert(user.updatedAtMs > prev);
|
||||
});
|
||||
it("should always increment updatedAtMs even if called in quick succession", function () {
|
||||
user.saveRecord();
|
||||
let prev = user.updatedAtMs;
|
||||
user.saveRecord();
|
||||
assert(user.updatedAtMs > prev, "updatedAtMs did not increase");
|
||||
});
|
||||
});
|
||||
|
||||
describe(".set()", function () {
|
||||
|
||||
+116
-256
@@ -342,264 +342,124 @@ describe("host/server", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("full version", function() {
|
||||
it("should do nothing when there is no newer version", async function() {
|
||||
server._factorioDir = path.join("test", "file", "factorio");
|
||||
server._targetVersion = "0.1.1";
|
||||
await server.checkForUpdates([{
|
||||
stable: true,
|
||||
version: "0.1.1",
|
||||
headlessUrl: "test1",
|
||||
}, {
|
||||
stable: false,
|
||||
version: "0.1.0",
|
||||
headlessUrl: "test2",
|
||||
}]);
|
||||
for (const [suite, target] of [
|
||||
["full version", "0.1.1"],
|
||||
["partial version", "0.1"],
|
||||
["latest version", "latest"],
|
||||
]) {
|
||||
/* eslint-disable no-loop-func */
|
||||
describe(suite, function() {
|
||||
it("should do nothing when there are no versions", async function() {
|
||||
server._factorioDir = path.join("test", "file", "factorio");
|
||||
server._targetVersion = target;
|
||||
await server.checkForUpdates([]);
|
||||
|
||||
assert.equal(fetchCalledWith, null);
|
||||
assert.equal(fetchCalledWith, null);
|
||||
});
|
||||
if (target !== "latest") {
|
||||
it("should do nothing when no version matches", async function() {
|
||||
server._factorioDir = path.join("test", "file", "factorio");
|
||||
server._targetVersion = target;
|
||||
await server.checkForUpdates([{
|
||||
stable: true,
|
||||
version: "0.2.1",
|
||||
headlessUrl: "test1",
|
||||
}, {
|
||||
stable: false,
|
||||
version: "0.2.0",
|
||||
headlessUrl: "test2",
|
||||
}]);
|
||||
|
||||
assert.equal(fetchCalledWith, null);
|
||||
});
|
||||
}
|
||||
it("should do nothing when there is no newer version", async function() {
|
||||
server._factorioDir = path.join("test", "file", "factorio");
|
||||
server._targetVersion = target;
|
||||
await server.checkForUpdates([{
|
||||
stable: true,
|
||||
version: "0.1.1",
|
||||
headlessUrl: "test1",
|
||||
}, {
|
||||
stable: false,
|
||||
version: "0.1.0",
|
||||
headlessUrl: "test2",
|
||||
}]);
|
||||
|
||||
assert.equal(fetchCalledWith, null);
|
||||
});
|
||||
it("should do nothing for direct installs", async function() {
|
||||
server._factorioDir = path.join("test", "file", "factorio", "0.1.1");
|
||||
server._targetVersion = target === "0.1.1" ? "0.1.5" : target;
|
||||
await server.checkForUpdates([{
|
||||
stable: true,
|
||||
version: "0.1.5",
|
||||
headlessUrl: "test1",
|
||||
}, {
|
||||
stable: true,
|
||||
version: "0.1.1",
|
||||
headlessUrl: "test1",
|
||||
}, {
|
||||
stable: false,
|
||||
version: "0.1.0",
|
||||
headlessUrl: "test2",
|
||||
}]);
|
||||
|
||||
assert.equal(fetchCalledWith, null);
|
||||
});
|
||||
it("should do nothing when on windows", async function() {
|
||||
let logLine = null;
|
||||
server._logger = { info: line => { logLine = line; } };
|
||||
Object.defineProperty(process, "platform", { value: "win32" });
|
||||
|
||||
server._factorioDir = path.join("test", "file", "factorio");
|
||||
server._targetVersion = target === "0.1.1" ? "0.1.5" : target;
|
||||
await server.checkForUpdates([{
|
||||
stable: true,
|
||||
version: "0.1.5",
|
||||
headlessUrl: "test1",
|
||||
}, {
|
||||
stable: true,
|
||||
version: "0.1.1",
|
||||
headlessUrl: "test2",
|
||||
}, {
|
||||
stable: false,
|
||||
version: "0.1.0",
|
||||
headlessUrl: "test3",
|
||||
}]);
|
||||
|
||||
assert.equal(fetchCalledWith, null);
|
||||
assert.ok(logLine !== null);
|
||||
assert.ok(logLine.endsWith("but must be manually downloaded"));
|
||||
});
|
||||
it("should do attempt to download on linux", async function() {
|
||||
let logLine = null;
|
||||
server._logger = { info: line => { logLine = line; } };
|
||||
Object.defineProperty(process, "platform", { value: "linux" });
|
||||
|
||||
server._factorioDir = path.join("test", "file", "factorio");
|
||||
server._targetVersion = target === "0.1.1" ? "0.1.5" : target;
|
||||
await assert.rejects(server.checkForUpdates([{
|
||||
stable: true,
|
||||
version: "0.1.5",
|
||||
headlessUrl: "test1",
|
||||
}, {
|
||||
stable: true,
|
||||
version: "0.1.1",
|
||||
headlessUrl: "test2",
|
||||
}, {
|
||||
stable: false,
|
||||
version: "0.1.0",
|
||||
headlessUrl: "test3",
|
||||
}]), new Error("Failed to fetch test1: -1 Fetch called"));
|
||||
|
||||
assert.equal(fetchCalledWith, "test1");
|
||||
assert.ok(logLine !== null);
|
||||
assert.ok(logLine.endsWith("starting download..."));
|
||||
});
|
||||
});
|
||||
it("should do nothing for direct installs", async function() {
|
||||
server._factorioDir = path.join("test", "file", "factorio", "0.1.1");
|
||||
server._targetVersion = "0.1.5";
|
||||
await server.checkForUpdates([{
|
||||
stable: true,
|
||||
version: "0.1.5",
|
||||
headlessUrl: "test1",
|
||||
}, {
|
||||
stable: true,
|
||||
version: "0.1.1",
|
||||
headlessUrl: "test1",
|
||||
}, {
|
||||
stable: false,
|
||||
version: "0.1.0",
|
||||
headlessUrl: "test2",
|
||||
}]);
|
||||
|
||||
assert.equal(fetchCalledWith, null);
|
||||
});
|
||||
it("should do nothing when on windows", async function() {
|
||||
let logLine = null;
|
||||
server._logger = { info: line => { logLine = line; } };
|
||||
Object.defineProperty(process, "platform", { value: "win32" });
|
||||
|
||||
server._factorioDir = path.join("test", "file", "factorio");
|
||||
server._targetVersion = "0.1.5";
|
||||
await server.checkForUpdates([{
|
||||
stable: true,
|
||||
version: "0.1.5",
|
||||
headlessUrl: "test1",
|
||||
}, {
|
||||
stable: true,
|
||||
version: "0.1.1",
|
||||
headlessUrl: "test2",
|
||||
}, {
|
||||
stable: false,
|
||||
version: "0.1.0",
|
||||
headlessUrl: "test3",
|
||||
}]);
|
||||
|
||||
assert.equal(fetchCalledWith, null);
|
||||
assert.ok(logLine !== null);
|
||||
assert.ok(logLine.endsWith("but must be manually downloaded"));
|
||||
});
|
||||
it("should do attempt to download on linux", async function() {
|
||||
let logLine = null;
|
||||
server._logger = { info: line => { logLine = line; } };
|
||||
Object.defineProperty(process, "platform", { value: "linux" });
|
||||
|
||||
server._factorioDir = path.join("test", "file", "factorio");
|
||||
server._targetVersion = "0.1.5";
|
||||
await assert.rejects(server.checkForUpdates([{
|
||||
stable: true,
|
||||
version: "0.1.5",
|
||||
headlessUrl: "test1",
|
||||
}, {
|
||||
stable: true,
|
||||
version: "0.1.1",
|
||||
headlessUrl: "test2",
|
||||
}, {
|
||||
stable: false,
|
||||
version: "0.1.0",
|
||||
headlessUrl: "test3",
|
||||
}]), new Error("Failed to fetch test1: -1 Fetch called"));
|
||||
|
||||
assert.equal(fetchCalledWith, "test1");
|
||||
assert.ok(logLine !== null);
|
||||
assert.ok(logLine.endsWith("starting download..."));
|
||||
});
|
||||
});
|
||||
describe("partial version", function() {
|
||||
it("should do nothing when there is no newer version", async function() {
|
||||
server._factorioDir = path.join("test", "file", "factorio");
|
||||
server._targetVersion = "0.1";
|
||||
await server.checkForUpdates([{
|
||||
stable: true,
|
||||
version: "0.1.1",
|
||||
headlessUrl: "test1",
|
||||
}, {
|
||||
stable: false,
|
||||
version: "0.1.0",
|
||||
headlessUrl: "test2",
|
||||
}]);
|
||||
|
||||
assert.equal(fetchCalledWith, null);
|
||||
});
|
||||
it("should do nothing for direct installs", async function() {
|
||||
server._factorioDir = path.join("test", "file", "factorio", "0.1.1");
|
||||
server._targetVersion = "0.1";
|
||||
await server.checkForUpdates([{
|
||||
stable: true,
|
||||
version: "0.1.5",
|
||||
headlessUrl: "test1",
|
||||
}, {
|
||||
stable: true,
|
||||
version: "0.1.1",
|
||||
headlessUrl: "test1",
|
||||
}, {
|
||||
stable: false,
|
||||
version: "0.1.0",
|
||||
headlessUrl: "test2",
|
||||
}]);
|
||||
|
||||
assert.equal(fetchCalledWith, null);
|
||||
});
|
||||
it("should do nothing when on windows", async function() {
|
||||
let logLine = null;
|
||||
server._logger = { info: line => { logLine = line; } };
|
||||
Object.defineProperty(process, "platform", { value: "win32" });
|
||||
|
||||
server._factorioDir = path.join("test", "file", "factorio");
|
||||
server._targetVersion = "0.1";
|
||||
await server.checkForUpdates([{
|
||||
stable: true,
|
||||
version: "0.1.5",
|
||||
headlessUrl: "test1",
|
||||
}, {
|
||||
stable: true,
|
||||
version: "0.1.1",
|
||||
headlessUrl: "test2",
|
||||
}, {
|
||||
stable: false,
|
||||
version: "0.1.0",
|
||||
headlessUrl: "test3",
|
||||
}]);
|
||||
|
||||
assert.equal(fetchCalledWith, null);
|
||||
assert.ok(logLine !== null);
|
||||
assert.ok(logLine.endsWith("but must be manually downloaded"));
|
||||
});
|
||||
it("should do attempt to download on linux", async function() {
|
||||
let logLine = null;
|
||||
server._logger = { info: line => { logLine = line; } };
|
||||
Object.defineProperty(process, "platform", { value: "linux" });
|
||||
|
||||
server._factorioDir = path.join("test", "file", "factorio");
|
||||
server._targetVersion = "0.1";
|
||||
await assert.rejects(server.checkForUpdates([{
|
||||
stable: true,
|
||||
version: "0.1.5",
|
||||
headlessUrl: "test1",
|
||||
}, {
|
||||
stable: true,
|
||||
version: "0.1.1",
|
||||
headlessUrl: "test2",
|
||||
}, {
|
||||
stable: false,
|
||||
version: "0.1.0",
|
||||
headlessUrl: "test3",
|
||||
}]), new Error("Failed to fetch test1: -1 Fetch called"));
|
||||
|
||||
assert.equal(fetchCalledWith, "test1");
|
||||
assert.ok(logLine !== null);
|
||||
assert.ok(logLine.endsWith("starting download..."));
|
||||
});
|
||||
});
|
||||
describe("latest version", function() {
|
||||
it("should do nothing when there is no newer version", async function() {
|
||||
server._factorioDir = path.join("test", "file", "factorio");
|
||||
server._targetVersion = "latest";
|
||||
await server.checkForUpdates([{
|
||||
stable: true,
|
||||
version: "0.1.1",
|
||||
headlessUrl: "test1",
|
||||
}, {
|
||||
stable: false,
|
||||
version: "0.1.0",
|
||||
headlessUrl: "test2",
|
||||
}]);
|
||||
|
||||
assert.equal(fetchCalledWith, null);
|
||||
});
|
||||
it("should do nothing for direct installs", async function() {
|
||||
server._factorioDir = path.join("test", "file", "factorio", "0.1.1");
|
||||
server._targetVersion = "latest";
|
||||
await server.checkForUpdates([{
|
||||
stable: true,
|
||||
version: "0.1.5",
|
||||
headlessUrl: "test1",
|
||||
}, {
|
||||
stable: true,
|
||||
version: "0.1.1",
|
||||
headlessUrl: "test1",
|
||||
}, {
|
||||
stable: false,
|
||||
version: "0.1.0",
|
||||
headlessUrl: "test2",
|
||||
}]);
|
||||
|
||||
assert.equal(fetchCalledWith, null);
|
||||
});
|
||||
it("should do nothing when on windows", async function() {
|
||||
let logLine = null;
|
||||
server._logger = { info: line => { logLine = line; } };
|
||||
Object.defineProperty(process, "platform", { value: "win32" });
|
||||
|
||||
server._factorioDir = path.join("test", "file", "factorio");
|
||||
server._targetVersion = "latest";
|
||||
await server.checkForUpdates([{
|
||||
stable: true,
|
||||
version: "0.1.5",
|
||||
headlessUrl: "test1",
|
||||
}, {
|
||||
stable: true,
|
||||
version: "0.1.1",
|
||||
headlessUrl: "test2",
|
||||
}, {
|
||||
stable: false,
|
||||
version: "0.1.0",
|
||||
headlessUrl: "test3",
|
||||
}]);
|
||||
|
||||
assert.equal(fetchCalledWith, null);
|
||||
assert.ok(logLine !== null);
|
||||
assert.ok(logLine.endsWith("but must be manually downloaded"));
|
||||
});
|
||||
it("should do attempt to download on linux", async function() {
|
||||
let logLine = null;
|
||||
server._logger = { info: line => { logLine = line; } };
|
||||
Object.defineProperty(process, "platform", { value: "linux" });
|
||||
|
||||
server._factorioDir = path.join("test", "file", "factorio");
|
||||
server._targetVersion = "latest";
|
||||
await assert.rejects(server.checkForUpdates([{
|
||||
stable: true,
|
||||
version: "0.1.5",
|
||||
headlessUrl: "test1",
|
||||
}, {
|
||||
stable: true,
|
||||
version: "0.1.1",
|
||||
headlessUrl: "test2",
|
||||
}, {
|
||||
stable: false,
|
||||
version: "0.1.0",
|
||||
headlessUrl: "test3",
|
||||
}]), new Error("Failed to fetch test1: -1 Fetch called"));
|
||||
|
||||
assert.equal(fetchCalledWith, "test1");
|
||||
assert.ok(logLine !== null);
|
||||
assert.ok(logLine.endsWith("starting download..."));
|
||||
});
|
||||
});
|
||||
/* eslint-enable no-loop-func */
|
||||
}
|
||||
it("should download a version correctly (live api)", async function() {
|
||||
slowTest(this);
|
||||
if (_platform !== "linux") {
|
||||
|
||||
+76
-15
@@ -5,13 +5,6 @@ const lib = require("@clusterio/lib");
|
||||
const fs = require("fs-extra");
|
||||
const path = require("path");
|
||||
|
||||
async function incrementDateMs() {
|
||||
const prev = Date.now();
|
||||
do {
|
||||
await lib.wait(1);
|
||||
} while (prev >= Date.now());
|
||||
}
|
||||
|
||||
class MockDatastoreProvider extends lib.DatastoreProvider {
|
||||
constructor() {
|
||||
super();
|
||||
@@ -571,12 +564,31 @@ describe("lib/datastore", function() {
|
||||
eventsRaised = [];
|
||||
datastoreProvider = new MockDatastoreProvider();
|
||||
datastoreProvider.value = new Map([
|
||||
["foo", {id: "foo"}], ["bar", {id: "bar"}], ["baz", {id: "baz"}],
|
||||
["foo", {id: "foo", updatedAtMs: 10}], ["bar", {id: "bar"}], ["baz", {id: "baz"}],
|
||||
]);
|
||||
datastore = new lib.SubscribableDatastore(datastoreProvider, datastoreProvider.value);
|
||||
datastore.on("update", updates => eventsRaised.push(updates));
|
||||
});
|
||||
|
||||
function forceFuture() {
|
||||
const future = {id: "future"};
|
||||
for (let i = 1; i < 100; i++) {
|
||||
datastore.set(future);
|
||||
}
|
||||
return future.updatedAtMs;
|
||||
}
|
||||
|
||||
describe("lastUpdateAtMs", function() {
|
||||
it("is set to the highest value after constructing a new instance", function() {
|
||||
assert(datastore.lastUpdatedAtMs === 10, `${datastore.lastUpdatedAtMs} !== 10`);
|
||||
});
|
||||
it("is set to the highest value after loading from a provider", async function() {
|
||||
const future = Date.now() + 2000;
|
||||
datastore.provider.value = ["future", {id: "future", updatedAtMs: future}];
|
||||
await datastore.load();
|
||||
assert(datastore.lastUpdatedAtMs === future, `${datastore.lastUpdatedAtMs} !== ${future}`);
|
||||
});
|
||||
});
|
||||
describe("set", function() {
|
||||
it("can set a value", function() {
|
||||
const value = { id: "test" };
|
||||
@@ -598,12 +610,21 @@ describe("lib/datastore", function() {
|
||||
const value = { id: "test" };
|
||||
datastore.set(value);
|
||||
assert.notEqual(value.updatedAtMs, undefined);
|
||||
assert(value.updatedAtMs > 0, "updatedAtMs greater than zero");
|
||||
assert(value.updatedAtMs > 0, "updatedAtMs not greater than zero");
|
||||
});
|
||||
it("increments the updatedAtMs property", async function() {
|
||||
const value = { id: "test" };
|
||||
datastore.set(value);
|
||||
const prev = value.updatedAtMs;
|
||||
await incrementDateMs();
|
||||
datastore.set(value);
|
||||
assert(value.updatedAtMs > prev, "updatedAtMs must increase");
|
||||
});
|
||||
it("increments the updatedAtMs property past existing values", async function() {
|
||||
const future = forceFuture();
|
||||
const value = { id: "test" };
|
||||
datastore.set(value);
|
||||
assert(value.updatedAtMs > future, "updatedAtMs must increase");
|
||||
});
|
||||
});
|
||||
|
||||
describe("setMany", function() {
|
||||
@@ -632,11 +653,22 @@ describe("lib/datastore", function() {
|
||||
assert.notEqual(updates[1].updatedAtMs, undefined);
|
||||
assert(updates[0].updatedAtMs > 0, "updatedAtMs greater than zero");
|
||||
assert(updates[1].updatedAtMs > 0, "updatedAtMs greater than zero");
|
||||
const prev = [updates[0].updatedAtMs, updates[1].updatedAtMs];
|
||||
await incrementDateMs();
|
||||
});
|
||||
it("increments the updatedAtMs property", async function() {
|
||||
const value = { id: "test1" };
|
||||
datastore.set(value);
|
||||
const prev = value.updatedAtMs;
|
||||
const updates = [{ id: "test1" }, { id: "test2" }];
|
||||
datastore.setMany(updates);
|
||||
assert(updates[0].updatedAtMs > prev[0], "updatedAtMs must increase");
|
||||
assert(updates[1].updatedAtMs > prev[1], "updatedAtMs must increase");
|
||||
assert(updates[0].updatedAtMs > prev, "updatedAtMs must increase");
|
||||
assert(updates[1].updatedAtMs > prev, "updatedAtMs must increase");
|
||||
});
|
||||
it("increments the updatedAtMs property past existing values", async function() {
|
||||
const future = forceFuture();
|
||||
const updates = [{ id: "test1" }, { id: "test2" }];
|
||||
datastore.setMany(updates);
|
||||
assert(updates[0].updatedAtMs > future, "updatedAtMs must increase");
|
||||
assert(updates[1].updatedAtMs > future, "updatedAtMs must increase");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -667,7 +699,20 @@ describe("lib/datastore", function() {
|
||||
const value = { id: "test" };
|
||||
datastore.delete(value);
|
||||
assert.notEqual(value.updatedAtMs, undefined);
|
||||
assert(value.updatedAtMs > 0, "updatedAtMs greater than zero");
|
||||
assert(value.updatedAtMs > 0, "updatedAtMs not greater than zero");
|
||||
});
|
||||
it("increments the updatedAtMs property", function() {
|
||||
const value = { id: "test" };
|
||||
datastore.set(value);
|
||||
const prev = value.updatedAtMs;
|
||||
datastore.delete(value);
|
||||
assert(value.updatedAtMs > prev, "updatedAtMs must increase");
|
||||
});
|
||||
it("increments the updatedAtMs property past existing values", function() {
|
||||
const future = forceFuture();
|
||||
const value = { id: "test" };
|
||||
datastore.delete(value);
|
||||
assert(value.updatedAtMs > future, "updatedAtMs must increase");
|
||||
});
|
||||
it("sets the isDeleted property", function() {
|
||||
const value = { id: "test" };
|
||||
@@ -704,6 +749,22 @@ describe("lib/datastore", function() {
|
||||
assert(values[0].updatedAtMs > 0, "updatedAtMs greater than zero");
|
||||
assert(values[1].updatedAtMs > 0, "updatedAtMs greater than zero");
|
||||
});
|
||||
it("increments the updatedAtMs property", function() {
|
||||
const value = { id: "foo" };
|
||||
datastore.set(value);
|
||||
const prev = value.updatedAtMs;
|
||||
const values = [{id: "foo", updatedAtMs: prev}, {id: "bar", updatedAtMs: prev}];
|
||||
datastore.deleteMany(values);
|
||||
assert(values[0].updatedAtMs > prev, "updatedAtMs must increase");
|
||||
assert(values[1].updatedAtMs > prev, "updatedAtMs must increase");
|
||||
});
|
||||
it("increments the updatedAtMs property past existing values", function() {
|
||||
const future = forceFuture();
|
||||
const values = [{id: "foo"}, {id: "bar"}];
|
||||
datastore.deleteMany(values);
|
||||
assert(values[0].updatedAtMs > future, "updatedAtMs must increase");
|
||||
assert(values[1].updatedAtMs > future, "updatedAtMs must increase");
|
||||
});
|
||||
it("sets the isDeleted property", function() {
|
||||
const values = [{id: "foo"}, {id: "bar"}];
|
||||
datastore.deleteMany(values);
|
||||
|
||||
+6
@@ -121,6 +121,12 @@ describe("FactorioVersions", function() {
|
||||
assert.equal(versions.filter(v => v.stable).length, 48);
|
||||
assert.equal(versions.filter(v => !v.stable).length, 57);
|
||||
});
|
||||
it("parses versions from empty html file", async function() {
|
||||
mockFetch("");
|
||||
|
||||
const versions = await fetchFactorioVersions();
|
||||
assert.equal(versions.length, 0);
|
||||
});
|
||||
it("parses versions from live api", async function() {
|
||||
slowTest(this);
|
||||
global.fetch = originalFetch;
|
||||
|
||||
+14
-2
@@ -651,8 +651,20 @@ describe("lib/prometheus", function() {
|
||||
let gauge = new prometheus.Gauge(
|
||||
"test", "Help", { register: false, labels: ["a", "b"] }
|
||||
);
|
||||
gauge.labels('x\\\\\n\"\"\n\\', "2");
|
||||
gauge.removeAll({ a: 'x\\\\\n\"\"\n\\' });
|
||||
|
||||
let labelValue = "";
|
||||
// Include every ASCII codepoint
|
||||
for (let i = 0; i < 128; i++) {
|
||||
labelValue += String.fromCodePoint(i);
|
||||
}
|
||||
// Include Unicode codepoints from the basic and supplementary planes.
|
||||
labelValue += "©💣";
|
||||
|
||||
gauge.labels(labelValue, "2");
|
||||
gauge.removeAll({ a: labelValue });
|
||||
assert(gauge._values.size === 0, "Matching labels remained");
|
||||
gauge.labels("1", labelValue);
|
||||
gauge.removeAll({ a: "1" });
|
||||
assert(gauge._values.size === 0, "Matching labels remained");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user