registry: SecurityPanelController with Arm.Response and its errors
Alexa.SecurityPanelController is described from its page: version 3 (the stub of 1.5.2 said 1), armState and the four alarms, Arm and Disarm. ctx.respond() answers Arm with Arm.Response under the namespace of the interface, its payload checked (exitDelayInSeconds 0 to 255, bypassedEndpoints), and Disarm with a Response. A Disarm carries the PIN the user said when the panel is declared with supportedAuthorizationTypes; the handler checks it and answers UNAUTHORIZED, which goes under Alexa.SecurityPanelController like the five other error types. A panel declared without options lists armState, where 1.5.2 listed no property; alarms are declared. 17 examples of the two pages are saved as fixtures. 215 tests pass, 207 before. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
f555a5df70
commit
c59cf3ff10
39 changed files with 1248 additions and 23 deletions
|
|
@ -48,14 +48,15 @@ for (const { namespace, page, declared, unfit = [], chosen = false, folded = []
|
|||
}
|
||||
}
|
||||
|
||||
const directives = examples(page, ".directive").map((name) => doc(page, name).directive);
|
||||
// A page also shows the directives of the interface it is paired with: TurnOn on the page of WakeOnLANController
|
||||
const directives = examples(page, ".directive").map((name) => doc(page, name).directive)
|
||||
.filter((directive) => directive.header.namespace === namespace);
|
||||
assert.deepEqual(
|
||||
[...new Set(directives.map((directive) => directive.header.name))].sort(),
|
||||
Object.keys(descriptor.directives).filter((name) => name !== "ReportState" && !folded.includes(name)).sort(),
|
||||
"the page has an example of every directive"
|
||||
);
|
||||
for (const { header, payload } of directives) {
|
||||
assert.equal(header.namespace, namespace);
|
||||
assert.deepEqual(descriptor.directives[header.name].payload.parse(payload, "payload"), payload);
|
||||
}
|
||||
|
||||
|
|
@ -135,8 +136,10 @@ test("every AlexaInterfaceType is in the registry: described, or a stub with the
|
|||
});
|
||||
|
||||
test("AlexaInterface.getVersion() and getProps() read the registry", () => {
|
||||
// What a thermostat lists is for its declaration to say: a 1.x declaration lists what 1.5.2 listed
|
||||
// What a thermostat or a security panel lists is for its declaration to say: a 1.x declaration of the thermostat
|
||||
// lists what 1.5.2 listed, one of the panel the arm state and no alarm
|
||||
const chosen = {
|
||||
"Alexa.SecurityPanelController": ["armState"],
|
||||
"Alexa.ThermostatController": ["targetSetpoint", "lowerSetpoint", "upperSetpoint", "thermostatMode"],
|
||||
"Alexa.ThermostatController.Schedule": ["scheduleEnabled"],
|
||||
};
|
||||
|
|
|
|||
51
test/registry/security-panel.test.js
Normal file
51
test/registry/security-panel.test.js
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
"use strict";
|
||||
// Alexa.SecurityPanelController: what the examples of its page do not show. The examples themselves are run by
|
||||
// descriptors.test.js, device/discovery.test.js and dispatch/interfaces.test.js.
|
||||
const { test } = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const { AlexaInterfaceType, DeclarationError, SecurityPanelController } = require("alex2node");
|
||||
const { endpoint } = require("../helpers/endpoint.js");
|
||||
|
||||
const panel = () => endpoint("panel-1", "Panel", ["SECURITY_PANEL"]);
|
||||
|
||||
test("a panel declared without options lists the arm state and no configuration, the 1.x way or with add()", () => {
|
||||
const announced = {
|
||||
type: "AlexaInterface",
|
||||
interface: "Alexa.SecurityPanelController",
|
||||
version: "3",
|
||||
properties: { supported: [{ name: "armState" }], proactivelyReported: false, retrievable: true },
|
||||
};
|
||||
assert.deepEqual(panel().addCapability(AlexaInterfaceType.SECURITY_PANEL_CONTROLLER).getJSON(), announced);
|
||||
const device = panel();
|
||||
assert.deepEqual(device.add(SecurityPanelController).toJSON(), announced);
|
||||
assert.deepEqual(device.check(), []);
|
||||
});
|
||||
|
||||
test("the alarms and the arm states of a declaration are the ones of the page, each listed once", () => {
|
||||
const { properties, configuration } = panel().add(SecurityPanelController, {
|
||||
supportedArmStates: ["ARMED_NIGHT", "DISARMED"],
|
||||
alarms: ["waterAlarm", "carbonMonoxideAlarm"],
|
||||
}).toJSON();
|
||||
assert.deepEqual(properties.supported, [{ name: "armState" }, { name: "waterAlarm" }, { name: "carbonMonoxideAlarm" }]);
|
||||
assert.deepEqual(configuration, { supportedArmStates: [{ value: "ARMED_NIGHT" }, { value: "DISARMED" }] });
|
||||
|
||||
const refused = (options, message) => assert.throws(
|
||||
() => panel().add(SecurityPanelController, options),
|
||||
(err) => err instanceof DeclarationError && err.message === `panel-1: Alexa.SecurityPanelController: ${message}`
|
||||
);
|
||||
refused({ alarms: ["smokeAlarm"] }, 'alarms[0]: expected burglaryAlarm | fireAlarm | carbonMonoxideAlarm | waterAlarm, got "smokeAlarm"');
|
||||
refused({ alarms: ["fireAlarm", "fireAlarm"] }, "fireAlarm is listed twice");
|
||||
refused({ supportedArmStates: [] }, "supportedArmStates: expected a list with 1 or more entries, got []");
|
||||
refused({ supportedAuthorizationTypes: ["SIX_DIGIT_PIN"] }, 'supportedAuthorizationTypes[0]: expected FOUR_DIGIT_PIN, got "SIX_DIGIT_PIN"');
|
||||
});
|
||||
|
||||
test("Arm.Response: an exit delay of 0 to 255 seconds, and the sensors that were bypassed", () => {
|
||||
const { namespace, name, payload } = SecurityPanelController.responseFor("Arm");
|
||||
assert.deepEqual([namespace, name], ["Alexa.SecurityPanelController", "Arm.Response"]);
|
||||
for (const fields of [{}, { exitDelayInSeconds: 0 }, { exitDelayInSeconds: 255, bypassedEndpoints: [{ friendlyName: "water sensor" }] }]) {
|
||||
assert.deepEqual(payload.parse(fields, "payload"), fields);
|
||||
}
|
||||
assert.throws(() => payload.parse({ exitDelayInSeconds: 1.5 }, "payload"), /^SchemaError: payload\.exitDelayInSeconds: expected an integer from 0 to 255, got 1\.5$/);
|
||||
assert.throws(() => payload.parse({ bypassedEndpoints: ["side window"] }, "payload"), /^SchemaError: payload\.bypassedEndpoints\[0\]: expected an object/);
|
||||
assert.equal(SecurityPanelController.responseFor("Disarm"), undefined);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue