mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-11 23:04:52 +09:00
Add tests for release channel resolution and caching
Cover fetchLatestReleases and resolveReleaseChannel, the controller's latest-releases cache with its on-disk offline fallback, the LatestReleasesRequest round-trip, and FactorioServer.setTargetVersion. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
9da48d5387
commit
f793ec33b3
3 files changed
+146
No files matched your search
@@ -233,6 +233,16 @@ describe("host/server", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe(".setTargetVersion()", function() {
|
||||
it("should override the target version before init", function() {
|
||||
const fresh = new hostServer.FactorioServer(
|
||||
path.join("test", "file", "factorio"), writePath, {}
|
||||
);
|
||||
fresh.setTargetVersion("0.1.5");
|
||||
assert.equal(fresh._targetVersion, "0.1.5");
|
||||
});
|
||||
});
|
||||
|
||||
describe(".init()", function() {
|
||||
it("should not throw on first call", async function() {
|
||||
await server.init();
|
||||
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
const assert = require("assert").strict;
|
||||
|
||||
const { fetchLatestReleases, resolveReleaseChannel } = require("@clusterio/lib");
|
||||
const { slowTest } = require("../../integration");
|
||||
|
||||
describe("LatestReleases", function() {
|
||||
describe("fetchLatestReleases", function() {
|
||||
const originalFetch = global.fetch;
|
||||
|
||||
beforeEach(() => {
|
||||
global.fetch = undefined;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
global.fetch = originalFetch;
|
||||
});
|
||||
|
||||
const sample = {
|
||||
experimental: { alpha: "2.1.8", demo: "2.0.77", expansion: "2.1.8", headless: "2.1.8" },
|
||||
stable: { alpha: "2.0.77", demo: "2.0.77", expansion: "2.0.77", headless: "2.0.77" },
|
||||
};
|
||||
|
||||
function mockFetch(json) {
|
||||
global.fetch = async () => ({
|
||||
ok: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
json: async () => json,
|
||||
});
|
||||
}
|
||||
|
||||
it("fetches and returns the releases json", async function() {
|
||||
mockFetch(sample);
|
||||
|
||||
const releases = await fetchLatestReleases();
|
||||
assert.deepEqual(releases, sample);
|
||||
});
|
||||
it("throws on HTTP error status", async function() {
|
||||
global.fetch = async () => ({
|
||||
ok: false,
|
||||
status: 500,
|
||||
statusText: "Server Error",
|
||||
json: async () => ({}),
|
||||
});
|
||||
|
||||
await assert.rejects(fetchLatestReleases(), /HTTP 500 Server Error/);
|
||||
});
|
||||
it("throws on network error", async function() {
|
||||
global.fetch = async () => {
|
||||
throw new Error("Network Error");
|
||||
};
|
||||
|
||||
await assert.rejects(fetchLatestReleases(), /Network Error/);
|
||||
});
|
||||
it("fetches from the live api", async function() {
|
||||
slowTest(this);
|
||||
global.fetch = originalFetch;
|
||||
|
||||
const releases = await fetchLatestReleases();
|
||||
assert.ok(releases.stable, "expected a stable channel");
|
||||
assert.ok(releases.experimental, "expected an experimental channel");
|
||||
assert.equal(typeof releases.stable.headless, "string");
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveReleaseChannel", function() {
|
||||
const releases = {
|
||||
experimental: { alpha: "2.1.8", demo: "2.0.77", headless: "2.1.8" },
|
||||
stable: { alpha: "2.0.77", headless: "2.0.77" },
|
||||
// A channel without a headless build, to exercise the fallback.
|
||||
oddball: { alpha: "1.2.3" },
|
||||
};
|
||||
|
||||
it("returns the headless build version by default", function() {
|
||||
assert.equal(resolveReleaseChannel(releases, "stable"), "2.0.77");
|
||||
assert.equal(resolveReleaseChannel(releases, "experimental"), "2.1.8");
|
||||
});
|
||||
it("returns the requested build version", function() {
|
||||
assert.equal(resolveReleaseChannel(releases, "experimental", "demo"), "2.0.77");
|
||||
});
|
||||
it("falls back to the first build when headless is missing", function() {
|
||||
assert.equal(resolveReleaseChannel(releases, "oddball"), "1.2.3");
|
||||
});
|
||||
it("returns undefined for an unknown channel", function() {
|
||||
assert.equal(resolveReleaseChannel(releases, "nightly"), undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,8 @@
|
||||
"use strict";
|
||||
const assert = require("assert").strict;
|
||||
const fs = require("node:fs/promises");
|
||||
const os = require("node:os");
|
||||
const path = require("node:path");
|
||||
const lib = require("@clusterio/lib");
|
||||
|
||||
const { Controller, ControlConnection } = require("@clusterio/controller");
|
||||
@@ -416,5 +419,50 @@ describe("messages/controller", function() {
|
||||
assert.deepEqual(versions3, versions);
|
||||
assert.equal(callCount, 2);
|
||||
});
|
||||
it("round trips LatestReleasesRequest", function() {
|
||||
testRoundTripJsonSerialisable(lib.LatestReleasesRequest, [[], [0], [60000]]);
|
||||
});
|
||||
it("caches the latest releases and persists them for offline use", async function() {
|
||||
const releases = {
|
||||
stable: { headless: "2.0.77" },
|
||||
experimental: { headless: "2.1.8" },
|
||||
};
|
||||
const databaseDirectory = await fs.mkdtemp(path.join(os.tmpdir(), "clusterio-test-"));
|
||||
controller.config.set("controller.database_directory", databaseDirectory);
|
||||
const cachePath = path.join(databaseDirectory, "latest-releases.json");
|
||||
const originalFetch = global.fetch;
|
||||
try {
|
||||
let callCount = 0;
|
||||
global.fetch = async () => {
|
||||
callCount += 1;
|
||||
return { ok: true, status: 200, statusText: "OK", json: async () => releases };
|
||||
};
|
||||
|
||||
// First call fetches from the api and persists the result to disk
|
||||
const fetched = await controlConnection.handleLatestReleasesRequest(
|
||||
new lib.LatestReleasesRequest(0)
|
||||
);
|
||||
assert.deepEqual(fetched, releases);
|
||||
assert.equal(callCount, 1);
|
||||
assert.deepEqual(JSON.parse(await fs.readFile(cachePath, "utf8")), releases);
|
||||
|
||||
// When the api is unavailable the persisted copy is served instead
|
||||
global.fetch = async () => { throw new Error("Network Error"); };
|
||||
const cached = await controlConnection.handleLatestReleasesRequest(
|
||||
new lib.LatestReleasesRequest(0)
|
||||
);
|
||||
assert.deepEqual(cached, releases);
|
||||
|
||||
// With no persisted copy and an unavailable api an empty object is returned
|
||||
await fs.rm(cachePath);
|
||||
const empty = await controlConnection.handleLatestReleasesRequest(
|
||||
new lib.LatestReleasesRequest(0)
|
||||
);
|
||||
assert.deepEqual(empty, {});
|
||||
} finally {
|
||||
global.fetch = originalFetch;
|
||||
await fs.rm(databaseDirectory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user