registry: describe an Alexa interface as data
src/registry/ holds what the library knows about an interface: namespace, version, the page it was read from, properties with their value schemas and directives with their payload schemas. Five interfaces are described (Alexa, PowerController, BrightnessController, TemperatureSensor, EndpointHealth); the other 65 names of AlexaInterfaceType are stubs with the version and property names of 1.5.2. schema.ts is the run-time check behind it (241 lines, no new dependency), catalog.ts the vocabularies of the pages: 103 assets (23 units), 6 actions, 9 states, 56 display categories, 22 reserved words, 73 error types under 11 namespaces. AlexaInterface.getVersion() and getProps() read the registry; the two switch statements are gone (-167 lines). On the wire: Alexa.EndpointHealth is announced at 3.1 (was 3.3; the page is titled 3.1 and no page mentions 3.3), and TimeHoldController and Camera.LiveViewController at 3 and 1.7 (1.5.2 sent the string "UNKNOWN"). DisplayCategory gains VACUUM. New exports: registry, DeclarationError, SchemaError, Assets, Units, Actions, States, DisplayCategories and the descriptor types. Tests: 20 JSON examples of the five pages under test/fixtures/alexa-docs; every directive payload and property value in them parses with its descriptor. npm test: 57 pass (was 30) in 10.8 s, also on Node 18.20.8 and 20.20.2. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
128ca35c2a
commit
aa0ffd64ea
93 changed files with 4058 additions and 490 deletions
72
test/registry/catalog.test.js
Normal file
72
test/registry/catalog.test.js
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
"use strict";
|
||||
// The vocabularies copied from Amazon's pages: their sizes as counted on the pages on 2026-09-28, and how they
|
||||
// relate to the 1.x enums.
|
||||
const { test } = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const {
|
||||
Actions, AlexaActions, AlexaErrorType, Assets, DisplayCategories, DisplayCategory, States, Units,
|
||||
} = require("alex2node");
|
||||
const { ERROR_TYPES, RESERVED_WORDS, LIMITS } = require("../../dist/cjs/registry/catalog.js");
|
||||
|
||||
const unique = (list) => new Set(list).size === list.length;
|
||||
|
||||
test("assets: the 103 ids of the global Alexa catalog, 23 of them units of measure", () => {
|
||||
assert.equal(Assets.length, 103);
|
||||
assert.ok(unique(Assets));
|
||||
for (const id of Assets) assert.match(id, /^Alexa\.(Actions|Button|DeviceName|Gestures?|Setting|Shower|Unit|Value)\.[A-Za-z0-9.]+$/);
|
||||
assert.equal(Units.length, 23);
|
||||
assert.deepEqual(Units, Assets.filter((id) => id.startsWith("Alexa.Unit.")));
|
||||
assert.ok(Units.includes("Alexa.Unit.Percent"));
|
||||
// The page has "Gesture" seven times and "Gestures" once
|
||||
assert.deepEqual(Assets.filter((id) => id.startsWith("Alexa.Gestures.")), ["Alexa.Gestures.DoubleTap"]);
|
||||
});
|
||||
|
||||
test("semantics: six actions, the ones the AlexaActions enum of 1.x has, and nine states", () => {
|
||||
assert.deepEqual([...Actions].sort(), Object.values(AlexaActions).sort());
|
||||
assert.deepEqual(States.map((id) => id.replace("Alexa.States.", "")), ["Open", "Closed", "EcoOn", "EcoOff", "Low", "Empty", "Full", "Done", "Stuck"]);
|
||||
});
|
||||
|
||||
test("display categories: the 56 of the page; the enum has them all and keeps VEHICLE", () => {
|
||||
assert.equal(DisplayCategories.length, 56);
|
||||
assert.ok(unique(DisplayCategories));
|
||||
const inEnum = Object.values(DisplayCategory);
|
||||
assert.deepEqual(DisplayCategories.filter((category) => !inEnum.includes(category)), []);
|
||||
assert.deepEqual(inEnum.filter((category) => !DisplayCategories.includes(category)), ["VEHICLE"]);
|
||||
assert.equal(DisplayCategory.VACUUM, "VACUUM");
|
||||
for (const [key, value] of Object.entries(DisplayCategory)) assert.equal(key, value);
|
||||
});
|
||||
|
||||
test("error types: the 73 of the error type table, each under the namespace of its interface", () => {
|
||||
assert.equal(Object.keys(ERROR_TYPES).length, 73);
|
||||
assert.equal(ERROR_TYPES.ENDPOINT_UNREACHABLE, "Alexa");
|
||||
assert.equal(ERROR_TYPES.INVALID_VALUE, "Alexa");
|
||||
assert.equal(ERROR_TYPES.THERMOSTAT_IS_OFF, "Alexa.ThermostatController");
|
||||
assert.equal(ERROR_TYPES.UNAUTHORIZED, "Alexa.SecurityPanelController");
|
||||
assert.equal(ERROR_TYPES.OBSTACLE_DETECTED, "Alexa.Safety");
|
||||
assert.equal(ERROR_TYPES.TEMPERATURE_VALUE_OUT_OF_RANGE, "Alexa", "the generic one, not a thermostat error");
|
||||
const namespaces = [...new Set(Object.values(ERROR_TYPES))].sort();
|
||||
assert.equal(namespaces.length, 11);
|
||||
for (const namespace of namespaces) assert.match(namespace, /^Alexa(\.[A-Za-z]+)*$/);
|
||||
|
||||
// AlexaErrorType of 1.x has every type of the table and two that the table does not list
|
||||
const inEnum = Object.values(AlexaErrorType);
|
||||
assert.deepEqual(Object.keys(ERROR_TYPES).filter((type) => !inEnum.includes(type)), []);
|
||||
assert.deepEqual(inEnum.filter((type) => !(type in ERROR_TYPES)).sort(), ["EXCEEDED_PIN_ATTEMPTS", "PIN_SETUP_REQUIRED"]);
|
||||
});
|
||||
|
||||
test("reserved words and limits", () => {
|
||||
assert.equal(RESERVED_WORDS.length, 22);
|
||||
assert.ok(unique(RESERVED_WORDS));
|
||||
for (const word of RESERVED_WORDS) assert.equal(word, word.toLowerCase());
|
||||
assert.deepEqual(LIMITS, {
|
||||
endpointsPerCustomer: 300,
|
||||
capabilitiesPerEndpoint: 100,
|
||||
endpointIdLength: 256,
|
||||
friendlyNameLength: 256,
|
||||
sceneFriendlyNameLength: 128,
|
||||
manufacturerNameLength: 128,
|
||||
descriptionLength: 128,
|
||||
additionalAttributeLength: 256,
|
||||
cookieBytes: 5000,
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue