cameras/cli/onvif-dashboard.test.ts
Michael Mainguy 65345d0984 Add the first-run experience and the onvif-dashboard command
- src/lib/first-run.ts settles where data lives (asked once, remembered
  in a pointer under the user's config dir), creates the owner-only key
  that protects stored camera logins, and reports whether MediaMTX is
  installed. Without a terminal nothing prompts: defaults are taken and
  logged, so a service still starts. Running it again changes nothing.
- cli/onvif-dashboard.mjs is the published command: setup, install-video
  and help. Node runs the TypeScript in src/lib directly, so the CLI
  needs no build of its own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 19:00:16 -05:00

69 lines
2.7 KiB
TypeScript

import { execFile } from "node:child_process";
import { mkdtemp, readFile, realpath, rm, stat } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { promisify } from "node:util";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
/** Runs the published command as a real process, in a directory of its own. */
const run = promisify(execFile);
const CLI = path.join(import.meta.dirname, "onvif-dashboard.mjs");
let dir: string;
beforeEach(async () => {
// realpath because macOS temp folders are symlinks, and the CLI reports resolved paths.
dir = await realpath(await mkdtemp(path.join(os.tmpdir(), "cli-")));
});
afterEach(() => rm(dir, { recursive: true, force: true }));
const cli = (args: string[]) =>
run(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", CLI, ...args], {
cwd: dir,
env: {
...process.env,
XDG_CONFIG_HOME: path.join(dir, "config"),
CAMERAS_BIN_DIR: path.join(dir, "bin"),
CAMERAS_DATA_DIR: undefined,
},
}).catch((err: Error & { code?: number; stdout?: string; stderr?: string }) => ({
code: err.code,
stdout: err.stdout ?? "",
stderr: err.stderr ?? "",
}));
describe("onvif-dashboard", () => {
it("explains itself, and says so when the command is unknown", async () => {
expect((await cli(["help"])).stdout).toContain("onvif-dashboard setup");
const bad = (await cli(["wat"])) as { code?: number; stderr: string };
expect(bad.code).toBe(2);
expect(bad.stderr).toContain("Unknown command: wat");
});
it("sets a machine up without a terminal: default folder, a key, and what to do next", async () => {
const { stdout } = await cli(["setup"]);
const data = path.join(dir, ".data");
expect(stdout).toContain("No terminal to ask");
expect(stdout).toContain(`Data folder: ${data}`);
expect(stdout).toContain("install-video");
expect(stdout).toContain("No admin login yet");
expect(stdout).toContain("onvif-dashboard start");
const secrets = path.join(data, "secrets.env");
expect((await stat(secrets)).mode & 0o777).toBe(0o600);
expect(await readFile(secrets, "utf8")).toMatch(/CAMERA_CREDENTIALS_KEY=[0-9a-f]{64}/);
expect(JSON.parse(await readFile(path.join(dir, "config/onvif-dashboard/config.json"), "utf8"))).toEqual({
dataDir: data,
});
});
it("running it again changes nothing", async () => {
await cli(["setup"]);
const secrets = path.join(dir, ".data", "secrets.env");
const before = await readFile(secrets, "utf8");
const { stdout } = await cli(["setup"]);
expect(await readFile(secrets, "utf8")).toBe(before);
expect(stdout).not.toContain("Made a key");
});
});