Files
windmill/cli/test/dev_recorder_routes_unit.test.ts
Ruben Fiszel 2105540cca feat: add session recording to wmill app dev (#10457)
* feat: add session recording to wmill app dev

* fix: harden dev recorder shell, bundle staleness guard and save route

* fix: keep dev-server recordings out of the raw app sync diff

* style: drop em dashes from new cli comments

* fix: tighten dev recorder save route origin, naming and io

* test: pin that only the root recordings folder is skipped

* fix: survive an oversized recording upload and match paths on windows

* fix: keep the app at the root and settle runnables stranded by a reload

* fix: make the recorder bundle hash stable on a crlf checkout

* docs: state the preflight-free content type the origin check guards

* test: build the sync-skip fixture with the platform separator

* docs: align the origin-guard test comment with the code

* chore: mark generated .gen.ts files as generated
2026-08-02 22:54:37 +02:00

44 lines
1.7 KiB
TypeScript

/**
* Guards on the routes `wmill app dev --recording` adds: what may write a
* recording, what a recording may be named, and that two saves never collide.
*/
import { expect, test } from "bun:test";
import {
isOwnOrigin,
isRecordingFileName,
recordingFileName,
} from "../src/commands/app/devRecorder.ts";
test("only the shell's own origin may save a recording", () => {
expect(isOwnOrigin("http://localhost:4000", "localhost:4000")).toBe(true);
expect(isOwnOrigin("http://127.0.0.1:4000", "127.0.0.1:4000")).toBe(true);
// A cross-site POST carrying JSON under a simple content type needs no
// preflight, so a foreign origin sharing the port must still be refused.
expect(isOwnOrigin("http://attacker.example:4000", "localhost:4000")).toBe(
false,
);
expect(isOwnOrigin("null", "localhost:4000")).toBe(false);
// No Origin at all is a non-browser client, not a cross-site page.
expect(isOwnOrigin(undefined, "localhost:4000")).toBe(true);
});
test("recording names stay inside the recordings folder", () => {
expect(isRecordingFileName("recording-2026-01-01-00-00-00-000.json")).toBe(
true,
);
expect(isRecordingFileName("../../../etc/passwd")).toBe(false);
expect(isRecordingFileName("..%2Fx.json")).toBe(false);
expect(isRecordingFileName("sub/dir.json")).toBe(false);
expect(isRecordingFileName("recording.txt")).toBe(false);
});
test("two saves in the same millisecond get distinct names", () => {
const now = new Date("2026-01-01T00:00:00.123Z");
const first = recordingFileName(now, 0);
const second = recordingFileName(now, 1);
expect(first).toBe("recording-2026-01-01-00-00-00-123.json");
expect(second).not.toBe(first);
expect(isRecordingFileName(second)).toBe(true);
});