readme: quick start, generated capability table, recipes, bridge contract, migration
The readme is rewritten for 2.0: what the library needs, install, the lamp as quick start, concepts, the table of interfaces, recipes, the bridge contract, errors, testing without Alexa, migration from 1.x and limits. scripts/capability-table.js (npm run docs) writes the table from the registry and the text of three example files into their code blocks; "Through Alexa" names only the runs of 2026-09-28, made with 1.5.2. test/readme.test.js fails on a stale table or file, runs examples/testing/lamp.test.js (MemoryPublisher, no broker) and compiles the other code blocks. package.json: description, keywords, CHANGELOG.md in files. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
a59de926a2
commit
785944b2da
6 changed files with 848 additions and 174 deletions
|
|
@ -31,6 +31,15 @@ to. Then discover the devices in the Alexa app.
|
|||
`sensor.js` and `doorbell.js` wait for the Enter key: it opens and closes the door, and rings the bell.
|
||||
`lock.js` takes `BOLT_SECONDS`, the time its bolt needs, 8 by default.
|
||||
|
||||
## Test a device without a broker
|
||||
|
||||
[testing/lamp.test.js](testing/lamp.test.js) tests a lamp with a `MemoryPublisher` and `bridge.receive()`. It needs
|
||||
no credentials and connects nowhere:
|
||||
|
||||
```sh
|
||||
node examples/testing/lamp.test.js
|
||||
```
|
||||
|
||||
## What was tested
|
||||
|
||||
`test/examples.test.js` starts every file with `node`, against a broker on 127.0.0.1, discovers the device, sends
|
||||
|
|
|
|||
83
examples/testing/lamp.test.js
Normal file
83
examples/testing/lamp.test.js
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
// 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");
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue