tests/speakExternal.test.ts
Rama main · réplica de lectura del repositorio original.
// The external speaker (server/speakExternal.ts) with a FAKE program — a
// shell script that reads the sentence from stdin and writes a WAV to {out}
// — through each of its endings: it spoke; it exited non-zero; it ran past
// the time allowed; it exited cleanly and wrote nothing; it wrote something
// that is not sound. And that a sentence is never part of the command line.
import assert from "node:assert/strict";
import { chmodSync, existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
import path from "node:path";
import { after, before, describe, it } from "node:test";
import { toneWav } from "../server/speakEngine.ts";
import { ExternalSpeakerError, runExternal, soundMime } from "../server/speakExternal.ts";
import { makeDir, removeVault } from "./helpers/vault.ts";
const dir = makeDir();
const work = path.join(dir, "work");
const fake = path.join(dir, "fake speaker.sh"); // a space: the template quotes it
const tone = path.join(dir, "tone.wav");
const said = path.join(dir, "said.txt");
const posix = process.platform !== "win32";
before(() => {
writeFileSync(tone, toneWav("x"));
writeFileSync(
fake,
`#!/bin/sh
# fake speaker: $1 is what to do, $2 the language, $3 the file to write.
cat > "${said}"
echo "speaking $2" >&2
case "$1" in
ok) cp "${tone}" "$3" ;;
ogg) printf 'OggS\\0\\2rest' > "$3" ;;
fail) echo "no such voice" >&2; exit 3 ;;
sleep) sleep 5; cp "${tone}" "$3" ;;
nofile) exit 0 ;;
junk) echo "hello" > "$3" ;;
esac
`,
);
chmodSync(fake, 0o755);
});
after(() => removeVault(dir));
const run = (mode: string, text = "Bonjour tout le monde.", timeoutMs?: number) =>
runExternal({ command: `"${fake}" ${mode} {lang} {out}`, text, lang: "fr" }, { dir: work, timeoutMs });
async function fails(p: Promise<unknown>, reason: string): Promise<ExternalSpeakerError> {
try {
await p;
} catch (err) {
assert.ok(err instanceof ExternalSpeakerError, String(err));
assert.equal(err.reason, reason);
assert.equal(err.code, "speakExternal");
assert.equal(err.status, 502);
return err;
}
assert.fail(`expected a ${reason} failure`);
}
describe("the external speaker", { skip: posix ? false : "the fake program is a POSIX shell script" }, () => {
it("speaks: the sentence on stdin, {lang} and {out} substituted, the WAV read back and removed", async () => {
const out = await run("ok", "Le chat « dort » ; rm -rf / $(reboot)");
assert.equal(out.mime, "audio/wav");
assert.deepEqual(out.audio, new Uint8Array(readFileSync(tone)));
// The sentence arrived whole on stdin — never a word of the command line.
assert.equal(readFileSync(said, "utf8"), "Le chat « dort » ; rm -rf / $(reboot)");
assert.deepEqual(readdirSync(work), [], "the {out} file is removed");
});
it("an Ogg file is answered as Ogg", async () => {
assert.equal((await run("ogg")).mime, "audio/ogg");
});
it("a non-zero exit fails, naming the exit and the program's last words", async () => {
const err = await fails(run("fail"), "exit");
assert.match(err.message, /failed \(3\)/);
assert.match(err.message, /no such voice/);
});
it("a program past its time is ended and fails", async () => {
const started = Date.now();
await fails(run("sleep", "x", 300), "timeout");
assert.ok(Date.now() - started < 3000, "it does not wait for the program");
});
it("an exit with no file fails", async () => {
await fails(run("nofile"), "noFile");
assert.deepEqual(readdirSync(work), []);
});
it("a file that is not WAV or Ogg fails", async () => {
await fails(run("junk"), "notAudio");
assert.deepEqual(readdirSync(work), []);
});
it("a program that is not there fails to start", async () => {
await fails(runExternal({ command: `${path.join(dir, "nothing-here")} {out}`, text: "x", lang: "fr" }, { dir: work }), "spawn");
assert.equal(existsSync(path.join(dir, "nothing-here")), false);
});
it("knows sound by its first bytes", () => {
assert.equal(soundMime(toneWav("x")), "audio/wav");
assert.equal(soundMime(new TextEncoder().encode("OggS....")), "audio/ogg");
assert.equal(soundMime(new TextEncoder().encode("RIFF....AVI ")), null);
assert.equal(soundMime(new Uint8Array(0)), null);
});
});