Layered: routes -> services -> repositories -> db, over PostgreSQL 18 with split roles (findr_migrator for DDL, findr_app for DML). Keycloak JWT auth with per-endpoint scope guards. Drizzle migrations run with the DDL role at container start. Pick-by-light: one ESP32 controller drives one WS2812 chain through several boxes; each box owns a contiguous LED slice. MQTT contract in src/lib/mqtt-topics.ts / docs/mqtt.md. /v1/pick controls light/idle/off and /v1/boxes + /v1/controllers manage the strip mapping. Reference resource /v1/parts wired end to end. Vitest, Biome, multi-stage Dockerfile, local compose.yml with Postgres. Signed-off-by: Peter Siegmund <mars3142@noreply.mars3142.dev>
54 lines
1.7 KiB
TypeScript
54 lines
1.7 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
boxNumberFromTopic,
|
|
idleCommand,
|
|
offCommand,
|
|
pickCommand,
|
|
topics,
|
|
} from "../src/lib/mqtt-topics.js";
|
|
|
|
describe("mqtt topic builders", () => {
|
|
const t = topics("findr");
|
|
|
|
it("builds the command and state topics for a box", () => {
|
|
expect(t.boxConfig(2)).toBe("findr/box/2/config");
|
|
expect(t.cmdLight(2)).toBe("findr/box/2/cmd/light");
|
|
expect(t.stateLight(2)).toBe("findr/box/2/state/light");
|
|
expect(t.ctrlOnline("abc")).toBe("findr/controller/abc/state/online");
|
|
expect(t.allBoxState()).toBe("findr/box/+/state/+");
|
|
expect(t.allControllerState()).toBe("findr/controller/+/state/+");
|
|
});
|
|
|
|
it("extracts the box number from a topic", () => {
|
|
expect(boxNumberFromTopic("findr/box/7/state/light")).toBe(7);
|
|
expect(boxNumberFromTopic("findr/box/x/state/light")).toBeNull();
|
|
expect(boxNumberFromTopic("other/box/7/state/light")).toBeNull();
|
|
});
|
|
|
|
it("respects a custom prefix", () => {
|
|
const p = topics("lab");
|
|
expect(p.cmdLight(3)).toBe("lab/box/3/cmd/light");
|
|
expect(boxNumberFromTopic("lab/box/3/evt/button", "lab")).toBe(3);
|
|
});
|
|
});
|
|
|
|
describe("light command builders", () => {
|
|
it("pick lights exactly the given bins", () => {
|
|
const cmd = pickCommand([{ cell: "D4", ledIndex: 27, mode: "take" }], {
|
|
seconds: 30,
|
|
requestId: "r1",
|
|
});
|
|
expect(cmd).toEqual({
|
|
effect: "pick",
|
|
bins: [{ cell: "D4", ledIndex: 27, mode: "take" }],
|
|
seconds: 30,
|
|
requestId: "r1",
|
|
});
|
|
});
|
|
|
|
it("idle and off carry just the effect", () => {
|
|
expect(idleCommand().effect).toBe("idle");
|
|
expect(offCommand().effect).toBe("off");
|
|
});
|
|
});
|