Files
Recordly/electron/ipc/recording/windowsFinalizer.test.ts
webadderall eaf87be95b Recordly 2.0 local review stack 2/6
Dependency-ordered review preparation. Not pushed or remotely reviewed.
2026-11-06 05:24:21 +11:00

110 lines
3.8 KiB
TypeScript

import { EventEmitter } from "node:events";
import { describe, expect, it, vi } from "vitest";
import { RecordingOwnerRecovery } from "./ownerRecovery";
import { WindowsRecordingFinalizer } from "./windowsFinalizer";
const drain = async () => {
for (let i = 0; i < 30; i++) await Promise.resolve();
};
describe("Windows recording finalization ownership", () => {
it.each([
"crash",
"remount",
])("joins in-flight mux work after %s and waits for metadata cleanup", async (loss) => {
let pendingVideo = false;
let audioPath: string | null = "old.wav";
let release!: () => void;
const gate = new Promise<void>((resolve) => {
release = resolve;
});
const perform = vi.fn(async () => {
pendingVideo = false;
await gate;
audioPath = null;
return { success: true, path: "/old.mp4" };
});
const mux = new WindowsRecordingFinalizer(() => pendingVideo, perform);
const report = vi.fn(async () => {});
const owner = Object.assign(new EventEmitter(), { id: 1 }) as unknown as Pick<
Electron.WebContents,
"id" | "on" | "removeListener"
>;
const recovery = new RecordingOwnerRecovery({
stop: async () => {
pendingVideo = true;
return { success: true, path: "/old.mp4" };
},
needsFinish: () => mux.needsFinish,
finish: () => mux.run(),
discard: async () => {},
reset: () => {},
report,
});
await recovery.start(owner, async () => ({ success: true }));
recovery.setUserRecording(true);
await recovery.stop();
recovery.setUserRecording(false);
const first = mux.run();
await drain();
expect(pendingVideo).toBe(false);
expect(mux.needsFinish).toBe(true);
expect(mux.run()).toBe(first);
owner.on("render-process-gone", () => {});
if (loss === "crash") (owner as unknown as EventEmitter).emit("render-process-gone");
else void recovery.recoverOwner(owner.id);
await drain();
const startNew = vi.fn(async () => {
audioPath = "new.wav";
return { success: true };
});
const next = recovery.start(owner, startNew);
await drain();
expect(startNew).not.toHaveBeenCalled();
expect(report).not.toHaveBeenCalled();
release();
await first;
await drain();
expect(perform).toHaveBeenCalledTimes(1);
expect(report).toHaveBeenCalledOnce();
expect(await next).toEqual({ success: true });
await drain();
expect(audioPath).toBe("new.wav");
});
it("releases an invalid or rejected recovery reservation without running mux", async () => {
const perform = vi.fn(async () => ({ success: true }));
const finalizer = new WindowsRecordingFinalizer(() => false, perform);
expect((await finalizer.recover(async () => null)).success).toBe(false);
expect(finalizer.needsFinish).toBe(false);
await expect(
finalizer.recover(async () => {
throw new Error("validation failed");
}),
).rejects.toThrow("validation failed");
expect(finalizer.needsFinish).toBe(false);
expect(perform).not.toHaveBeenCalled();
});
it("does not run metadata cleanup for a stale mux request without a stopped video", async () => {
const perform = vi.fn(async () => ({ success: true }));
const mux = new WindowsRecordingFinalizer(() => false, perform);
expect((await mux.run()).success).toBe(false);
expect(perform).not.toHaveBeenCalled();
});
it("keeps a failed mux reservation until its operation has settled, then permits retry", async () => {
let release!: (value: { success: boolean }) => void;
let pending = true;
const perform = vi.fn(() => {
pending = false;
return new Promise<{ success: boolean }>((resolve) => {
release = resolve;
});
});
const mux = new WindowsRecordingFinalizer(() => pending, perform);
const result = mux.run();
await drain();
expect(mux.needsFinish).toBe(true);
release({ success: false });
expect(await result).toEqual({ success: false });
await drain();
expect(mux.needsFinish).toBe(false);
});
});