130 lines · 4.4 KB · TypeScript
RawDownload
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 });  }});