/** * Reusable test doubles for the recording dependency-inversion seam. * * The {@link RecordingProcess} / {@link RecordingProcessFactory} seam in `ffmpeg/record.ts` exists so a consuming plugin's HKSV recording path can be driven without * spawning a real FFmpeg child. This module ships the fakes that cash that in: a configurable {@link TestRecordingProcess} that yields caller-supplied init and media * segments deterministically, and a {@link TestRecordingProcessFactory} that records every `create` call and hands back the process. Any HKSV-capable plugin can hold * the test factory in place of {@link ffmpeg/record!recordingProcessFactory | recordingProcessFactory} to exercise its recording delegate FFmpeg-free, in CI, with * no binary on the path. * * The double needs no real fMP4: a recording consumer forwards `segments()` output opaquely to HomeKit, so the segments are opaque `Buffer`s the test chooses. The * keyframe-bearing fMP4 concern (timeshift / prebuffer) lives entirely in a consumer's own segment double, not here. * * @module */ import type { FfmpegRecordingInit, RecordingProcess, RecordingProcessFactory } from "./record.ts"; import type { FfmpegOptions } from "./options.ts"; import type { Mp4Segment } from "./mp4-assembler.ts"; import { Writable } from "node:stream"; /** * Construction-time configuration for a {@link TestRecordingProcess}. Every field has a deterministic default so a bare `new TestRecordingProcess()` is usable; supply * only the fields a given test steers a branch with. * * @property bufferedSegments - The value the `bufferedSegments` getter reports. Defaults to `0`. * @property initSegment - The buffer `getInitSegment()` resolves with. Defaults to an empty buffer. * @property isTimedOut - The value the `isTimedOut` getter reports. Defaults to `false`. * @property segments - The media-segment buffers `segments()` yields, in order. Defaults to an empty array. * @property stderrLog - The lines the `stderrLog` getter reports. Defaults to an empty array. * * @category Testing */ export interface TestRecordingProcessInit { bufferedSegments?: number; initSegment?: Buffer; isTimedOut?: boolean; segments?: Buffer[]; stderrLog?: readonly string[]; } /** * A configurable, FFmpeg-free {@link RecordingProcess} fake. It satisfies the same interface the production * {@link ffmpeg/record!FfmpegRecordingProcess | FfmpegRecordingProcess} does, so a recording delegate constructs and drives it exactly as it would the real class - but * it spawns no child and yields caller-supplied bytes deterministically. * * Fidelity to the real contract: `abort()` aborts a genuine {@link AbortSignal} with a real {@link HbpuAbortError} reason (defaulting to `"shutdown"` exactly as * {@link ffmpeg/process!FfmpegProcess.abort | FfmpegProcess.abort} does), so a consumer's `isHbpuAbortReason` / timeout derivations stay meaningful; `getInitSegment()` * rejects with `signal.reason` after a pre-init abort, mirroring the real assembler's init-reject contract; and `segments()` terminates on EITHER its own signal or the * passed per-call signal, mirroring how * the real assembler composes the two. The `stdin` sink records every written chunk and stays writable across abort, so a consumer driving a `BackpressureWriter` over it * can assert the segment feed regardless of abort ordering. * * @see RecordingProcess * @see TestRecordingProcessFactory * * @category Testing */ export declare class TestRecordingProcess implements RecordingProcess { #private; readonly abortCalls: unknown[]; readonly stdinWrites: Buffer[]; readonly stdin: Writable; /** * Construct a configurable recording-process fake. * * @param init - Optional configuration. See {@link TestRecordingProcessInit}. Every field defaults, so a bare `new TestRecordingProcess()` is valid. */ constructor(init?: TestRecordingProcessInit); /** * Abort the recording process. Aborts the internal signal with the supplied reason, defaulting to a real `HbpuAbortError("shutdown")` exactly as the production * `FfmpegProcess.abort` does, and records the (defaulted) reason for assertions. Safe to call more than once: the underlying signal aborts only once. * * @param reason - Optional abort reason. Typically an {@link HbpuAbortError}. */ abort(reason?: unknown): void; /** * The configured buffered-segment depth. */ get bufferedSegments(): number; /** * Resolve with the configured init segment. Rejects with `signal.reason` if `abort()` fired before init was requested, mirroring the real * `FfmpegFMp4Process.getInitSegment` -> `Mp4SegmentAssembler.initSegment` reject-on-pre-init-abort contract. * * @returns A promise resolving to the configured init segment bytes. */ getInitSegment(): Promise; /** * The configured timed-out flag. */ get isTimedOut(): boolean; /** * Yield the configured media segments in order. Terminates (returns) when EITHER this process's own signal aborts OR the passed `init.signal` aborts - composing both, * mirroring how the real `Mp4SegmentAssembler.segments` honors the process signal and the per-call signal together. * * This double does NOT gate media delivery on init-first the way the real assembler does. The real HKSV consumer always awaits `getInitSegment()` before iterating, so * the simplification is invisible to it; a future `segments()`-first consumer must not assume an ordering this double does not enforce. * * @param init - Optional init options. `signal` composes with this process's own signal; aborting either terminates this generator call. * * @returns An async generator yielding the configured media segment buffers in order. */ segments(init?: { signal?: AbortSignal; }): AsyncGenerator; /** * The composed abort signal representing this process's lifetime. Aborts exactly once, when `abort()` is called; `signal.reason` carries the recorded reason. */ get signal(): AbortSignal; /** * The configured stderr lines. */ get stderrLog(): readonly string[]; /** * Yield the whole segment stream as a kind-tagged sequence: one {@link Mp4Segment} of kind `"init"` carrying the configured init segment, then one of kind `"media"` * per configured media segment, in order. Terminates on this process's own signal or the passed `init.signal` the same way {@link TestRecordingProcess.segments} does; * an abort before the init item is yielded ends the stream with nothing yielded, mirroring the real `Mp4SegmentAssembler.stream` return-on-pre-init-abort behavior. * * @param init - Optional init options. `signal` composes with this process's own signal; aborting either terminates this generator call. * * @returns An async generator yielding one `"init"` segment followed by the configured `"media"` segments in order. */ stream(init?: { signal?: AbortSignal; }): AsyncGenerator; } /** * A {@link RecordingProcessFactory} fake that records every `create` call (the options and init it was passed, for assertions) and returns a * {@link TestRecordingProcess}, mirroring the create-call-recording discipline a consumer's streaming-delegate factory double uses. By default it returns a fresh, * default-configured process per call; supply a process to the constructor to return a single pre-configured instance from every `create`. * * @see RecordingProcessFactory * @see TestRecordingProcess * * @category Testing */ export declare class TestRecordingProcessFactory implements RecordingProcessFactory { #private; readonly createCalls: { init: FfmpegRecordingInit; options: FfmpegOptions; process: TestRecordingProcess; }[]; /** * Construct a recording-process factory fake. * * @param process - Optional pre-configured {@link TestRecordingProcess} to return from every `create`. When omitted, each `create` returns a fresh, * default-configured process. */ constructor(process?: TestRecordingProcess); /** * Record the create call and return a {@link TestRecordingProcess} - the constructor-supplied instance when one was given, otherwise a fresh default-configured one. * * @param options - The {@link FfmpegOptions} the consumer passed. * @param init - The {@link FfmpegRecordingInit} the consumer passed. * * @returns The recording-process double. */ create(options: FfmpegOptions, init: FfmpegRecordingInit): RecordingProcess; } //# sourceMappingURL=recording-process-double.d.ts.map