mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 23:00:03 +00:00
fix(cli): classify missing whisper-cpp as a setup gap, not a command error (#1628)
transcribe hard-failed with cli_error whenever whisper-cpp was absent. On Linux/Docker/CI (no Homebrew, no compiler toolchain) that is unavoidable, so it drove ~30k cli_error/day that are really "install the prerequisite" rather than bugs — and buried genuine transcription failures in the command-error budget. ensureWhisper now throws a typed WhisperUnavailableError when no binary exists and none can be built. The transcribe command reports that on a dedicated transcribe_unavailable metric instead of cli_error, and a new --optional flag lets pipelines skip captions and exit 0. Real transcription crashes still fail as cli_error. init and the skill pipelines already continue without captions. Also removes a stale doc reference to a `transcribe --provider groq` flag that does not exist.
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { writeFileSync, mkdtempSync, rmSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import { WhisperUnavailableError } from "../whisper/manager.js";
|
||||
|
||||
// Make the whisper core report "unavailable" so we exercise the soft-skip path.
|
||||
const transcribeMock = vi.fn();
|
||||
vi.mock("../whisper/transcribe.js", () => ({ transcribe: transcribeMock }));
|
||||
|
||||
const trackTranscribeUnavailable = vi.fn();
|
||||
const trackCommandFailure = vi.fn();
|
||||
vi.mock("../telemetry/events.js", () => ({
|
||||
trackTranscribeUnavailable: (...a: unknown[]) => trackTranscribeUnavailable(...a),
|
||||
trackCommandFailure: (...a: unknown[]) => trackCommandFailure(...a),
|
||||
}));
|
||||
|
||||
import transcribeCmd from "./transcribe.js";
|
||||
|
||||
function dummyAudio(): { dir: string; input: string } {
|
||||
const dir = mkdtempSync(join(tmpdir(), "hf-transcribe-test-"));
|
||||
const input = join(dir, "narration.wav");
|
||||
writeFileSync(input, "not-real-audio");
|
||||
return { dir, input };
|
||||
}
|
||||
|
||||
describe("transcribe — whisper unavailable", () => {
|
||||
let dirs: string[] = [];
|
||||
let priorExitCode: typeof process.exitCode;
|
||||
|
||||
beforeEach(() => {
|
||||
dirs = [];
|
||||
priorExitCode = process.exitCode;
|
||||
process.exitCode = undefined;
|
||||
transcribeMock.mockReset();
|
||||
trackTranscribeUnavailable.mockReset();
|
||||
trackCommandFailure.mockReset();
|
||||
transcribeMock.mockRejectedValue(
|
||||
new WhisperUnavailableError("whisper-cpp not found. Install: brew install whisper-cpp"),
|
||||
);
|
||||
vi.spyOn(console, "log").mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.exitCode = priorExitCode;
|
||||
for (const d of dirs) rmSync(d, { recursive: true, force: true });
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("explicit run exits non-zero and is NOT reported as a command failure", async () => {
|
||||
const { dir, input } = dummyAudio();
|
||||
dirs.push(dir);
|
||||
await transcribeCmd.run!({ args: { input, json: true, optional: false } } as never);
|
||||
|
||||
expect(process.exitCode).toBe(1);
|
||||
expect(trackTranscribeUnavailable).toHaveBeenCalledWith({ optional: false });
|
||||
expect(trackCommandFailure).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("--optional skips cleanly with exit 0", async () => {
|
||||
const { dir, input } = dummyAudio();
|
||||
dirs.push(dir);
|
||||
await transcribeCmd.run!({ args: { input, json: true, optional: true } } as never);
|
||||
|
||||
expect(process.exitCode).toBe(0);
|
||||
expect(trackTranscribeUnavailable).toHaveBeenCalledWith({ optional: true });
|
||||
expect(trackCommandFailure).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user