mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-12 15: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.
681 lines
25 KiB
JavaScript
681 lines
25 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import events from "node:events";
|
|
|
|
import * as lib from "@clusterio/lib";
|
|
import * as mock from "../../mock.js";
|
|
|
|
const addr = lib.Address.fromShorthand;
|
|
|
|
describe("lib/link/link", function() {
|
|
function throwSimple(message) {
|
|
let err = new Error(message);
|
|
err.stack = message;
|
|
throw err;
|
|
}
|
|
|
|
describe("class Link", function() {
|
|
let testConnector;
|
|
/** @type {lib.Link} */
|
|
let testLink;
|
|
let src = addr({ controlId: 1 });
|
|
let dst = addr("controller");
|
|
|
|
class SimpleRequest {
|
|
static type = "request";
|
|
static src = ["controller", "control"];
|
|
static dst = ["controller", "control"];
|
|
static permission = null;
|
|
}
|
|
lib.Link.register(SimpleRequest);
|
|
class NumberRequest {
|
|
static type = "request";
|
|
static src = ["controller", "control"];
|
|
static dst = ["controller", "control"];
|
|
static permission = null;
|
|
constructor(value) { this.value = value; }
|
|
static jsonSchema = { type: "number" };
|
|
toJSON() { return this.value; }
|
|
static fromJSON(json) { return new this(json); }
|
|
}
|
|
NumberRequest.Response = class {
|
|
constructor(value) { this.value = value; }
|
|
static jsonSchema = { type: "number" };
|
|
toJSON() { return this.value; }
|
|
static fromJSON(json) { return new this(json); }
|
|
};
|
|
lib.Link.register(NumberRequest);
|
|
class SimpleEvent {
|
|
static type = "event";
|
|
static src = ["controller", "control"];
|
|
static dst = ["controller", "control"];
|
|
static permission = null;
|
|
}
|
|
lib.Link.register(SimpleEvent);
|
|
class NumberEvent {
|
|
static type = "event";
|
|
static src = ["controller", "control"];
|
|
static dst = ["controller", "control"];
|
|
static permission = null;
|
|
constructor(value) { this.value = value; }
|
|
static jsonSchema = { type: "number" };
|
|
toJSON() { return this.value; }
|
|
static fromJSON(json) { return new this(json); }
|
|
}
|
|
lib.Link.register(NumberEvent);
|
|
|
|
beforeEach(function() {
|
|
testConnector = new mock.MockConnector(src, dst);
|
|
testLink = new lib.Link(testConnector);
|
|
});
|
|
|
|
it("should handle unknown message", async function() {
|
|
testConnector.emit("message", { type: "unknown" });
|
|
});
|
|
|
|
describe("Request handling", function() {
|
|
it("should give an error response back on unrecognized request", function() {
|
|
testConnector.emit("message", new lib.MessageRequest(1, dst, src, "UnhandledRequest"));
|
|
assert.deepEqual(testConnector.sentMessages, [
|
|
new lib.MessageResponseError(
|
|
testConnector._seq - 1,
|
|
src,
|
|
dst,
|
|
new lib.ResponseError("Unrecognized request UnhandledRequest", "InvalidMessage")
|
|
),
|
|
]);
|
|
});
|
|
it("should give an error response back on unhandled request", function() {
|
|
class UnhandledRequest {
|
|
static type = "request";
|
|
static src = "controller";
|
|
static dst = "control";
|
|
}
|
|
lib.Link.register(UnhandledRequest);
|
|
testConnector.emit("message", new lib.MessageRequest(1, dst, src, "UnhandledRequest"));
|
|
assert.deepEqual(testConnector.sentMessages, [
|
|
new lib.MessageResponseError(
|
|
testConnector._seq - 1,
|
|
src,
|
|
dst,
|
|
new lib.ResponseError("No handler for UnhandledRequest")
|
|
),
|
|
]);
|
|
});
|
|
it("should give an error response back on invalid src", function() {
|
|
class InvalidSrcRequest {
|
|
static type = "request";
|
|
static src = "host";
|
|
static dst = "control";
|
|
}
|
|
lib.Link.register(InvalidSrcRequest);
|
|
testConnector.emit("message", new lib.MessageRequest(1, dst, src, "InvalidSrcRequest"));
|
|
assert.deepEqual(testConnector.sentMessages, [
|
|
new lib.MessageResponseError(
|
|
testConnector._seq - 1,
|
|
src,
|
|
dst,
|
|
new lib.ResponseError(
|
|
"Source [Address controller:0] is not allowed for InvalidSrcRequest",
|
|
"InvalidMessage"
|
|
)
|
|
),
|
|
]);
|
|
});
|
|
it("should give an error response back on invalid dst", function() {
|
|
class InvalidDstRequest {
|
|
static type = "request";
|
|
static src = "controller";
|
|
static dst = "host";
|
|
}
|
|
lib.Link.register(InvalidDstRequest);
|
|
testConnector.emit("message", new lib.MessageRequest(1, dst, src, "InvalidDstRequest"));
|
|
assert.deepEqual(testConnector.sentMessages, [
|
|
new lib.MessageResponseError(
|
|
testConnector._seq - 1,
|
|
src,
|
|
dst,
|
|
new lib.ResponseError(
|
|
"Destination [Address control:1] is not allowed for InvalidDstRequest",
|
|
"InvalidMessage",
|
|
)
|
|
),
|
|
]);
|
|
});
|
|
it("should give an error response back on broadcast src", function() {
|
|
testConnector.emit("message", new lib.MessageRequest(1, addr("allControls"), src, "SimpleRequest"));
|
|
assert.deepEqual(testConnector.sentMessages, [
|
|
new lib.MessageResponseError(
|
|
testConnector._seq - 1,
|
|
src,
|
|
addr("allControls"),
|
|
new lib.ResponseError("Message src may not be broadcast", "InvalidMessage")
|
|
),
|
|
]);
|
|
});
|
|
it("should give an error response back on requst with broadcast dst", function() {
|
|
testConnector.emit("message", new lib.MessageRequest(1, dst, addr("allControls"), "SimpleRequest"));
|
|
assert.deepEqual(testConnector.sentMessages, [
|
|
new lib.MessageResponseError(
|
|
testConnector._seq - 1,
|
|
src,
|
|
dst,
|
|
new lib.ResponseError(
|
|
"Destination [Address 4:control] is not allowed for SimpleRequest",
|
|
"InvalidMessage",
|
|
)
|
|
),
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe("Event handling", function() {
|
|
it("should ignore unrecognized event", function() {
|
|
testConnector.emit("message", new lib.MessageEvent(1, dst, src, "UnhandledEvent"));
|
|
assert.deepEqual(testConnector.sentMessages, []);
|
|
});
|
|
it("should ignore unhandled event", function() {
|
|
class UnhandledEvent {
|
|
static type = "event";
|
|
static src = "controller";
|
|
static dst = "control";
|
|
}
|
|
lib.Link.register(UnhandledEvent);
|
|
testConnector.emit("message", new lib.MessageEvent(1, dst, src, "UnhandledEvent"));
|
|
assert.deepEqual(testConnector.sentMessages, []);
|
|
});
|
|
it("should ignore invalid src", function() {
|
|
class InvalidSrcEvent {
|
|
static type = "event";
|
|
static src = "host";
|
|
static dst = "control";
|
|
}
|
|
let handled = false;
|
|
lib.Link.register(InvalidSrcEvent);
|
|
testLink.snoopEvent(InvalidSrcEvent, async () => { handled = true; });
|
|
testConnector.emit("message", new lib.MessageEvent(1, dst, src, "InvalidSrcEvent"));
|
|
assert(!handled, "event was not ignored");
|
|
assert.deepEqual(testConnector.sentMessages, []);
|
|
});
|
|
it("should ignore invalid dst", function() {
|
|
class InvalidDstEvent {
|
|
static type = "event";
|
|
static src = "controller";
|
|
static dst = "host";
|
|
}
|
|
lib.Link.register(InvalidDstEvent);
|
|
let handled = false;
|
|
testLink.snoopEvent(InvalidDstEvent, async () => { handled = true; });
|
|
testConnector.emit("message", new lib.MessageEvent(1, dst, src, "InvalidDstEvent"));
|
|
testConnector.emit("message", new lib.MessageEvent(1, dst, addr("allControls"), "InvalidDstEvent"));
|
|
assert(!handled, "event was not ignored");
|
|
assert.deepEqual(testConnector.sentMessages, []);
|
|
});
|
|
it("should ignore broadcast src", function() {
|
|
let handled = false;
|
|
testLink.snoopEvent(SimpleEvent, async () => { handled = true; });
|
|
testConnector.emit("message", new lib.MessageEvent(1, addr("allControls"), src, "SimpleEvent"));
|
|
assert(!handled, "event was not ignored");
|
|
assert.deepEqual(testConnector.sentMessages, []);
|
|
});
|
|
it("should handle event with broadcast dst", function() {
|
|
let handled = false;
|
|
testLink.snoopEvent(SimpleEvent, async () => { handled = true; });
|
|
testConnector.emit("message", new lib.MessageEvent(1, dst, addr("allControls"), "SimpleEvent"));
|
|
assert(handled, "event was ignored");
|
|
assert.deepEqual(testConnector.sentMessages, []);
|
|
});
|
|
});
|
|
|
|
it("should send ready on connector prepareDisconnect", async function() {
|
|
let message = events.once(testConnector, "send");
|
|
message.catch(() => {});
|
|
testConnector.emit("disconnectPrepare");
|
|
assert.deepEqual(await message, [new lib.MessageDisconnect("ready")]);
|
|
});
|
|
it("should send ready on connector prepareDisconnect if an error occurs", async function() {
|
|
testLink.prepareDisconnect = async () => { throwSimple("Error occured"); };
|
|
let message = events.once(testConnector, "send");
|
|
message.catch(() => {});
|
|
testConnector.emit("disconnectPrepare");
|
|
assert.deepEqual(await message, [new lib.MessageDisconnect("ready")]);
|
|
});
|
|
|
|
it("should reject pending requests on close", async function() {
|
|
let pending = testLink.send(new SimpleRequest());
|
|
pending.catch(() => {});
|
|
testConnector.emit("close");
|
|
await assert.rejects(pending, { message: "Session Closed" });
|
|
});
|
|
it("should reject pending requests on invalidate", async function() {
|
|
let pending = testLink.send(new SimpleRequest());
|
|
pending.catch(() => {});
|
|
testConnector.emit("invalidate");
|
|
await assert.rejects(pending, { message: "Session Lost" });
|
|
});
|
|
|
|
describe(".send()", function() {
|
|
it("should send request to the other side of the link", async function() {
|
|
let message = events.once(testConnector, "send");
|
|
message.catch(() => {});
|
|
testLink.send(new SimpleRequest());
|
|
let srcReq = new lib.Address(lib.Address.control, 1, 1);
|
|
assert.deepEqual(
|
|
await message,
|
|
[new lib.MessageRequest(1, srcReq, dst, "SimpleRequest", undefined)]
|
|
);
|
|
});
|
|
it("should send request with data to the other side of the link", async function() {
|
|
let message = events.once(testConnector, "send");
|
|
message.catch(() => {});
|
|
testLink.send(new NumberRequest(22));
|
|
let srcReq = new lib.Address(lib.Address.control, 1, 1);
|
|
assert.deepEqual(
|
|
await message,
|
|
[new lib.MessageRequest(1, srcReq, dst, "NumberRequest", new NumberRequest(22))]
|
|
);
|
|
});
|
|
it("should send request and resolve when response is received", async function() {
|
|
let message = events.once(testConnector, "send");
|
|
message.catch(() => {});
|
|
let request = testLink.send(new SimpleRequest());
|
|
let srcReq = new lib.Address(lib.Address.control, 1, 1);
|
|
testConnector.emit("message", new lib.MessageResponse(1, dst, srcReq));
|
|
await request;
|
|
assert.deepEqual(testLink._pendingRequests, new Map());
|
|
});
|
|
it("should send request and reject with error when error response is received", async function() {
|
|
let message = events.once(testConnector, "send");
|
|
message.catch(() => {});
|
|
let request = testLink.send(new SimpleRequest());
|
|
let srcReq = new lib.Address(lib.Address.control, 1, 1);
|
|
testConnector.emit(
|
|
"message",
|
|
new lib.MessageResponseError(
|
|
1, dst, srcReq, new lib.ResponseError("Error")
|
|
)
|
|
);
|
|
await assert.rejects(
|
|
request,
|
|
{ message: "Error" }
|
|
);
|
|
assert.deepEqual(testLink._pendingRequests, new Map());
|
|
});
|
|
it("should send request and resolve with data when response is received", async function() {
|
|
let message = events.once(testConnector, "send");
|
|
message.catch(() => {});
|
|
let request = testLink.send(new NumberRequest(22));
|
|
let srcReq = new lib.Address(lib.Address.control, 1, 1);
|
|
testConnector.emit("message", new lib.MessageResponse(1, dst, srcReq, 44));
|
|
assert.deepEqual(await request, new NumberRequest.Response(44));
|
|
assert.deepEqual(testLink._pendingRequests, new Map());
|
|
});
|
|
it("should send event to the other side of the link", async function() {
|
|
let message = events.once(testConnector, "send");
|
|
message.catch(() => {});
|
|
testLink.send(new SimpleEvent());
|
|
assert.deepEqual(
|
|
await message,
|
|
[new lib.MessageEvent(1, src, dst, "SimpleEvent", undefined)]
|
|
);
|
|
});
|
|
it("should send event with data to the other side of the link", async function() {
|
|
let message = events.once(testConnector, "send");
|
|
message.catch(() => {});
|
|
testLink.send(new NumberEvent(22));
|
|
assert.deepEqual(
|
|
await message,
|
|
[new lib.MessageEvent(1, src, dst, "NumberEvent", new NumberEvent(22))]
|
|
);
|
|
});
|
|
});
|
|
describe(".handle()", function() {
|
|
it("should register a request handler", function() {
|
|
let handled = false;
|
|
testLink.handle(SimpleRequest, async () => { handled = true; });
|
|
assert(testLink._requestHandlers.has(SimpleRequest), "request handler was not registered");
|
|
testConnector.emit("message", new lib.MessageRequest(1, dst, src, "SimpleRequest"));
|
|
assert(handled, "request was not handled");
|
|
});
|
|
it("should send response error from request handler throwing", async function() {
|
|
let message = events.once(testConnector, "send");
|
|
message.catch(() => {});
|
|
testLink.handle(SimpleRequest, async () => { throwSimple("Error"); });
|
|
testConnector.emit("message", new lib.MessageRequest(1, dst, src, "SimpleRequest"));
|
|
assert.deepEqual(
|
|
await message,
|
|
[new lib.MessageResponseError(
|
|
1, src, dst, new lib.ResponseError("Error", undefined, "Error")
|
|
)]
|
|
);
|
|
});
|
|
it("should send response error on request validation failing", async function() {
|
|
let message = events.once(testConnector, "send");
|
|
message.catch(() => {});
|
|
testLink.handle(NumberRequest, async () => 1);
|
|
testConnector.emit("message", new lib.MessageRequest(1, dst, src, "NumberRequest", "not a number"));
|
|
let response = (await message)[0];
|
|
assert.deepEqual(
|
|
response,
|
|
new lib.MessageResponseError(
|
|
1, src, dst,
|
|
new lib.ResponseError(
|
|
"Request NumberRequest failed validation",
|
|
response.data.code,
|
|
response.data.stack,
|
|
)
|
|
)
|
|
);
|
|
});
|
|
it("should send response error on response validation failing", async function() {
|
|
let message = events.once(testConnector, "send");
|
|
message.catch(() => {});
|
|
testLink.handle(NumberRequest, async () => "not a number");
|
|
testConnector.emit("message", new lib.MessageRequest(1, dst, src, "NumberRequest", 1));
|
|
let response = (await message)[0];
|
|
assert.deepEqual(
|
|
response,
|
|
new lib.MessageResponseError(
|
|
1, src, dst,
|
|
new lib.ResponseError(
|
|
"Response for request NumberRequest failed validation",
|
|
response.data.code,
|
|
response.data.stack,
|
|
)
|
|
)
|
|
);
|
|
});
|
|
it("should send value returned from request handler", async function() {
|
|
let message = events.once(testConnector, "send");
|
|
message.catch(() => {});
|
|
testLink.handle(NumberRequest, async (request) => request.value + 4);
|
|
testConnector.emit("message", new lib.MessageRequest(1, dst, src, "NumberRequest", 1));
|
|
let response = (await message)[0];
|
|
assert.deepEqual(
|
|
response,
|
|
new lib.MessageResponse(1, src, dst, 5)
|
|
);
|
|
});
|
|
it("should register an event handler", function() {
|
|
let handled = false;
|
|
testLink.handle(SimpleEvent, async () => { handled = true; });
|
|
assert(testLink._eventHandlers.has(SimpleEvent), "event handler was not registered");
|
|
testConnector.emit("message", new lib.MessageEvent(1, dst, src, "SimpleEvent"));
|
|
assert(handled, "event was not handled");
|
|
});
|
|
it("should pass value to event handler", async function() {
|
|
let value;
|
|
testLink.handle(NumberEvent, async (event) => { value = event.value; });
|
|
testConnector.emit("message", new lib.MessageEvent(1, dst, src, "NumberEvent", 9));
|
|
assert.deepEqual(value, 9);
|
|
});
|
|
it("should log errors from event handler", function() {
|
|
testLink.handle(SimpleEvent, async () => { throwSimple("Error"); });
|
|
testConnector.emit("message", new lib.MessageEvent(1, dst, src, "SimpleEvent"));
|
|
});
|
|
it("should log validation errors from event handler", async function() {
|
|
let logged = [];
|
|
const originalError = lib.logger.error;
|
|
lib.logger.error = msg => { logged.push(msg); };
|
|
try {
|
|
testLink.handle(SimpleEvent, async () => {
|
|
testLink.sendEvent(new NumberEvent("not a number"), dst);
|
|
});
|
|
testConnector.emit("message", new lib.MessageEvent(1, dst, src, "SimpleEvent"));
|
|
await new Promise(resolve => setImmediate(resolve));
|
|
} finally {
|
|
lib.logger.error = originalError;
|
|
}
|
|
assert.equal(logged.length, 1);
|
|
assert(logged[0].startsWith(
|
|
"Unexpected error handling SimpleEvent:\nError: Event NumberEvent failed validation\n"
|
|
));
|
|
assert(logged[0].includes('"message": "must be number"'));
|
|
});
|
|
it("should fall back to message when event handler error has no stack", async function() {
|
|
let logged = [];
|
|
const originalError = lib.logger.error;
|
|
lib.logger.error = msg => { logged.push(msg); };
|
|
try {
|
|
testLink.handle(SimpleEvent, async () => {
|
|
let err = new Error("no stack");
|
|
err.stack = undefined;
|
|
throw err;
|
|
});
|
|
testConnector.emit("message", new lib.MessageEvent(1, dst, src, "SimpleEvent"));
|
|
await new Promise(resolve => setImmediate(resolve));
|
|
} finally {
|
|
lib.logger.error = originalError;
|
|
}
|
|
assert.deepEqual(logged, ["Unexpected error handling SimpleEvent:\nno stack"]);
|
|
});
|
|
it("should throw on unknown type", function() {
|
|
assert.throws(
|
|
() => testLink.handle({ name: "Bad", type: "bad" }),
|
|
{ message: "Class Bad has unrecognized type bad" }
|
|
);
|
|
});
|
|
it("should throw on double registration", function() {
|
|
testLink.handle(SimpleRequest);
|
|
assert.throws(
|
|
() => testLink.handle(SimpleRequest),
|
|
new Error("Request SimpleRequest is already registered")
|
|
);
|
|
testLink.handle(SimpleEvent);
|
|
assert.throws(
|
|
() => testLink.handle(SimpleEvent),
|
|
new Error("Event SimpleEvent is already registered")
|
|
);
|
|
});
|
|
});
|
|
describe("static .register()", function() {
|
|
it("should throw if Request has only one of jsonSchema and fromJSON", function() {
|
|
class BadRequest1 {
|
|
static type = "request";
|
|
static src = "controller";
|
|
static dst = "control";
|
|
static jsonSchema = {};
|
|
}
|
|
assert.throws(
|
|
() => lib.Link.register(BadRequest1),
|
|
new Error("Request BadRequest1 has static jsonSchema but is missing static fromJSON")
|
|
);
|
|
class BadRequest2 {
|
|
static type = "request";
|
|
static src = "controller";
|
|
static dst = "control";
|
|
static fromJSON() {};
|
|
}
|
|
assert.throws(
|
|
() => lib.Link.register(BadRequest2),
|
|
new Error("Request BadRequest2 has static fromJSON but is missing static jsonSchema")
|
|
);
|
|
});
|
|
it("should throw if Event has only one of jsonSchema and fromJSON", function() {
|
|
class BadEvent1 {
|
|
static type = "event";
|
|
static src = "controller";
|
|
static dst = "control";
|
|
static jsonSchema = {};
|
|
}
|
|
assert.throws(
|
|
() => lib.Link.register(BadEvent1),
|
|
new Error("Event BadEvent1 has static jsonSchema but is missing static fromJSON")
|
|
);
|
|
class BadEvent2 {
|
|
static type = "event";
|
|
static src = "controller";
|
|
static dst = "control";
|
|
static fromJSON() {};
|
|
}
|
|
assert.throws(
|
|
() => lib.Link.register(BadEvent2),
|
|
new Error("Event BadEvent2 has static fromJSON but is missing static jsonSchema")
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("._processMessage()", function() {
|
|
it("should throw on unhandled type", function() {
|
|
assert.throws(
|
|
() => testLink._processMessage({ type: "invalid" }),
|
|
{ message: "Unhandled message type invalid" }
|
|
);
|
|
});
|
|
|
|
it("should throw on Event failing validation", function() {
|
|
class StringEvent {
|
|
static type = "event";
|
|
static src = "controller";
|
|
static dst = "control";
|
|
constructor(value) { this.value = value; }
|
|
static jsonSchema = { type: "string" };
|
|
static fromJSON(json) { return new this(json); };
|
|
}
|
|
lib.Link.register(StringEvent);
|
|
testLink.handle(StringEvent, () => {});
|
|
assert.throws(
|
|
() => testLink._processMessage(new lib.MessageEvent(1, dst, src, "StringEvent", 99)),
|
|
{ message: "Event StringEvent failed validation" }
|
|
);
|
|
});
|
|
|
|
describe("Broadcast handling", function() {
|
|
// A broadcast is addressed to every link of the type it targets,
|
|
// so a link it reaches has to pass it on as well as handle it,
|
|
// see #575.
|
|
function mockRouter(record) {
|
|
return { forwardMessage: () => { record.routed = true; return true; } };
|
|
}
|
|
|
|
it("should route a broadcast addressed to this link as well as handle it", function() {
|
|
const record = {};
|
|
let handled = false;
|
|
testLink.router = mockRouter(record);
|
|
testLink.handle(SimpleEvent, async () => { handled = true; });
|
|
testConnector.emit("message", new lib.MessageEvent(1, dst, addr("allControls"), "SimpleEvent"));
|
|
assert(record.routed, "broadcast was not routed onwards");
|
|
assert(handled, "broadcast was not handled locally");
|
|
});
|
|
|
|
it("should handle a broadcast on a link which does not route", function() {
|
|
// Instances have no router, so routing unconditionally would
|
|
// throw here and the event would never be handled.
|
|
let handled = false;
|
|
testLink.handle(SimpleEvent, async () => { handled = true; });
|
|
testConnector.emit("message", new lib.MessageEvent(1, dst, addr("allControls"), "SimpleEvent"));
|
|
assert(handled, "broadcast was not handled locally");
|
|
});
|
|
|
|
it("should not route a non broadcast addressed to this link", function() {
|
|
const record = {};
|
|
testLink.router = mockRouter(record);
|
|
testLink.handle(SimpleEvent, async () => {});
|
|
testConnector.emit("message", new lib.MessageEvent(1, dst, src, "SimpleEvent"));
|
|
assert(!record.routed, "message addressed to this link was routed onwards");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe(".forwardRequest()", function() {
|
|
// A request is recorded so that its response can be sent back the
|
|
// way it came. If it never left there is nothing to send back, and
|
|
// leaving it recorded has it answered twice, see #650.
|
|
function forwardingSetup() {
|
|
const originConnector = new mock.MockConnector(dst, src);
|
|
const origin = new lib.Link(originConnector);
|
|
const nextHopConnector = new mock.MockConnector(src, dst);
|
|
const nextHop = new lib.Link(nextHopConnector);
|
|
const message = new lib.MessageRequest(1, src, dst, "SimpleRequest");
|
|
return { origin, originConnector, nextHop, nextHopConnector, message };
|
|
}
|
|
|
|
it("should record a request it forwarded", function() {
|
|
const { origin, nextHop, nextHopConnector, message } = forwardingSetup();
|
|
nextHop.forwardRequest(message, origin);
|
|
assert.equal(nextHop.pendingRequestCount, 1);
|
|
assert.equal(nextHopConnector.sentMessages.length, 1);
|
|
});
|
|
|
|
it("should not record a request it failed to forward", function() {
|
|
const { origin, nextHop, nextHopConnector, message } = forwardingSetup();
|
|
nextHopConnector.send = () => throwSimple("Session Closed");
|
|
|
|
assert.throws(() => nextHop.forwardRequest(message, origin), { message: "Session Closed" });
|
|
assert.equal(nextHop.pendingRequestCount, 0, "failed forward was left pending");
|
|
});
|
|
|
|
it("should not answer a request it failed to forward a second time", function() {
|
|
const { origin, originConnector, nextHop, nextHopConnector, message } = forwardingSetup();
|
|
nextHopConnector.send = () => throwSimple("Session Closed");
|
|
|
|
// Routers answer the origin themselves when forwarding throws.
|
|
assert.throws(() => nextHop.forwardRequest(message, origin), { message: "Session Closed" });
|
|
originConnector.sendResponseError(new lib.ResponseError("Session Closed"), message.src);
|
|
const answers = originConnector.sentMessages.length;
|
|
|
|
nextHop._clearPendingRequests(new lib.SessionLost("Session Lost"));
|
|
assert.equal(originConnector.sentMessages.length, answers, "request was answered twice");
|
|
});
|
|
});
|
|
|
|
describe(".snoopEvent()", function() {
|
|
it("should snoop an event", function() {
|
|
let handled = false;
|
|
testLink.snoopEvent(SimpleEvent, async () => { handled = true; });
|
|
assert(testLink._eventSnoopers.has(SimpleEvent), "event was not snooped");
|
|
testConnector.emit("message", new lib.MessageEvent(1, dst, src, "SimpleEvent"));
|
|
assert(handled, "event was not handled");
|
|
});
|
|
it("should log errors from snoop handler", function() {
|
|
testLink.snoopEvent(SimpleEvent, async () => { throwSimple("Error"); });
|
|
testConnector.emit("message", new lib.MessageEvent(1, dst, src, "SimpleEvent"));
|
|
});
|
|
it("should log validation errors from snoop handler", async function() {
|
|
let logged = [];
|
|
const originalError = lib.logger.error;
|
|
lib.logger.error = msg => { logged.push(msg); };
|
|
try {
|
|
testLink.handle(SimpleEvent, async () => {});
|
|
testLink.snoopEvent(SimpleEvent, async () => {
|
|
testLink.sendEvent(new NumberEvent("not a number"), dst);
|
|
});
|
|
testConnector.emit("message", new lib.MessageEvent(1, dst, src, "SimpleEvent"));
|
|
await new Promise(resolve => setImmediate(resolve));
|
|
} finally {
|
|
lib.logger.error = originalError;
|
|
}
|
|
assert.equal(logged.length, 1);
|
|
assert(logged[0].startsWith(
|
|
"Unexpected error snooping SimpleEvent:\nError: Event NumberEvent failed validation\n"
|
|
));
|
|
assert(logged[0].includes('"message": "must be number"'));
|
|
});
|
|
it("should fall back to message when snoop handler error has no stack", async function() {
|
|
let logged = [];
|
|
const originalError = lib.logger.error;
|
|
lib.logger.error = msg => { logged.push(msg); };
|
|
try {
|
|
testLink.handle(SimpleEvent, async () => {});
|
|
testLink.snoopEvent(SimpleEvent, async () => {
|
|
let err = new Error("no stack");
|
|
err.stack = undefined;
|
|
throw err;
|
|
});
|
|
testConnector.emit("message", new lib.MessageEvent(1, dst, src, "SimpleEvent"));
|
|
await new Promise(resolve => setImmediate(resolve));
|
|
} finally {
|
|
lib.logger.error = originalError;
|
|
}
|
|
assert.deepEqual(logged, ["Unexpected error snooping SimpleEvent:\nno stack"]);
|
|
});
|
|
it("should throw on double registration", function() {
|
|
testLink.snoopEvent(SimpleEvent);
|
|
assert.throws(
|
|
() => testLink.snoopEvent(SimpleEvent),
|
|
new Error("Event SimpleEvent is already snooped")
|
|
);
|
|
});
|
|
});
|
|
});
|
|
});
|