mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-12 23:04:52 +09:00
Migrate the usage of CommonJS specific code and imports to ESM. Roughly summarised this: - Changes all require() calls to import statement or expressions. - Changes usage of module.exports to export statements. - Replaces require.resolve with import.meta.resolve. - Replaces conditional imports with conditional subpath imports. - Replaces module === require.main with import.meta.main. - Adds the full filename with extention to all relative imports. - Changes all usage of __dirname and __filename to their import.meta equivalents. - Sets "type": "module" in package.json files. - Removes usage of "use strict". - Adds .ts an an alias of .js for the webpack build. Due to differences in how require and import resolves files the plugin entrypoints will have to include the filename and extension after this migration in order to work.
407 lines
14 KiB
JavaScript
407 lines
14 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import events from "node:events";
|
|
import FormData from "form-data";
|
|
import http from "node:http";
|
|
|
|
import { wait } from "@clusterio/lib";
|
|
import * as routes from "@clusterio/controller/dist/node/src/routes.js";
|
|
import * as mock from "../mock.js";
|
|
|
|
describe("controller/src/routes", function() {
|
|
let controller;
|
|
let server;
|
|
let port;
|
|
beforeEach(async function() {
|
|
controller = new mock.MockController();
|
|
routes.addRouteHandlers(controller.app);
|
|
server = http.createServer(controller.app);
|
|
server.listen(0, "localhost");
|
|
await events.once(server, "listening");
|
|
port = server.address().port;
|
|
});
|
|
afterEach(function() {
|
|
server.close();
|
|
});
|
|
describe("/api/stream/:id", function() {
|
|
let endpoint;
|
|
beforeEach(function() {
|
|
endpoint = `http://localhost:${port}/api/stream`;
|
|
});
|
|
it("should respond with 404 if stream doesn't exist", async function() {
|
|
let response;
|
|
response = await fetch(`${endpoint}/01020304`);
|
|
assert.equal(response.status, 404);
|
|
response = await fetch(`${endpoint}/01020304`, {
|
|
method: "PUT", body: "test stream",
|
|
});
|
|
assert.equal(response.status, 404);
|
|
});
|
|
it("should respond with 500 if stream times out", async function() {
|
|
let stream = await routes.createProxyStream(controller.app);
|
|
let response = await fetch(`${endpoint}/${stream.id}`);
|
|
assert.equal(response.status, 500);
|
|
});
|
|
it("should passthrough a stream", async function() {
|
|
let stream;
|
|
let responses;
|
|
stream = await routes.createProxyStream(controller.app);
|
|
responses = await Promise.all([
|
|
(async function() {
|
|
await wait(100);
|
|
return await fetch(`${endpoint}/${stream.id}`, {
|
|
method: "PUT", body: "test content",
|
|
});
|
|
}()),
|
|
fetch(`${endpoint}/${stream.id}`),
|
|
]);
|
|
assert.equal(responses[0].status, 200);
|
|
assert.equal(responses[1].status, 200);
|
|
assert.equal(await responses[1].text(), "test content");
|
|
stream = await routes.createProxyStream(controller.app);
|
|
responses = await Promise.all([
|
|
fetch(`${endpoint}/${stream.id}`, {
|
|
method: "PUT", body: "test content",
|
|
}),
|
|
(async function() {
|
|
await wait(100);
|
|
return await fetch(`${endpoint}/${stream.id}`);
|
|
}()),
|
|
]);
|
|
assert.equal(responses[0].status, 200);
|
|
assert.equal(responses[1].status, 200);
|
|
assert.equal(await responses[1].text(), "test content");
|
|
});
|
|
});
|
|
describe("stripStaticPrefix()", function() {
|
|
it("should remove a leading static/", function() {
|
|
assert.equal(routes.stripStaticPrefix("static/main.abc.js"), "main.abc.js");
|
|
});
|
|
it("should leave other paths alone", function() {
|
|
assert.equal(routes.stripStaticPrefix("main.abc.js"), "main.abc.js");
|
|
assert.equal(routes.stripStaticPrefix("other/static/main.js"), "other/static/main.js");
|
|
});
|
|
});
|
|
describe("/api/plugins", function() {
|
|
let endpoint;
|
|
beforeEach(function() {
|
|
endpoint = `http://localhost:${port}/api/plugins`;
|
|
controller.plugins = new Map([["foo", {}]]);
|
|
controller.mockConfigEntries.set("foo.load_plugin", true);
|
|
});
|
|
it("should strip static/ from the plugin bundle path", async function() {
|
|
controller.pluginInfos = [{
|
|
name: "foo", version: "1.0.0", npmPackage: "foo",
|
|
manifest: { "foo.js": "static/foo.abc.js" },
|
|
}];
|
|
let response = await fetch(endpoint);
|
|
assert.equal(response.status, 200);
|
|
assert.deepEqual(await response.json(), [{
|
|
name: "foo", version: "1.0.0", enabled: true, loaded: true, npmPackage: "foo",
|
|
web: { main: "foo.abc.js" },
|
|
}]);
|
|
});
|
|
it("should report missing manifest and entrypoint", async function() {
|
|
controller.pluginInfos = [
|
|
{ name: "foo", version: "1.0.0", npmPackage: "foo" },
|
|
{ name: "bar", version: "1.0.0", npmPackage: "bar", manifest: {} },
|
|
{ name: "baz", version: "1.0.0", npmPackage: "baz", manifest: { "baz.js": "remoteEntry.js" } },
|
|
];
|
|
let response = await fetch(endpoint);
|
|
assert.equal(response.status, 200);
|
|
let data = await response.json();
|
|
assert.equal(data[0].web.error, "Missing dist/web/manifest.json");
|
|
assert.equal(data[1].web.error, "Missing bar.js entry in manifest.json");
|
|
assert.equal(data[2].web.error, "Incompatible old remoteEntry.js entrypoint.");
|
|
assert.equal(data[1].loaded, false);
|
|
assert.equal(data[1].enabled, false);
|
|
});
|
|
});
|
|
describe("/api/cluster-name", function() {
|
|
let endpoint;
|
|
beforeEach(function() {
|
|
endpoint = `http://localhost:${port}/api/cluster-name`;
|
|
});
|
|
it("should return the cluster name from config", async function() {
|
|
controller.mockConfigEntries.set("controller.name", "Test Cluster");
|
|
let response = await fetch(endpoint);
|
|
assert.equal(response.status, 200);
|
|
let data = await response.json();
|
|
assert.deepEqual(data, { name: "Test Cluster" });
|
|
});
|
|
});
|
|
describe("/api/upload-save", function() {
|
|
let endpoint, testToken;
|
|
beforeEach(function() {
|
|
endpoint = `http://localhost:${port}/api/upload-save`;
|
|
testToken = controller.users.signUserToken({ id: "test" });
|
|
});
|
|
it("should respond with 401 with invalid token", async function() {
|
|
let response;
|
|
response = await fetch(`${endpoint}?instance_id=123&filename=file.zip`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/zip" },
|
|
body: "totally a zip file",
|
|
});
|
|
assert.equal(response.status, 401);
|
|
response = await fetch(`${endpoint}?instance_id=123&filename=file.zip`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/zip",
|
|
"X-Access-Token": "invalid",
|
|
},
|
|
body: "totally a zip file",
|
|
});
|
|
assert.equal(response.status, 401);
|
|
response = await fetch(`${endpoint}?instance_id=123&filename=file.zip`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/zip",
|
|
"X-Access-Token": controller.users.signUserToken({ id: "invalid" }),
|
|
},
|
|
body: "totally a zip file",
|
|
});
|
|
assert.equal(response.status, 401);
|
|
const user = controller.users.getOrCreateUser("test");
|
|
user.tokenValidAfter = Math.floor((Date.now() + 60e3) / 1000);
|
|
user.saveRecord();
|
|
response = await fetch(`${endpoint}?instance_id=123&filename=file.zip`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/zip",
|
|
"X-Access-Token": testToken,
|
|
},
|
|
body: "totally a zip file",
|
|
});
|
|
assert.equal(response.status, 401);
|
|
});
|
|
|
|
it("should respond with 403 when user has insufficient permission", async function() {
|
|
const user = controller.users.getOrCreateUser("player");
|
|
let response = await fetch(`${endpoint}?instance_id=123&filename=file.zip`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/zip",
|
|
"X-Access-Token": controller.users.signUserToken(user),
|
|
},
|
|
body: "totally a zip file",
|
|
});
|
|
assert.equal(response.status, 403);
|
|
});
|
|
|
|
it("should respond with 415 if content type is missing or invalid", async function() {
|
|
let response;
|
|
response = await fetch(`${endpoint}?instance_id=123&filename=file.zip`, {
|
|
method: "POST",
|
|
headers: {
|
|
"X-Access-Token": testToken,
|
|
},
|
|
body: "totally a zip file",
|
|
});
|
|
assert.equal(response.status, 415);
|
|
assert.deepEqual(await response.json(), { request_errors: ["invalid Content-Type"] });
|
|
response = await fetch(`${endpoint}?instance_id=123&filename=file.zip`, {
|
|
method: "POST",
|
|
headers: {
|
|
"X-Access-Token": testToken,
|
|
"Content-Type": "invalid",
|
|
},
|
|
body: "totally a zip file",
|
|
});
|
|
assert.equal(response.status, 415);
|
|
assert.deepEqual(await response.json(), { request_errors: ["invalid Content-Type"] });
|
|
response = await fetch(`${endpoint}?instance_id=123&filename=file.zip`, {
|
|
method: "POST",
|
|
headers: {
|
|
"X-Access-Token": testToken,
|
|
"Content-Type": "text/plain",
|
|
},
|
|
body: "totally a zip file",
|
|
});
|
|
assert.equal(response.status, 415);
|
|
assert.deepEqual(await response.json(), { request_errors: ["invalid Content-Type"] });
|
|
});
|
|
|
|
it("should respond with 400 if invalid multipart request", async function() {
|
|
let response;
|
|
let form;
|
|
form = new FormData();
|
|
form.append("instance_id", "123");
|
|
form.append("file", "totally a zip file", { filename: "test.zip", contentType: "text/plain" });
|
|
response = await fetch(endpoint, {
|
|
method: "POST",
|
|
headers: {
|
|
"X-Access-Token": testToken,
|
|
...form.getHeaders(),
|
|
},
|
|
body: form.getBuffer(),
|
|
});
|
|
assert.equal(response.status, 400);
|
|
assert.deepEqual(await response.json(), { request_errors: ["invalid file Content-Type"] });
|
|
form = new FormData();
|
|
form.append("file", "totally a zip file", { filename: "test.zip", contentType: "application/zip" });
|
|
form.append("instance_id", "123");
|
|
response = await fetch(endpoint, {
|
|
method: "POST",
|
|
headers: {
|
|
"X-Access-Token": testToken,
|
|
...form.getHeaders(),
|
|
},
|
|
body: form.getBuffer(),
|
|
});
|
|
assert.equal(response.status, 400);
|
|
assert.deepEqual(
|
|
await response.json(), { request_errors: ["instance_id must come before files uploaded"] }
|
|
);
|
|
form = new FormData();
|
|
form.append("instance_id", "123");
|
|
form.append("file", "totally a zip file", { filename: "test.txt", contentType: "application/zip" });
|
|
response = await fetch(endpoint, {
|
|
method: "POST",
|
|
headers: {
|
|
"X-Access-Token": testToken,
|
|
...form.getHeaders(),
|
|
},
|
|
body: form.getBuffer(),
|
|
});
|
|
assert.equal(response.status, 400);
|
|
assert.deepEqual(
|
|
await response.json(), { request_errors: ["filename must end with .zip"] }
|
|
);
|
|
form = new FormData();
|
|
form.append("instance_id", "invalid");
|
|
form.append("file", "totally a zip file", { filename: "test.zip", contentType: "application/zip" });
|
|
response = await fetch(endpoint, {
|
|
method: "POST",
|
|
headers: {
|
|
"X-Access-Token": testToken,
|
|
...form.getHeaders(),
|
|
},
|
|
body: form.getBuffer(),
|
|
});
|
|
assert.equal(response.status, 400);
|
|
assert.deepEqual(
|
|
await response.json(), { request_errors: ["invalid instance_id"] }
|
|
);
|
|
});
|
|
|
|
it("should respond with 400 if invalid file request", async function() {
|
|
let response;
|
|
response = await fetch(`${endpoint}?instance_id=123`, {
|
|
method: "POST",
|
|
headers: {
|
|
"X-Access-Token": testToken,
|
|
"Content-Type": "application/zip",
|
|
},
|
|
body: "totally a zip file",
|
|
});
|
|
assert.equal(response.status, 400);
|
|
assert.deepEqual(await response.json(), { request_errors: ["Missing or invalid filename parameter"] });
|
|
response = await fetch(`${endpoint}?instance_id=123&filename=test.txt`, {
|
|
method: "POST",
|
|
headers: {
|
|
"X-Access-Token": testToken,
|
|
"Content-Type": "application/zip",
|
|
},
|
|
body: "totally a zip file",
|
|
});
|
|
assert.equal(response.status, 400);
|
|
assert.deepEqual(await response.json(), { request_errors: ["filename must end with .zip"] });
|
|
response = await fetch(`${endpoint}?filename=test.zip`, {
|
|
method: "POST",
|
|
headers: {
|
|
"X-Access-Token": testToken,
|
|
"Content-Type": "application/zip",
|
|
},
|
|
body: "totally a zip file",
|
|
});
|
|
assert.equal(response.status, 400);
|
|
assert.deepEqual(
|
|
await response.json(), { request_errors: ["Missing or invalid instance_id parameter"] }
|
|
);
|
|
});
|
|
|
|
it("should respond with 500 if the transfer failed", async function() {
|
|
controller.sendToHostByInstanceId = async (request) => {
|
|
throw new Error("Something went wrong");
|
|
};
|
|
let response;
|
|
let form;
|
|
form = new FormData();
|
|
form.append("instance_id", "123");
|
|
form.append("file", "totally a zip file", { filename: "test.zip", contentType: "application/zip" });
|
|
response = await fetch(endpoint, {
|
|
method: "POST",
|
|
headers: {
|
|
"X-Access-Token": testToken,
|
|
...form.getHeaders(),
|
|
},
|
|
body: form.getBuffer(),
|
|
});
|
|
assert.equal(response.status, 500);
|
|
assert.deepEqual(await response.json(), { errors: ["Something went wrong"], request_errors: [] });
|
|
response = await fetch(`${endpoint}?instance_id=123&filename=test.zip`, {
|
|
method: "POST",
|
|
headers: {
|
|
"X-Access-Token": testToken,
|
|
"Content-Type": "application/zip",
|
|
},
|
|
body: "totally a zip file",
|
|
});
|
|
assert.equal(response.status, 500);
|
|
assert.deepEqual(await response.json(), { errors: ["Something went wrong"], request_errors: [] });
|
|
controller.sendToHostByInstanceId = async (request) => {
|
|
await new Promise(() => {});
|
|
};
|
|
response = await fetch(`${endpoint}?instance_id=123&filename=test.zip`, {
|
|
method: "POST",
|
|
headers: {
|
|
"X-Access-Token": testToken,
|
|
"Content-Type": "application/zip",
|
|
},
|
|
body: "totally a zip file",
|
|
});
|
|
assert.equal(response.status, 500);
|
|
assert.deepEqual(
|
|
await response.json(), { errors: ["Timed out establishing stream to host"], request_errors: [] }
|
|
);
|
|
});
|
|
|
|
it("should complete a valid transfer", async function() {
|
|
controller.sendToHostByInstanceId = async (request) => {
|
|
const stream = await fetch(`http://localhost:${port}/api/stream/${request.streamId}`);
|
|
assert.equal(await stream.text(), "totally a zip file");
|
|
return request.name;
|
|
};
|
|
let response;
|
|
response = await fetch(`${endpoint}?instance_id=123&filename=test.zip`, {
|
|
method: "POST",
|
|
headers: {
|
|
"X-Access-Token": testToken,
|
|
"Content-Type": "application/zip",
|
|
},
|
|
body: "totally a zip file",
|
|
});
|
|
assert.deepEqual(await response.json(), { saves: ["test.zip"] });
|
|
assert.equal(response.status, 200);
|
|
});
|
|
it("should complete a valid transfer with non-standerd mime type", async function() {
|
|
controller.sendToHostByInstanceId = async (request) => {
|
|
const stream = await fetch(`http://localhost:${port}/api/stream/${request.streamId}`);
|
|
assert.equal(await stream.text(), "totally a zip file");
|
|
return request.name;
|
|
};
|
|
let response;
|
|
response = await fetch(`${endpoint}?instance_id=123&filename=test.zip`, {
|
|
method: "POST",
|
|
headers: {
|
|
"X-Access-Token": testToken,
|
|
"Content-Type": "application/x-zip-compressed",
|
|
},
|
|
body: "totally a zip file",
|
|
});
|
|
assert.deepEqual(await response.json(), { saves: ["test.zip"] });
|
|
assert.equal(response.status, 200);
|
|
});
|
|
});
|
|
});
|