import { expect, test } from "bun:test";import { mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync,} from "node:fs";import { tmpdir } from "node:os";import { join } from "node:path";import { VoiceProfileError, VoiceStore, VOICE_CONSENT_VERSION } from "./voice-store"; const fixture = (): { directory: string; store: VoiceStore } => { const directory = mkdtempSync(join(tmpdir(), "local-studio-voice-")); return { directory, store: new VoiceStore(join(directory, "controller.db"), directory), };}; test("sweeps only owned orphan plaintext when the store starts", () => { const directory = mkdtempSync(join(tmpdir(), "local-studio-voice-sweep-")); const temporary = join(directory, "runtime", "speech", "tmp"); const orphan = "123e4567-e89b-42d3-a456-426614174000.wav"; try { mkdirSync(temporary, { recursive: true, mode: 0o755 }); writeFileSync(join(temporary, orphan), "private", { mode: 0o644 }); writeFileSync(join(temporary, "preserve.wav"), "unowned", { mode: 0o644 }); new VoiceStore(join(directory, "controller.db"), directory); expect(readdirSync(temporary)).toEqual(["preserve.wav"]); expect(statSync(temporary).mode & 0o777).toBe(0o700); } finally { rmSync(directory, { recursive: true, force: true }); }}); test("stores only opaque metadata and encrypted voice bytes", async () => { const { directory, store } = fixture(); const audio = Buffer.from("private-reference-audio"); try { const profile = await store.create({ name: "My voice", durationMs: 10_000, consent: VOICE_CONSENT_VERSION, audio, }); expect(profile.id).toMatch(/^voice_[a-f\d]{32}$/); expect(store.list()).toEqual([profile]); expect(store.consentRecord(profile.id)?.consent_version).toBe(VOICE_CONSENT_VERSION); const database = readFileSync(join(directory, "controller.db")); const vaultDirectory = join(directory, "speech", "vault"); const encrypted = readFileSync(join(vaultDirectory, "profiles", `${profile.id}.bin`)); expect(database.includes(audio)).toBe(false); expect(encrypted.includes(audio)).toBe(false); expect(readFileSync(join(vaultDirectory, "master.key"))).toHaveLength(32); } finally { rmSync(directory, { recursive: true, force: true }); }}); test("decrypts into a short-lived controller path and deletes profiles", async () => { const { directory, store } = fixture(); try { const audio = Buffer.from("normalized-wave"); const profile = await store.create({ name: "Local voice", durationMs: 9_000, consent: VOICE_CONSENT_VERSION, audio, }); let plaintextPath = ""; const value = await store.withPlaintext(profile.id, async (path) => { plaintextPath = path; expect(readFileSync(path)).toEqual(audio); return "used"; }); expect(value).toBe("used"); expect(readdirSync(join(directory, "runtime", "speech", "tmp"))).toEqual([]); expect(await store.delete(profile.id)).toBe(true); expect(store.get(profile.id)).toBeNull(); expect(() => readFileSync(plaintextPath)).toThrow(); await expect(store.withPlaintext(profile.id, async () => undefined)).rejects.toMatchObject({ code: "voice_not_found", }); } finally { rmSync(directory, { recursive: true, force: true }); }}); test("rejects missing consent and authenticated ciphertext changes", async () => { const { directory, store } = fixture(); try { await expect( store.create({ name: "No consent", durationMs: 10_000, consent: "", audio: Buffer.alloc(32), }), ).rejects.toBeInstanceOf(VoiceProfileError); await expect( store.create({ name: "Too short", durationMs: 5_999, consent: VOICE_CONSENT_VERSION, audio: Buffer.alloc(32), }), ).rejects.toMatchObject({ code: "voice_duration_invalid" }); const profile = await store.create({ name: "Tamper test", durationMs: 10_000, consent: VOICE_CONSENT_VERSION, audio: Buffer.alloc(32, 7), }); const blobPath = join(directory, "speech", "vault", "profiles", `${profile.id}.bin`); const blob = readFileSync(blobPath); blob[blob.length - 1] = (blob[blob.length - 1] ?? 0) ^ 1; writeFileSync(blobPath, blob); await expect(store.withPlaintext(profile.id, async () => undefined)).rejects.toThrow(); } finally { rmSync(directory, { recursive: true, force: true }); }});
import { expect, test } from "bun:test";
import {
mkdirSync,
mkdtempSync,
readFileSync,
readdirSync,
rmSync,
statSync,
writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { VoiceProfileError, VoiceStore, VOICE_CONSENT_VERSION } from "./voice-store";
const fixture = (): { directory: string; store: VoiceStore } => {
const directory = mkdtempSync(join(tmpdir(), "local-studio-voice-"));
return {
directory,
store: new VoiceStore(join(directory, "controller.db"), directory),
};
};
test("sweeps only owned orphan plaintext when the store starts", () => {
const directory = mkdtempSync(join(tmpdir(), "local-studio-voice-sweep-"));
const temporary = join(directory, "runtime", "speech", "tmp");
const orphan = "123e4567-e89b-42d3-a456-426614174000.wav";
try {
mkdirSync(temporary, { recursive: true, mode: 0o755 });
writeFileSync(join(temporary, orphan), "private", { mode: 0o644 });
writeFileSync(join(temporary, "preserve.wav"), "unowned", { mode: 0o644 });
new VoiceStore(join(directory, "controller.db"), directory);
expect(readdirSync(temporary)).toEqual(["preserve.wav"]);
expect(statSync(temporary).mode & 0o777).toBe(0o700);
} finally {
rmSync(directory, { recursive: true, force: true });
}
});
test("stores only opaque metadata and encrypted voice bytes", async () => {
const { directory, store } = fixture();
const audio = Buffer.from("private-reference-audio");
try {
const profile = await store.create({
name: "My voice",
durationMs: 10_000,
consent: VOICE_CONSENT_VERSION,
audio,
});
expect(profile.id).toMatch(/^voice_[a-f\d]{32}$/);
expect(store.list()).toEqual([profile]);
expect(store.consentRecord(profile.id)?.consent_version).toBe(VOICE_CONSENT_VERSION);
const database = readFileSync(join(directory, "controller.db"));
const vaultDirectory = join(directory, "speech", "vault");
const encrypted = readFileSync(join(vaultDirectory, "profiles", `${profile.id}.bin`));
expect(database.includes(audio)).toBe(false);
expect(encrypted.includes(audio)).toBe(false);
expect(readFileSync(join(vaultDirectory, "master.key"))).toHaveLength(32);
} finally {
rmSync(directory, { recursive: true, force: true });
}
});
test("decrypts into a short-lived controller path and deletes profiles", async () => {
const { directory, store } = fixture();
try {
const audio = Buffer.from("normalized-wave");
const profile = await store.create({
name: "Local voice",
durationMs: 9_000,
consent: VOICE_CONSENT_VERSION,
audio,
});
let plaintextPath = "";
const value = await store.withPlaintext(profile.id, async (path) => {
plaintextPath = path;
expect(readFileSync(path)).toEqual(audio);
return "used";
});
expect(value).toBe("used");
expect(readdirSync(join(directory, "runtime", "speech", "tmp"))).toEqual([]);
expect(await store.delete(profile.id)).toBe(true);
expect(store.get(profile.id)).toBeNull();
expect(() => readFileSync(plaintextPath)).toThrow();
await expect(store.withPlaintext(profile.id, async () => undefined)).rejects.toMatchObject({
code: "voice_not_found",
});
} finally {
rmSync(directory, { recursive: true, force: true });
}
});
test("rejects missing consent and authenticated ciphertext changes", async () => {
const { directory, store } = fixture();
try {
await expect(
store.create({
name: "No consent",
durationMs: 10_000,
consent: "",
audio: Buffer.alloc(32),
}),
).rejects.toBeInstanceOf(VoiceProfileError);
await expect(
store.create({
name: "Too short",
durationMs: 5_999,
consent: VOICE_CONSENT_VERSION,
audio: Buffer.alloc(32),
}),
).rejects.toMatchObject({ code: "voice_duration_invalid" });
const profile = await store.create({
name: "Tamper test",
durationMs: 10_000,
consent: VOICE_CONSENT_VERSION,
audio: Buffer.alloc(32, 7),
});
const blobPath = join(directory, "speech", "vault", "profiles", `${profile.id}.bin`);
const blob = readFileSync(blobPath);
blob[blob.length - 1] = (blob[blob.length - 1] ?? 0) ^ 1;
writeFileSync(blobPath, blob);
await expect(store.withPlaintext(profile.id, async () => undefined)).rejects.toThrow();
} finally {
rmSync(directory, { recursive: true, force: true });
}
});