mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-13 07:04:53 +09:00
Merge pull request #821 from Cooldude2606/feature/version-selector
Add drop down version selector based on Wube version archive
This commit is contained in:
24 files changed
+2245
-9
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,127 @@
|
||||
const assert = require("assert").strict;
|
||||
const { ValueCache, wait } = require("@clusterio/lib");
|
||||
|
||||
describe("ValueCache", function () {
|
||||
describe("constructor", function () {
|
||||
it("constructs with a fetch function", function() {
|
||||
const fetcher = async () => 42;
|
||||
const cache = new ValueCache(fetcher);
|
||||
assert.ok(cache);
|
||||
});
|
||||
});
|
||||
|
||||
describe("get", function() {
|
||||
it("throws TypeError for negative maxAgeMs", async function() {
|
||||
const fetcher = async () => 42;
|
||||
const cache = new ValueCache(fetcher);
|
||||
|
||||
assert.throws(() => cache.get(-1), err => {
|
||||
assert.equal(err instanceof TypeError, true);
|
||||
assert.match(String(err.message), /maxAgeMs/);
|
||||
return true;
|
||||
});
|
||||
});
|
||||
it("uses a default of 0 for maxAgeMs", async function() {
|
||||
let callCount = 0;
|
||||
const fetcher = async () => {
|
||||
callCount += 1;
|
||||
return callCount;
|
||||
};
|
||||
|
||||
const cache = new ValueCache(fetcher);
|
||||
|
||||
const v1 = await cache.get();
|
||||
const v2 = await cache.get();
|
||||
|
||||
assert.equal(v1, 1);
|
||||
assert.equal(v2, 2);
|
||||
assert.equal(callCount, 2);
|
||||
});
|
||||
it("returns cached value when within maxAgeMs", async function() {
|
||||
let callCount = 0;
|
||||
const fetcher = async () => {
|
||||
callCount += 1;
|
||||
return callCount;
|
||||
};
|
||||
|
||||
const cache = new ValueCache(fetcher);
|
||||
|
||||
const v1 = await cache.get(1000);
|
||||
const v2 = await cache.get(1000);
|
||||
|
||||
assert.equal(v1, 1);
|
||||
assert.equal(v2, 1);
|
||||
assert.equal(callCount, 1);
|
||||
});
|
||||
it("refreshes the cache when older than maxAgeMs", async function() {
|
||||
let callCount = 0;
|
||||
const fetcher = async () => {
|
||||
callCount += 1;
|
||||
return callCount;
|
||||
};
|
||||
|
||||
const cache = new ValueCache(fetcher);
|
||||
|
||||
const v1 = await cache.get(50);
|
||||
const v2 = await cache.get(1000); // Use cached value
|
||||
await wait(100);
|
||||
const v3 = await cache.get(50); // Stale after 100ms wait
|
||||
const v4 = await cache.get(1000); // Use cached value
|
||||
|
||||
assert.equal(v1, 1);
|
||||
assert.equal(v2, 1);
|
||||
assert.equal(v3, 2);
|
||||
assert.equal(v4, 2);
|
||||
assert.equal(callCount, 2);
|
||||
});
|
||||
it("shares in-flight fetch between concurrent callers", async function() {
|
||||
let callCount = 0;
|
||||
let resolveFetcher;
|
||||
const fetcher = () => (
|
||||
new Promise(resolve => {
|
||||
callCount += 1;
|
||||
resolveFetcher = resolve;
|
||||
})
|
||||
);
|
||||
|
||||
const cache = new ValueCache(fetcher);
|
||||
|
||||
const p1 = cache.get(1000);
|
||||
const p2 = cache.get(1000);
|
||||
|
||||
assert.equal(callCount, 1);
|
||||
|
||||
resolveFetcher(42);
|
||||
const [v1, v2] = await Promise.all([p1, p2]);
|
||||
|
||||
assert.equal(v1, 42);
|
||||
assert.equal(v2, 42);
|
||||
assert.equal(callCount, 1);
|
||||
});
|
||||
it("propagates errors from the fetcher and clears ongoing fetch", async function() {
|
||||
let callCount = 0;
|
||||
const error = new Error("fetch failed");
|
||||
const fetcher = async () => {
|
||||
callCount += 1;
|
||||
throw error;
|
||||
};
|
||||
|
||||
const cache = new ValueCache(fetcher);
|
||||
|
||||
await assert.rejects(cache.get(), err => {
|
||||
assert.equal(err, error);
|
||||
return true;
|
||||
});
|
||||
|
||||
assert.equal(callCount, 1);
|
||||
|
||||
// After a failure, a new attempt to fetch should be made
|
||||
await assert.rejects(cache.get(), err => {
|
||||
assert.equal(err, error);
|
||||
return true;
|
||||
});
|
||||
|
||||
assert.equal(callCount, 2);
|
||||
});
|
||||
});
|
||||
});
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
const assert = require("assert").strict;
|
||||
const { readFile } = require("fs-extra");
|
||||
|
||||
const { fetchFactorioVersions } = require("@clusterio/lib");
|
||||
const { slowTest } = require("../../integration");
|
||||
|
||||
describe("FactorioVersions", function() {
|
||||
describe("fetchFactorioVersions", function() {
|
||||
const originalFetch = global.fetch;
|
||||
|
||||
beforeEach(() => {
|
||||
global.fetch = undefined;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
global.fetch = originalFetch;
|
||||
});
|
||||
|
||||
function mockFetch(html) {
|
||||
const mockResponse = {
|
||||
ok: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
text: async () => html,
|
||||
};
|
||||
|
||||
global.fetch = async () => mockResponse;
|
||||
}
|
||||
|
||||
it("fetches and parsed versions with stable / experimental flags", async function() {
|
||||
mockFetch(`
|
||||
<html>
|
||||
<body>
|
||||
<a class="
|
||||
slot-button-inline
|
||||
version-button-experimental
|
||||
" href="/download/archive/2.0.73">
|
||||
2.0.73
|
||||
</a>
|
||||
</body>
|
||||
<body>
|
||||
<a class="
|
||||
slot-button-inline
|
||||
version-button-stable
|
||||
" href="/download/archive/2.0.72">
|
||||
2.0.72
|
||||
</a>
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
|
||||
const versions = await fetchFactorioVersions();
|
||||
assert.deepEqual(versions, [
|
||||
{
|
||||
stable: false,
|
||||
version: "2.0.73",
|
||||
headlessUrl: "www.factorio.com/get-download/2.0.73/headless/linux64",
|
||||
},
|
||||
{
|
||||
stable: true,
|
||||
version: "2.0.72",
|
||||
headlessUrl: "www.factorio.com/get-download/2.0.72/headless/linux64",
|
||||
},
|
||||
]);
|
||||
});
|
||||
it("deduplicates versions by last occurrence", async function() {
|
||||
mockFetch(`
|
||||
<html>
|
||||
<body>
|
||||
<a class="
|
||||
slot-button-inline
|
||||
version-button-experimental
|
||||
" href="/download/archive/2.0.73">
|
||||
2.0.73
|
||||
</a>
|
||||
</body>
|
||||
<body>
|
||||
<a class="
|
||||
slot-button-inline
|
||||
version-button-stable
|
||||
" href="/download/archive/2.0.73">
|
||||
2.0.73
|
||||
</a>
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
|
||||
const versions = await fetchFactorioVersions();
|
||||
assert.deepEqual(versions, [
|
||||
{
|
||||
stable: true,
|
||||
version: "2.0.73",
|
||||
headlessUrl: "www.factorio.com/get-download/2.0.73/headless/linux64",
|
||||
},
|
||||
]);
|
||||
});
|
||||
it("thorws on HTTP error status", async function() {
|
||||
const mockResponse = {
|
||||
ok: false,
|
||||
status: 500,
|
||||
statusText: "Server Error",
|
||||
text: async () => "",
|
||||
};
|
||||
|
||||
global.fetch = async () => mockResponse;
|
||||
|
||||
await assert.rejects(fetchFactorioVersions(), /HTTP 500 Server Error/);
|
||||
});
|
||||
it("thorws on network error status", async function() {
|
||||
global.fetch = async () => {
|
||||
throw new Error("Network Error");
|
||||
};
|
||||
|
||||
await assert.rejects(fetchFactorioVersions(), /Network Error/);
|
||||
});
|
||||
it("parses versions from saved html file", async function() {
|
||||
mockFetch(await readFile("test/file/html/factorio-download-archive.html"));
|
||||
|
||||
const versions = await fetchFactorioVersions();
|
||||
assert.equal(versions.length, 105);
|
||||
assert.equal(versions.filter(v => v.stable).length, 48);
|
||||
assert.equal(versions.filter(v => !v.stable).length, 57);
|
||||
});
|
||||
it("parses versions from live api", async function() {
|
||||
slowTest(this);
|
||||
global.fetch = originalFetch;
|
||||
|
||||
const versions = await fetchFactorioVersions();
|
||||
assert.ok(versions.length >= 105);
|
||||
assert.ok(versions.filter(v => v.stable).length >= 48);
|
||||
assert.ok(versions.filter(v => !v.stable).length >= 57);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -34,4 +34,44 @@ describe("messages/controller", function() {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("External", function() {
|
||||
it("runs", async function() {
|
||||
let callCount = 0;
|
||||
const versions = [
|
||||
{ version: "1.2.3", stable: true, headlessUrl: "path/to/download" },
|
||||
{ version: "1.2.4", stable: false, headlessUrl: "path/to/download" },
|
||||
];
|
||||
|
||||
// Mock the cache to avoid needless fetches
|
||||
controller.factorioVersions = new lib.ValueCache(async () => {
|
||||
callCount += 1;
|
||||
return versions;
|
||||
});
|
||||
|
||||
// First call should result in a fetch
|
||||
const versions1 = await controlConnection.handleFactorioVersionsRequest(
|
||||
new lib.FactorioVersionsRequest()
|
||||
);
|
||||
|
||||
assert.deepEqual(versions1, versions);
|
||||
assert.equal(callCount, 1);
|
||||
|
||||
// Second call should not fetch due to default maxAgeMs being greater than 0
|
||||
const versions2 = await controlConnection.handleFactorioVersionsRequest(
|
||||
new lib.FactorioVersionsRequest()
|
||||
);
|
||||
|
||||
assert.deepEqual(versions2, versions);
|
||||
assert.equal(callCount, 1);
|
||||
|
||||
// Setting maxAgeMs to 0 should result in a new fetch
|
||||
const versions3 = await controlConnection.handleFactorioVersionsRequest(
|
||||
new lib.FactorioVersionsRequest(0)
|
||||
);
|
||||
|
||||
assert.deepEqual(versions3, versions);
|
||||
assert.equal(callCount, 2);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user