mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
test: coverage campaign and mutation testing across five packages (#628)
Coverage 83.6 to 87.36% lines, 81.63 to 84.14% branches. Mutation testing across five packages: image-engine 85, media-engine 92, doc-engine 87, shared+enterprise 86, apps/api security and jobs slice. Runs all five lanes weekly. Fixes the silently-broken mutation CI (babel pin), a redact-pdf envelope-shape test bug, an untested enterprise license valid-signature path, and an audit test that only exercised a hand-copied reproduction. Test and config only, no product code changes beyond the babel pin and one test-only oidc export. Full suite: 16,712 pass, 0 fail.
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
import { EventEmitter } from "node:events";
|
||||
|
||||
/**
|
||||
* A stand-in for a spawned ChildProcess. stdout/stderr are EventEmitters and the
|
||||
* process itself is an EventEmitter, matching how the doc-engine wrappers wire up
|
||||
* `child.stdout.on("data")`, `child.stderr.on("data")`, `child.on("close")`, and
|
||||
* `child.on("error")`.
|
||||
*/
|
||||
export interface FakeChild extends EventEmitter {
|
||||
stdout: EventEmitter;
|
||||
stderr: EventEmitter;
|
||||
kill: (signal?: string) => boolean;
|
||||
killed: boolean;
|
||||
killSignals: string[];
|
||||
}
|
||||
|
||||
export function createFakeChild(): FakeChild {
|
||||
const proc = new EventEmitter() as FakeChild;
|
||||
proc.stdout = new EventEmitter();
|
||||
proc.stderr = new EventEmitter();
|
||||
proc.killed = false;
|
||||
proc.killSignals = [];
|
||||
proc.kill = (signal?: string) => {
|
||||
proc.killed = true;
|
||||
proc.killSignals.push(signal ?? "SIGTERM");
|
||||
return true;
|
||||
};
|
||||
return proc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit stdout chunks then close with the given exit code on the next microtask,
|
||||
* so the wrapper's Promise settles after the caller has awaited it.
|
||||
*/
|
||||
export function settleClose(
|
||||
child: FakeChild,
|
||||
opts: { stdout?: string; stderr?: string; code?: number | null; signal?: string | null } = {},
|
||||
): void {
|
||||
queueMicrotask(() => {
|
||||
if (opts.stdout !== undefined) child.stdout.emit("data", Buffer.from(opts.stdout, "utf8"));
|
||||
if (opts.stderr !== undefined) child.stderr.emit("data", Buffer.from(opts.stderr, "utf8"));
|
||||
// Preserve an explicit `code: null` (signal-only exit) instead of coercing it to 0.
|
||||
const code = "code" in opts ? (opts.code ?? null) : 0;
|
||||
child.emit("close", code, opts.signal ?? null);
|
||||
});
|
||||
}
|
||||
|
||||
/** Emit an "error" event (spawn ENOENT style) on the next microtask. */
|
||||
export function settleError(child: FakeChild, err: Error): void {
|
||||
queueMicrotask(() => {
|
||||
child.emit("error", err);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import type { spawn } from "node:child_process";
|
||||
import type { Mock } from "vitest";
|
||||
import { createFakeChild, type FakeChild, settleClose, settleError } from "./fake-child.js";
|
||||
|
||||
/**
|
||||
* Helpers over a mocked `spawn` for the doc-engine CLI wrappers. All of them
|
||||
* spawn(bin, args, opts), pipe stdout/stderr, and settle on close/error, so a
|
||||
* single set of helpers drives every wrapper's success and failure paths and
|
||||
* exposes the exact argv for assertion.
|
||||
*/
|
||||
export function makeSpawnHelpers(mockSpawn: Mock<typeof spawn>) {
|
||||
/** Program the next spawn to close with the given code/output. */
|
||||
function nextClose(
|
||||
opts: { stdout?: string; stderr?: string; code?: number | null; signal?: string | null } = {},
|
||||
): void {
|
||||
mockSpawn.mockImplementationOnce(() => {
|
||||
const child = createFakeChild();
|
||||
settleClose(child, opts);
|
||||
return child as never;
|
||||
});
|
||||
}
|
||||
|
||||
/** Program the next spawn to emit an error event. */
|
||||
function nextError(err: Error): void {
|
||||
mockSpawn.mockImplementationOnce(() => {
|
||||
const child = createFakeChild();
|
||||
settleError(child, err);
|
||||
return child as never;
|
||||
});
|
||||
}
|
||||
|
||||
/** Program the next spawn to hand back a child that never settles on its own. */
|
||||
function nextManual(): FakeChild {
|
||||
const child = createFakeChild();
|
||||
mockSpawn.mockImplementationOnce(() => child as never);
|
||||
return child;
|
||||
}
|
||||
|
||||
function lastCall(): [string, string[], Record<string, unknown>] {
|
||||
const calls = mockSpawn.mock.calls;
|
||||
const c = calls[calls.length - 1];
|
||||
return [c[0] as string, c[1] as string[], c[2] as Record<string, unknown>];
|
||||
}
|
||||
function lastBin(): string {
|
||||
return lastCall()[0];
|
||||
}
|
||||
function lastArgs(): string[] {
|
||||
return lastCall()[1];
|
||||
}
|
||||
function lastOpts(): Record<string, unknown> {
|
||||
return lastCall()[2];
|
||||
}
|
||||
|
||||
return { nextClose, nextError, nextManual, lastCall, lastBin, lastArgs, lastOpts };
|
||||
}
|
||||
Reference in New Issue
Block a user