test(39-02): add failing tests for voice capability detection

This commit is contained in:
Nexus Dev 2026-04-04 03:30:39 +00:00
parent de544a6dde
commit 0f46d9b3bd

View file

@ -0,0 +1,136 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
// Mock systeminformation before any imports that use it
vi.mock("systeminformation", () => ({
default: {
graphics: vi.fn().mockResolvedValue({ controllers: [] }),
},
}));
// Mock child_process execFile before importing hardware service
vi.mock("node:child_process", () => {
const execFileCb = vi.fn();
return { execFile: execFileCb };
});
// Mock node:os so we can control RAM/CPU model
vi.mock("node:os", () => {
const osActual = {
totalmem: vi.fn().mockReturnValue(16 * 1024 * 1024 * 1024),
freemem: vi.fn().mockReturnValue(8 * 1024 * 1024 * 1024),
cpus: vi.fn().mockReturnValue([{ model: "Intel Core i9" }]),
};
return { default: osActual, ...osActual };
});
import { execFile } from "node:child_process";
import os from "node:os";
import si from "systeminformation";
import { hardwareService, _resetHardwareCache } from "../services/hardware.js";
// Helper: make execFile resolve for specific commands
function mockExecFilePartial(resolveFor: string[]) {
(execFile as ReturnType<typeof vi.fn>).mockImplementation(
(c: string, _args: string[], _opts: unknown, cb: (err: null | Error, stdout: string, stderr: string) => void) => {
if (resolveFor.includes(c)) {
cb(null, "version output", "");
} else {
const err = Object.assign(new Error("ENOENT"), { code: "ENOENT" });
cb(err as Error, "", "");
}
}
);
}
// Helper: make execFile reject with ENOENT for all
function mockExecFileAllFail() {
(execFile as ReturnType<typeof vi.fn>).mockImplementation(
(_c: string, _args: string[], _opts: unknown, cb: (err: Error, stdout: string, stderr: string) => void) => {
const err = Object.assign(new Error("ENOENT"), { code: "ENOENT" });
cb(err, "", "");
}
);
}
describe("voice capability detection", () => {
beforeEach(() => {
vi.resetAllMocks();
// Re-stub si.graphics after resetAllMocks clears the mock
vi.mocked(si.graphics).mockResolvedValue({ controllers: [] } as Awaited<ReturnType<typeof si.graphics>>);
// Restore default os mocks
vi.mocked(os.totalmem).mockReturnValue(16 * 1024 * 1024 * 1024);
vi.mocked(os.freemem).mockReturnValue(8 * 1024 * 1024 * 1024);
vi.mocked(os.cpus).mockReturnValue([{ model: "Intel Core i9" } as ReturnType<typeof os.cpus>[0]]);
_resetHardwareCache();
});
it("returns whisperAvailable=true, piperAvailable=true when both binaries resolve", async () => {
mockExecFilePartial(["whisper-cpp", "piper"]);
const svc = hardwareService();
const info = await svc.detect();
expect(info.voiceCapability.whisperAvailable).toBe(true);
expect(info.voiceCapability.piperAvailable).toBe(true);
});
it("returns whisperAvailable=false, piperAvailable=false when both binaries throw ENOENT", async () => {
mockExecFileAllFail();
const svc = hardwareService();
const info = await svc.detect();
expect(info.voiceCapability.whisperAvailable).toBe(false);
expect(info.voiceCapability.piperAvailable).toBe(false);
});
it("returns whisperAvailable=true, piperAvailable=false when only whisper-cpp is found", async () => {
mockExecFilePartial(["whisper-cpp"]);
const svc = hardwareService();
const info = await svc.detect();
expect(info.voiceCapability.whisperAvailable).toBe(true);
expect(info.voiceCapability.piperAvailable).toBe(false);
});
it("returns whisperAvailable=true when whisper (fallback) resolves but whisper-cpp fails", async () => {
mockExecFilePartial(["whisper"]);
const svc = hardwareService();
const info = await svc.detect();
expect(info.voiceCapability.whisperAvailable).toBe(true);
});
it("sets voiceTierSufficient=false for cpu_only tier with < 4GB free RAM", async () => {
mockExecFileAllFail();
vi.mocked(os.freemem).mockReturnValue(2 * 1024 * 1024 * 1024); // 2GB free
vi.mocked(os.totalmem).mockReturnValue(8 * 1024 * 1024 * 1024);
vi.mocked(os.cpus).mockReturnValue([{ model: "Intel Core i5" } as ReturnType<typeof os.cpus>[0]]);
// linux = non-Apple, cpu_only path
const savedPlatform = process.platform;
Object.defineProperty(process, "platform", { value: "linux", configurable: true });
_resetHardwareCache();
const svc = hardwareService();
const info = await svc.detect();
expect(info.hardwareTier).toBe("cpu_only");
expect(info.voiceCapability.voiceTierSufficient).toBe(false);
Object.defineProperty(process, "platform", { value: savedPlatform, configurable: true });
});
it("sets voiceTierSufficient=true for apple_silicon tier", async () => {
mockExecFileAllFail();
vi.mocked(os.totalmem).mockReturnValue(16 * 1024 * 1024 * 1024);
vi.mocked(os.freemem).mockReturnValue(8 * 1024 * 1024 * 1024);
vi.mocked(os.cpus).mockReturnValue([{ model: "Apple M4" } as ReturnType<typeof os.cpus>[0]]);
const savedPlatform = process.platform;
Object.defineProperty(process, "platform", { value: "darwin", configurable: true });
_resetHardwareCache();
const svc = hardwareService();
const info = await svc.detect();
expect(info.hardwareTier).toBe("apple_silicon");
expect(info.voiceCapability.voiceTierSufficient).toBe(true);
Object.defineProperty(process, "platform", { value: savedPlatform, configurable: true });
});
});