messages: pure builders for every event the bridge sends
src/messages builds Response, StateReport, DeferredResponse, ErrorResponse, ChangeReport and the scene, doorbell and simple events from plain values; messageId and times are parameters, so a test compares whole objects. StateBuilder collects properties checked by the descriptors, AlexaError and AlexaErrors carry the payload fields of an error type. AlexaStatusMessage and AlexaErrorResponse move to src/compat and are written on the builders. On the wire, against 1.5.2: a DeferredResponse has no context key, a ChangeReport has no correlationToken, the namespace of an ErrorResponse follows its type, and a ChangeReport without a changed property is not published: send() resolves "" and the bridge reports an error that names the endpoint. 143 tests pass (108 before). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
2558e21787
commit
46fa06728c
68 changed files with 3410 additions and 1208 deletions
91
test/messages/state.test.js
Normal file
91
test/messages/state.test.js
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
"use strict";
|
||||
// StateBuilder: the properties of one message, checked by the descriptors of the registry.
|
||||
const { test } = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const {
|
||||
BrightnessController, Capability, EndpointHealth, PowerController, RangeController, SchemaError, StateBuilder,
|
||||
TemperatureSensor, messages,
|
||||
} = require("alex2node");
|
||||
|
||||
const AT = "2026-09-28T10:00:00.000Z";
|
||||
const now = () => new Date(AT);
|
||||
|
||||
test("set: the property of a described interface, its time from the clock of the builder", () => {
|
||||
const state = new StateBuilder({ now }).set(PowerController, "powerState", "ON").set(BrightnessController, "brightness", 40);
|
||||
assert.deepEqual(state.context, [
|
||||
{ namespace: "Alexa.PowerController", name: "powerState", value: "ON", timeOfSample: AT, uncertaintyInMilliseconds: 0 },
|
||||
{ namespace: "Alexa.BrightnessController", name: "brightness", value: 40, timeOfSample: AT, uncertaintyInMilliseconds: 0 },
|
||||
]);
|
||||
assert.deepEqual(state.change, []);
|
||||
});
|
||||
|
||||
test("set: a property has the time and the uncertainty it is given", () => {
|
||||
const changedAt = "2026-09-28T09:12:00.000Z";
|
||||
const [property] = new StateBuilder({ now })
|
||||
.set(PowerController, "powerState", "OFF", { timeOfSample: changedAt, uncertaintyInMilliseconds: 6000 }).context;
|
||||
assert.equal(property.timeOfSample, changedAt);
|
||||
assert.equal(property.uncertaintyInMilliseconds, 6000);
|
||||
});
|
||||
|
||||
test("set: a temperature is reported in the scale it is given in", () => {
|
||||
const [property] = new StateBuilder({ now }).set(TemperatureSensor, "temperature", { value: 70, scale: "FAHRENHEIT" }).context;
|
||||
assert.deepEqual(property.value, { value: 70, scale: "FAHRENHEIT" });
|
||||
});
|
||||
|
||||
test("set: a value Alexa would not take is refused, the error names the interface and the property", () => {
|
||||
const state = new StateBuilder({ now });
|
||||
assert.throws(() => state.set(PowerController, "powerState", "on"), (err) => {
|
||||
assert.ok(err instanceof SchemaError);
|
||||
assert.match(err.message, /^Alexa\.PowerController: powerState: expected .*ON.*got "on"$/);
|
||||
return true;
|
||||
});
|
||||
assert.throws(() => state.set(PowerController, "power", "ON"), /Alexa\.PowerController: power: not a property of the interface, which has powerState/);
|
||||
assert.deepEqual(state.context, []);
|
||||
});
|
||||
|
||||
test("set: a generic controller needs its instance, given with the property or by the capability", () => {
|
||||
const state = new StateBuilder({ now });
|
||||
assert.throws(() => state.set(RangeController, "rangeValue", 3), /Alexa\.RangeController: rangeValue: the interface has instances/);
|
||||
|
||||
const lift = new Capability(RangeController, { instance: "Blind.Lift", options: {} });
|
||||
state.set(RangeController, "rangeValue", 3, { instance: "Blind.Tilt" }).set(lift, "rangeValue", 40);
|
||||
assert.deepEqual(state.context, [
|
||||
{ namespace: "Alexa.RangeController", instance: "Blind.Tilt", name: "rangeValue", value: 3, timeOfSample: AT, uncertaintyInMilliseconds: 0 },
|
||||
{ namespace: "Alexa.RangeController", instance: "Blind.Lift", name: "rangeValue", value: 40, timeOfSample: AT, uncertaintyInMilliseconds: 0 },
|
||||
]);
|
||||
});
|
||||
|
||||
test("setRaw: a property of an interface the library does not describe, nothing checked", () => {
|
||||
const state = new StateBuilder({ now }).setRaw("Alexa.Vendor.Thing", "level", { anything: true }, { instance: "Thing.One" });
|
||||
assert.deepEqual(state.context, [
|
||||
{ namespace: "Alexa.Vendor.Thing", instance: "Thing.One", name: "level", value: { anything: true }, timeOfSample: AT, uncertaintyInMilliseconds: 0 },
|
||||
]);
|
||||
});
|
||||
|
||||
test("health: the connectivity of Alexa.EndpointHealth, with the reason when there is one", () => {
|
||||
const state = new StateBuilder({ now }).health("OK").health("UNREACHABLE", "WIFI_AP_NOT_FOUND");
|
||||
assert.deepEqual(state.context.map((property) => [property.namespace, property.name, property.value]), [
|
||||
[EndpointHealth.namespace, "connectivity", { value: "OK" }],
|
||||
[EndpointHealth.namespace, "connectivity", { value: "UNREACHABLE", reason: "WIFI_AP_NOT_FOUND" }],
|
||||
]);
|
||||
assert.throws(() => new StateBuilder().health("ASLEEP"), SchemaError);
|
||||
});
|
||||
|
||||
test("changed and unchanged: the list the properties that follow go to; add() names one", () => {
|
||||
const state = new StateBuilder({ now, target: "change" })
|
||||
.set(PowerController, "powerState", "ON")
|
||||
.unchanged()
|
||||
.set(BrightnessController, "brightness", 40)
|
||||
.changed()
|
||||
.health("OK")
|
||||
.add(messages.property("Alexa.Vendor.Thing", "level", 1, { timeOfSample: AT }), "context");
|
||||
assert.deepEqual(state.change.map((property) => property.name), ["powerState", "connectivity"]);
|
||||
assert.deepEqual(state.context.map((property) => property.name), ["brightness", "level"]);
|
||||
});
|
||||
|
||||
test("context and change are copies: the message built from them does not change with the builder", () => {
|
||||
const state = new StateBuilder({ now }).set(PowerController, "powerState", "ON");
|
||||
const report = messages.stateReport({ endpointId: "lamp-1", correlationToken: "ct", messageId: "m", context: state.context });
|
||||
state.set(BrightnessController, "brightness", 40);
|
||||
assert.equal(report.context.properties.length, 1);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue