test(39-02): add failing tests for voice capability detection
This commit is contained in:
parent
0ea8d35515
commit
cc05befdb0
1 changed files with 136 additions and 0 deletions
136
server/src/__tests__/39-voice-hardware-probe.test.ts
Normal file
136
server/src/__tests__/39-voice-hardware-probe.test.ts
Normal 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 });
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Add table
Reference in a new issue