// A lamp tested without a broker and without Alexa. The bridge publishes into a MemoryPublisher, and // bridge.receive() gives it a message as the broker would. connect() is never called. // // node examples/testing/lamp.test.js // // "alex2node" is the installed package; inside this checkout it resolves to the built dist/. const { test } = require("node:test"); const assert = require("node:assert/strict"); const { Alex2MQTT, MemoryPublisher, PowerController } = require("alex2node"); // The device under test. In your program this is the module that declares your devices on a bridge. function declareLamp(bridge, lamp) { const device = bridge.addDevice({ endpointId: "desk-lamp", name: "Desk Lamp", categories: ["LIGHT"] }); const power = device.add(PowerController); device.state((s) => s.set(power, "powerState", lamp.on ? "ON" : "OFF").health("OK")); power.on("TurnOn", (ctx) => { if (lamp.broken) throw new Error("the lamp does not answer"); lamp.on = true; return ctx.respond(); }); return device; } // A bridge that publishes into sent, and what Alex2MQTT would send it function setup(lamp) { const sent = new MemoryPublisher(); const bridge = new Alex2MQTT("user", "password", "root", false, { publisher: sent }); declareLamp(bridge, lamp); let count = 0; const directive = (namespace, name, payload = {}) => { count += 1; return JSON.stringify({ header: { namespace, name, payloadVersion: "3", messageId: `message-${count}`, correlationToken: `token-${count}` }, endpoint: { endpointId: "desk-lamp" }, payload, }); }; return { sent, bridge, directive }; } test("discovery announces the lamp", async () => { const { sent, bridge } = setup({ on: false }); await bridge.receive("root/discover", "{}"); const [{ topic, message }] = sent.published; assert.equal(topic, "root/discover_r"); assert.equal(message[0].friendlyName, "Desk Lamp"); assert.deepEqual(message[0].capabilities.map((capability) => capability.interface), ["Alexa.PowerController", "Alexa.EndpointHealth", "Alexa"]); }); test("TurnOn switches the lamp and answers with its state", async () => { const lamp = { on: false }; const { sent, bridge, directive } = setup(lamp); await bridge.receive("root/desk-lamp/alexaDirective", directive("Alexa.PowerController", "TurnOn")); assert.equal(lamp.on, true); const [{ topic, message }] = sent.published; assert.equal(topic, "root/desk-lamp/alexaResponce"); assert.equal(message.event.header.name, "Response"); assert.equal(message.event.header.correlationToken, "token-1"); const power = message.context.properties.find((property) => property.name === "powerState"); assert.equal(power.value, "ON"); }); test("a handler that throws is answered with INTERNAL_ERROR", async () => { const { sent, bridge, directive } = setup({ on: false, broken: true }); const errors = []; bridge.on("error", (err) => errors.push(err)); await bridge.receive("root/desk-lamp/alexaDirective", directive("Alexa.PowerController", "TurnOn")); const [{ message }] = sent.published; assert.equal(message.event.header.name, "ErrorResponse"); assert.equal(message.event.payload.type, "INTERNAL_ERROR"); assert.equal(errors.length, 1); }); test("a directive without a handler is answered with INVALID_DIRECTIVE", async () => { const { sent, bridge, directive } = setup({ on: true }); await bridge.receive("root/desk-lamp/alexaDirective", directive("Alexa.PowerController", "TurnOff")); assert.equal(sent.published[0].message.event.payload.type, "INVALID_DIRECTIVE"); });