"use strict"; const assert = require("assert").strict; const events = require("events"); const link = require("@clusterio/lib/link"); const errors = require("@clusterio/lib/errors"); const schema = require("@clusterio/lib/schema"); const mock = require("../../mock"); describe("lib/link/link", function() { describe("class Link", function() { let testConnector; let testLink; before(function() { testConnector = new mock.MockConnector(); testLink = new link.Link("source", "target", testConnector); testLink.setValidator("simple", schema.compile({ properties: { "data": { type: "object", properties: { "string": { type: "string" }, }, }, }, })); }); it("should emitt error processing message to connector", async function() { testLink.setHandler("throws", () => { throw new Error("Handler error"); }, () => true); let result = events.once(testConnector, "message"); testConnector.emit("message", { seq: 1, type: "throws", data: {}}); await assert.rejects(result, new Error("Handler error")); }); describe(".setValidator()", function() { it("should set the validator", function() { testLink.setValidator("test", () => true); assert(testLink._validators.has("test"), "validator was not added"); }); it("should throw on duplicated validator", function() { assert.throws( () => testLink.setValidator("test", () => true), new Error("test already has a validator") ); }); }); describe(".setHandler()", function() { it("should throw if validator is not passed", function() { assert.throws( () => testLink.setHandler("no_validator_test", (message) => {}), new Error("validator is required") ); }); it("should set the handler", function() { function handler(message) { } testLink.setHandler("handler_test", handler, (message) => true); assert.equal(testLink._handlers.get("handler_test"), handler); }); it("should throw on duplicated handler", function() { assert.throws( () => testLink.setHandler("handler_test", (message) => {}), new Error("handler_test already has a handler") ); }); }); describe(".processMessage()", function() { it("should throw on invalid message", function() { assert.throws( () => testLink.processMessage({ data: "invalid message" }), { message: "Malformed" } ); }); it("should throw on message without validator", function() { assert.throws( () => testLink.processMessage({ seq: 1, type: "no_validator", data: {} }), new errors.InvalidMessage("No validator for no_validator on source-target") ); }); it("should throw on message failing validation", function() { assert.throws( () => testLink.processMessage({ seq: 1, type: "simple", data: { string: 1 }}), { message: "Validation failed for simple" } ); }); it("should throw on unhandled message", function() { assert.throws( () => testLink.processMessage({ seq: 1, type: "simple", data: { string: "a" }}), new errors.InvalidMessage("Unhandled message simple") ); }); }); describe("._processHandler()", function() { it("should call the handler", function() { let handled = []; testLink.setHandler("handled", (message) => handled.push(message), (message) => true); testLink._processHandler({ seq: 1, type: "handled", data: { test: "foo" } }); assert.deepEqual(handled, [{ seq: 1, type: "handled", data: { test: "foo" } }]); }); it("should return true if handled", function() { let result = testLink._processHandler({ seq: 1, type: "handled", data: {} }); assert.equal(result, true); }); it("should return false if not handled", function() { let result = testLink._processHandler({ seq: 1, type: "not_handled", data: {} }); assert.equal(result, false); }); }); describe("._processWaiters()", function() { it("should call waiters", async function() { let waiter = new Promise(resolve => { testLink._waiters.set("test", [{ resolve, data: {} }]); }); assert( testLink._processWaiters({ type: "test", seq: 4, data: {} }), "Waiter was not processed" ); assert.deepEqual(await waiter, { type: "test", seq: 4, data: {} }); }); it("should filter on data", async function() { let waiter = new Promise(resolve => { testLink._waiters.set("test", [{ resolve, data: { a: 1, b: 2 } }]); }); assert( !testLink._processWaiters({ type: "test", seq: 4, data: {} }), "Incorrectly matched empty message" ); assert( !testLink._processWaiters({ type: "test", seq: 4, data: { a: 1, b: 0 } }), "Incorrectly matched message with wrong data" ); assert( testLink._processWaiters({ type: "test", seq: 4, data: { a: 1, b: 2, c: 3 } }), "Did not match correct message" ); assert.deepEqual(await waiter, { type: "test", seq: 4, data: { a: 1, b: 2, c: 3 } }); }); }); describe(".waitFor()", function() { before(function() { testLink.setValidator("waiter", (message) => true); }); it("should throw on missing validator", async function() { await assert.rejects( testLink.waitFor("no_validator", {}), new Error("No validator for no_validator on source-target") ); }); it("should wait for a given message", async function() { let result = testLink.waitFor("waiter", {}); assert( testLink._processWaiters({ type: "waiter", seq: 5, data: {} }), "Did not proccess waiter" ); assert.deepEqual(await result, { type: "waiter", seq: 5, data: {} }); }); it("should handle multiple waiters", async function() { let result1 = testLink.waitFor("waiter", {}); let result2 = testLink.waitFor("waiter", {}); assert( testLink._processWaiters({ type: "waiter", seq: 5, data: {} }), "Did not proccess waiter" ); assert.deepEqual(await result1, { type: "waiter", seq: 5, data: {} }); assert.deepEqual(await result2, { type: "waiter", seq: 5, data: {} }); }); }); }); });