bridge: subscribe to discover and +/alexaDirective only

The bridge subscribed to <root>/#, so the broker sent back every discover_r, alexaResponce and changeReport
the bridge published, and the backend's <endpointId>/alexaDirective_e. It now subscribes to <root>/discover
and <root>/+/alexaDirective, both named in src/topics.ts; a directive topic is recognised by the root and the
last segment instead of by counting three segments.
The test reads the subscriptions from the broker, publishes seven other topics under the root and one
directive for each of two endpoints: the bridge receives the two directives and the discovery request,
nothing else. Tests: 148 -> 150 (the count in the body of 6789a1a, 108 -> 113, should read 143 -> 148).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
David 2026-09-28 19:36:09 +00:00
parent 6789a1a077
commit e0fe9d2812
11 changed files with 225 additions and 93 deletions

View file

@ -0,0 +1,63 @@
"use strict";
// What the bridge subscribes to: <root>/discover and <root>/+/alexaDirective. 1.x subscribed to <root>/#, which also
// delivered everything the bridge published itself and the backend's <endpointId>/alexaDirective_e.
const { test } = require("node:test");
const assert = require("node:assert/strict");
const { Alex2MQTT, DisplayCategory, PowerController, topics } = require("alex2node");
const { broker, watcher, connected, directive, until, sleep } = require("./helpers/harness.js");
test("topics: the two filters, and the endpointId of a directive topic", () => {
assert.deepEqual(topics.subscriptions("root"), ["root/discover", "root/+/alexaDirective"]);
assert.equal(topics.directive("root", "lamp-1"), "root/lamp-1/alexaDirective");
assert.equal(topics.directiveEndpoint("root", "root/lamp-1/alexaDirective"), "lamp-1");
for (const other of [
"root/lamp-1/alexaDirective_e", "root/lamp-1/alexaResponce", "root/alexaDirective", "root/a/b/alexaDirective",
"root//alexaDirective", "root2/lamp-1/alexaDirective", "root/discover",
]) {
assert.equal(topics.directiveEndpoint("root", other), null, other);
}
});
test("the bridge subscribes to discover and +/alexaDirective: two endpoints get their directives, no other topic reaches the bridge", async () => {
const { aedes, url } = await broker();
const subscribed = [];
aedes.on("subscribe", (subscriptions, client) => {
if (client.id === "bridge-under-test") subscribed.push(...subscriptions.map((s) => s.topic));
});
const alexa = await watcher(url, "root/#");
const received = [];
const log = (line, detail) => { if (line === "MQTT Message Received") received.push(detail.topic); };
const bridge = await connected(new Alex2MQTT("u", "p", "root", false, { host: url, log, mqtt: { clientId: "bridge-under-test" } }));
assert.deepEqual(subscribed.sort(), ["root/+/alexaDirective", "root/discover"]);
const heard = [];
for (const id of ["lamp-1", "lamp-2"]) {
const lamp = bridge.registerDevice(id, id, DisplayCategory.LIGHT);
const answer = (d) => {
heard.push(`${id} ${d.header.name}`);
return lamp.getStatusMessage(d.header.correlationToken, true).addPowerControllerProp(PowerController.ON).send();
};
lamp.on("Event", answer);
lamp.on("ReportState", answer);
}
bridge.on("directive", ({ endpointId, name }) => heard.push(`bridge ${endpointId} ${name}`));
// Under the root, and none of them for the bridge: the last three are what the bridge and the backend publish
const turnOn = directive("Alexa.PowerController", "TurnOn", "lamp-1", "not-for-the-bridge");
for (const topic of ["root/lamp-1/alexaDirective_e", "root/lamp-1/status", "root/lamp-1/alexaDirective/more", "root/alexaDirective"]) {
alexa.publish(topic, turnOn);
}
alexa.publish("root/lamp-1/alexaResponce", { event: {} });
alexa.publish("root/changeReport", { event: {} });
alexa.publish("root/discover_r", []);
alexa.send("root", directive("Alexa.PowerController", "TurnOn", "lamp-1", "ct-1"));
alexa.send("root", directive("Alexa.PowerController", "TurnOff", "lamp-2", "ct-2"));
alexa.publish("root/discover", "");
const answered = (id) => alexa.on(`root/${id}/alexaResponce`).some((m) => m.event.header);
await until(() => answered("lamp-1") && answered("lamp-2") && alexa.on("root/discover_r").length === 2, 3000, "both responses and the discovery answer");
await sleep(100); // time for the echo of the bridge's own publishes, which must not come
assert.deepEqual(heard, ["bridge lamp-1 TurnOn", "lamp-1 TurnOn", "bridge lamp-2 TurnOff", "lamp-2 TurnOff"]);
assert.deepEqual(received, ["root/lamp-1/alexaDirective", "root/lamp-2/alexaDirective", "root/discover"]);
});