Files
2026-07-13 13:39:12 +08:00

172 lines
6.8 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import { Buffer } from "node:buffer";
import {
pcmToWav,
vertexGenerateSpeech,
vertexTranscribe,
vertexGenerateMusic,
vertexGenerateVideo,
} from "../../open-sse/executors/vertexMedia.ts";
// Service Account credential with a pre-set accessToken so resolveVertexAuth never
// performs a real OAuth token exchange (getAccessToken is skipped when accessToken is present).
function saCredentials(region = "us-central1") {
return {
apiKey: JSON.stringify({ project_id: "proj-test", client_email: "svc@x.iam", private_key: "x" }),
accessToken: "test-bearer-token",
providerSpecificData: { region },
};
}
function expressCredentials() {
return { apiKey: "express-key-abc", accessToken: null, providerSpecificData: {} };
}
interface FetchCall {
url: string;
init: any;
}
function installFetch(responders: Array<(call: FetchCall) => unknown>) {
const calls: FetchCall[] = [];
let i = 0;
(globalThis as any).fetch = async (url: string, init: any) => {
const call = { url: String(url), init };
calls.push(call);
const payload = responders[Math.min(i, responders.length - 1)](call);
i += 1;
return {
ok: true,
status: 200,
json: async () => payload,
text: async () => JSON.stringify(payload),
};
};
return calls;
}
test("pcmToWav writes a valid RIFF/WAVE header with correct sizes", () => {
const pcm = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]);
const wav = pcmToWav(pcm, 24000);
assert.equal(wav.subarray(0, 4).toString("ascii"), "RIFF");
assert.equal(wav.subarray(8, 12).toString("ascii"), "WAVE");
assert.equal(wav.readUInt32LE(4), 36 + pcm.length); // RIFF chunk size
assert.equal(wav.readUInt32LE(24), 24000); // sample rate
assert.equal(wav.readUInt32LE(40), pcm.length); // data chunk size
assert.equal(wav.length, 44 + pcm.length);
});
test("vertexGenerateSpeech posts generateContent with AUDIO modality and returns WAV", async () => {
const pcmB64 = Buffer.from([10, 20, 30, 40]).toString("base64");
const calls = installFetch([
() => ({
candidates: [
{ content: { parts: [{ inlineData: { data: pcmB64, mimeType: "audio/L16;codec=pcm;rate=24000" } }] } },
],
}),
]);
const { audio, contentType } = await vertexGenerateSpeech(saCredentials("europe-west4"), {
model: "gemini-2.5-flash-preview-tts",
input: "Hello world",
voice: "Puck",
});
assert.equal(contentType, "audio/wav");
assert.equal(audio.subarray(0, 4).toString("ascii"), "RIFF");
assert.equal(
calls[0].url,
"https://europe-west4-aiplatform.googleapis.com/v1/projects/proj-test/locations/europe-west4/publishers/google/models/gemini-2.5-flash-preview-tts:generateContent"
);
const body = JSON.parse(calls[0].init.body);
assert.deepEqual(body.generationConfig.responseModalities, ["AUDIO"]);
assert.equal(body.generationConfig.speechConfig.voiceConfig.prebuiltVoiceConfig.voiceName, "Puck");
assert.equal(calls[0].init.headers.Authorization, "Bearer test-bearer-token");
});
test("vertexGenerateSpeech defaults the voice to Kore", async () => {
const pcmB64 = Buffer.from([1, 2]).toString("base64");
const calls = installFetch([
() => ({ candidates: [{ content: { parts: [{ inlineData: { data: pcmB64, mimeType: "audio/L16;rate=16000" } }] } }] }),
]);
await vertexGenerateSpeech(saCredentials(), { model: "gemini-2.5-flash-preview-tts", input: "hi" });
const body = JSON.parse(calls[0].init.body);
assert.equal(body.generationConfig.speechConfig.voiceConfig.prebuiltVoiceConfig.voiceName, "Kore");
});
test("vertexTranscribe posts audio inlineData and returns the joined text", async () => {
const calls = installFetch([
() => ({ candidates: [{ content: { parts: [{ text: "the quick brown fox" }] } }] }),
]);
const text = await vertexTranscribe(saCredentials(), {
model: "gemini-2.5-flash",
audioBase64: "QUJD",
mimeType: "audio/mpeg",
prompt: "Transcribe please",
});
assert.equal(text, "the quick brown fox");
const body = JSON.parse(calls[0].init.body);
const parts = body.contents[0].parts;
assert.equal(parts[0].text, "Transcribe please");
assert.equal(parts[1].inlineData.mimeType, "audio/mpeg");
assert.equal(parts[1].inlineData.data, "QUJD");
assert.ok(calls[0].url.endsWith("/gemini-2.5-flash:generateContent"));
});
test("vertexGenerateMusic posts predict to lyria and returns base64 WAV", async () => {
const calls = installFetch([() => ({ predictions: [{ bytesBase64Encoded: "TY9MUA==" }] })]);
const { base64, format } = await vertexGenerateMusic(saCredentials(), {
model: "lyria-002",
prompt: "relaxing sax",
});
assert.equal(base64, "TY9MUA==");
assert.equal(format, "wav");
assert.ok(calls[0].url.endsWith("/lyria-002:predict"));
const body = JSON.parse(calls[0].init.body);
assert.equal(body.instances[0].prompt, "relaxing sax");
});
test("vertexGenerateVideo submits predictLongRunning then polls fetchPredictOperation", async () => {
const calls = installFetch([
() => ({ name: "projects/proj-test/.../operations/op-1" }), // submit
() => ({ name: "projects/proj-test/.../operations/op-1" }), // poll #1 (not done)
() => ({
done: true,
response: { videos: [{ bytesBase64Encoded: "TVA0VklERU8=" }] },
}), // poll #2 (done)
]);
const result = await vertexGenerateVideo(saCredentials(), {
model: "veo-3.0-fast-generate-001",
prompt: "a cat playing piano",
aspectRatio: "16:9",
durationSeconds: 4,
pollIntervalMs: 1,
maxWaitMs: 5000,
});
assert.equal(result.base64, "TVA0VklERU8=");
assert.equal(result.format, "mp4");
assert.ok(calls[0].url.endsWith("/veo-3.0-fast-generate-001:predictLongRunning"));
assert.ok(calls[1].url.endsWith("/veo-3.0-fast-generate-001:fetchPredictOperation"));
const submitBody = JSON.parse(calls[0].init.body);
assert.equal(submitBody.parameters.aspectRatio, "16:9");
assert.equal(submitBody.parameters.durationSeconds, 4);
assert.equal(submitBody.instances[0].prompt, "a cat playing piano");
});
test("Express API key uses the project-less publisher endpoint with ?key=", async () => {
const pcmB64 = Buffer.from([1]).toString("base64");
const calls = installFetch([
() => ({ candidates: [{ content: { parts: [{ inlineData: { data: pcmB64, mimeType: "audio/L16;rate=24000" } }] } }] }),
]);
await vertexGenerateSpeech(expressCredentials(), { model: "gemini-2.5-flash-preview-tts", input: "hi" });
assert.equal(
calls[0].url,
"https://aiplatform.googleapis.com/v1/publishers/google/models/gemini-2.5-flash-preview-tts:generateContent?key=express-key-abc"
);
assert.equal(calls[0].init.headers.Authorization, undefined);
});