proactiveEvent() built every proactive event without a context, so doorbellPress() and
device.raise(DoorbellEventSource, "DoorbellPress") left out the "context": {} that the example of
alexa-doorbelleventsource.html has. The Event of Alexa.SimpleEventSource is printed without one, so
the event descriptor says which it is (emptyContext) and raise() passes it on to the builder.
A raised DoorbellPress is now { event, context: {} }, 13 bytes longer; the Event of a button is
unchanged. The tests compare the keys and the context of the built message with the fixture of the
page, for the builder, the descriptor and raise(). 267 tests pass.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
170 lines
8 KiB
JavaScript
170 lines
8 KiB
JavaScript
"use strict";
|
|
// The builders of src/messages/build.ts. Given a messageId and the times, a builder returns the same object every
|
|
// time, so each test compares the whole message.
|
|
const { test } = require("node:test");
|
|
const assert = require("node:assert/strict");
|
|
const { messages, MessageError } = require("alex2node");
|
|
const { doc } = require("../helpers/fixtures.js");
|
|
const { UUID_V4 } = require("../helpers/harness.js");
|
|
|
|
const AT = "2026-09-28T10:00:00.000Z";
|
|
const messageId = "0c2bd0a4-6c1c-4b7a-9d3e-5f0a3a1b2c3d";
|
|
const powerState = messages.property("Alexa.PowerController", "powerState", "ON", { timeOfSample: AT });
|
|
const brightness = messages.property("Alexa.BrightnessController", "brightness", 40, { timeOfSample: AT, uncertaintyInMilliseconds: 500 });
|
|
|
|
test("response: the event and the state of the endpoint as its context", () => {
|
|
assert.deepEqual(messages.response({ endpointId: "lamp-1", correlationToken: "ct", messageId, context: [powerState, brightness] }), {
|
|
event: {
|
|
header: { namespace: "Alexa", name: "Response", messageId, correlationToken: "ct", payloadVersion: "3" },
|
|
endpoint: { endpointId: "lamp-1" },
|
|
payload: {},
|
|
},
|
|
context: {
|
|
properties: [
|
|
{ namespace: "Alexa.PowerController", name: "powerState", value: "ON", timeOfSample: AT, uncertaintyInMilliseconds: 0 },
|
|
{ namespace: "Alexa.BrightnessController", name: "brightness", value: 40, timeOfSample: AT, uncertaintyInMilliseconds: 500 },
|
|
],
|
|
},
|
|
});
|
|
});
|
|
|
|
test("response: nothing to report is an empty list of properties; an interface names its own response", () => {
|
|
const armed = messages.response({
|
|
namespace: "Alexa.SecurityPanelController", name: "Arm.Response", endpointId: "panel", correlationToken: "ct", messageId,
|
|
payload: { exitDelayInSeconds: 30 },
|
|
});
|
|
assert.deepEqual(armed, {
|
|
event: {
|
|
header: { namespace: "Alexa.SecurityPanelController", name: "Arm.Response", messageId, correlationToken: "ct", payloadVersion: "3" },
|
|
endpoint: { endpointId: "panel" },
|
|
payload: { exitDelayInSeconds: 30 },
|
|
},
|
|
context: { properties: [] },
|
|
});
|
|
});
|
|
|
|
test("stateReport: a Response by the name of StateReport", () => {
|
|
assert.deepEqual(messages.stateReport({ endpointId: "lamp-1", correlationToken: "ct", messageId, context: [powerState] }), {
|
|
event: {
|
|
header: { namespace: "Alexa", name: "StateReport", messageId, correlationToken: "ct", payloadVersion: "3" },
|
|
endpoint: { endpointId: "lamp-1" },
|
|
payload: {},
|
|
},
|
|
context: { properties: [powerState] },
|
|
});
|
|
});
|
|
|
|
test("deferredResponse: no context key, the estimate only when given", () => {
|
|
const event = {
|
|
header: { namespace: "Alexa", name: "DeferredResponse", messageId, correlationToken: "ct", payloadVersion: "3" },
|
|
endpoint: { endpointId: "lamp-1" },
|
|
};
|
|
assert.deepEqual(messages.deferredResponse({ endpointId: "lamp-1", correlationToken: "ct", messageId }), {
|
|
event: { ...event, payload: {} },
|
|
});
|
|
assert.deepEqual(
|
|
messages.deferredResponse({ endpointId: "lamp-1", correlationToken: "ct", messageId, estimatedDeferralInSeconds: 20 }),
|
|
{ event: { ...event, payload: { estimatedDeferralInSeconds: 20 } } }
|
|
);
|
|
});
|
|
|
|
test("errorResponse: the event alone, the fields of the type next to type and message", () => {
|
|
const answer = messages.errorResponse({
|
|
endpointId: "lamp-1", correlationToken: "ct", messageId,
|
|
type: "VALUE_OUT_OF_RANGE", message: "too high", extra: { validRange: { minimumValue: 1, maximumValue: 10 } },
|
|
});
|
|
assert.deepEqual(answer, {
|
|
event: {
|
|
header: { namespace: "Alexa", name: "ErrorResponse", messageId, correlationToken: "ct", payloadVersion: "3" },
|
|
endpoint: { endpointId: "lamp-1" },
|
|
payload: { type: "VALUE_OUT_OF_RANGE", message: "too high", validRange: { minimumValue: 1, maximumValue: 10 } },
|
|
},
|
|
});
|
|
});
|
|
|
|
test("changeReport: what changed in payload.change, the rest in the context, no correlationToken", () => {
|
|
const report = messages.changeReport({ endpointId: "lamp-1", messageId, cause: "APP_INTERACTION", changed: [powerState], context: [brightness] });
|
|
assert.deepEqual(report, {
|
|
event: {
|
|
header: { namespace: "Alexa", name: "ChangeReport", messageId, payloadVersion: "3" },
|
|
endpoint: { endpointId: "lamp-1" },
|
|
payload: { change: { cause: { type: "APP_INTERACTION" }, properties: [powerState] } },
|
|
},
|
|
context: { properties: [brightness] },
|
|
});
|
|
});
|
|
|
|
test("changeReport: a property that changed is not repeated in the context; instances are told apart", () => {
|
|
const lift = (instance, value) => messages.property("Alexa.RangeController", "rangeValue", value, { instance, timeOfSample: AT });
|
|
const report = messages.changeReport({
|
|
endpointId: "blind", messageId,
|
|
changed: [lift("Blind.Lift", 40)],
|
|
context: [lift("Blind.Lift", 30), lift("Blind.Tilt", 5)],
|
|
});
|
|
assert.equal(report.event.payload.change.cause.type, "PHYSICAL_INTERACTION");
|
|
assert.deepEqual(report.context.properties, [lift("Blind.Tilt", 5)]);
|
|
});
|
|
|
|
test("changeReport: without a changed property it is refused, the error names the endpoint and what to do", () => {
|
|
assert.throws(
|
|
() => messages.changeReport({ endpointId: "lamp-1", changed: [], context: [brightness] }),
|
|
(err) => {
|
|
assert.ok(err instanceof MessageError);
|
|
assert.equal(err.endpointId, "lamp-1");
|
|
assert.match(err.message, /^lamp-1: a ChangeReport needs at least one property that changed/);
|
|
assert.match(err.message, /send no report when nothing changed/);
|
|
return true;
|
|
}
|
|
);
|
|
});
|
|
|
|
test("sceneEvent: ActivationStarted and DeactivationStarted with the cause and the time", () => {
|
|
const started = messages.sceneEvent({ endpointId: "scene-1", correlationToken: "ct", messageId, activated: true, timestamp: AT });
|
|
assert.deepEqual(started, {
|
|
event: {
|
|
header: { namespace: "Alexa.SceneController", name: "ActivationStarted", messageId, correlationToken: "ct", payloadVersion: "3" },
|
|
endpoint: { endpointId: "scene-1" },
|
|
payload: { cause: { type: "VOICE_INTERACTION" }, timestamp: AT },
|
|
},
|
|
context: {},
|
|
});
|
|
const stopped = messages.sceneEvent({
|
|
endpointId: "scene-1", correlationToken: "ct", messageId, activated: false, cause: "APP_INTERACTION", timestamp: new Date(AT),
|
|
});
|
|
assert.equal(stopped.event.header.name, "DeactivationStarted");
|
|
assert.deepEqual(stopped.event.payload, { cause: { type: "APP_INTERACTION" }, timestamp: AT });
|
|
});
|
|
|
|
test("doorbellPress: an event without a correlationToken, with the empty context of the page", () => {
|
|
const built = messages.doorbellPress({ endpointId: "door", messageId, timestamp: AT });
|
|
assert.deepEqual(built, {
|
|
event: {
|
|
header: { namespace: "Alexa.DoorbellEventSource", name: "DoorbellPress", messageId, payloadVersion: "3" },
|
|
endpoint: { endpointId: "door" },
|
|
payload: { cause: { type: "PHYSICAL_INTERACTION" }, timestamp: AT },
|
|
},
|
|
context: {},
|
|
});
|
|
assert.deepEqual(built.context, doc("alexa-doorbelleventsource", "DoorbellPress.event").context);
|
|
});
|
|
|
|
test("simpleEvent: the instance in the header, payloadVersion 1.0, no context as on the page", () => {
|
|
assert.equal("context" in doc("alexa-simpleeventsource", "Event.event"), false);
|
|
assert.deepEqual(messages.simpleEvent({ endpointId: "button", instance: "Button.Front", id: "Button.SinglePush.1", messageId, timestamp: AT }), {
|
|
event: {
|
|
header: { namespace: "Alexa.SimpleEventSource", name: "Event", instance: "Button.Front", messageId, payloadVersion: "1.0" },
|
|
endpoint: { endpointId: "button" },
|
|
payload: { id: "Button.SinglePush.1", timestamp: AT },
|
|
},
|
|
});
|
|
});
|
|
|
|
test("left out, the messageId is a new version 4 UUID and the time is now", () => {
|
|
const before = Date.now();
|
|
const first = messages.doorbellPress({ endpointId: "door" });
|
|
const second = messages.doorbellPress({ endpointId: "door" });
|
|
assert.match(first.event.header.messageId, UUID_V4);
|
|
assert.notEqual(first.event.header.messageId, second.event.header.messageId);
|
|
const at = Date.parse(first.event.payload.timestamp);
|
|
assert.ok(at >= before && at <= Date.now(), first.event.payload.timestamp);
|
|
});
|