Files
remoteconn-gitea/apps/miniprogram/utils/ttsGateway.test.ts
2026-03-21 18:57:10 +08:00

360 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
function clearModuleCache() {
["./ttsGateway.js", "./syncAuth.js", "./opsConfig.js"].forEach((modulePath) => {
delete require.cache[require.resolve(modulePath)];
});
}
describe("ttsGateway", () => {
beforeEach(() => {
vi.restoreAllMocks();
clearModuleCache();
});
afterEach(() => {
clearModuleCache();
delete (global as typeof globalThis & { wx?: unknown }).wx;
delete (globalThis as typeof globalThis & { __REMOTE_CONN_OPS__?: unknown }).__REMOTE_CONN_OPS__;
});
it("应使用同步 token 请求 TTS synthesize 接口", async () => {
const downloadFile = vi.fn(
(options?: { success?: (value: { statusCode: number }) => void; filePath?: string }) => {
options?.success?.({
statusCode: 200
});
}
);
const wxRuntime = {
env: {
USER_DATA_PATH: "/userdata"
},
getStorageSync: vi.fn(),
setStorageSync: vi.fn(),
getFileSystemManager: vi.fn(() => ({
access: vi.fn((options?: { fail?: () => void }) => {
options?.fail?.();
})
})),
login: vi.fn((options?: { success?: (value: { code: string }) => void }) => {
options?.success?.({ code: "mock-code" });
}),
downloadFile,
request: vi.fn(
(options?: {
url?: string;
method?: string;
header?: Record<string, string>;
timeout?: number;
success?: (value: { statusCode: number; data: Record<string, unknown> }) => void;
}) => {
if (String(options?.url).endsWith("/api/miniprogram/auth/login")) {
options?.success?.({
statusCode: 200,
data: {
ok: true,
token: "sync-token",
expiresAt: "2099-01-01T00:00:00.000Z"
}
});
return;
}
options?.success?.({
statusCode: 200,
data: {
ok: true,
cacheKey: "cache-1",
cached: true,
audioUrl: "https://gateway.example.com/api/miniprogram/tts/audio/cache-1?ticket=demo",
expiresAt: "2099-01-01T00:00:00.000Z"
}
});
}
)
};
(global as typeof globalThis & { wx: typeof wxRuntime }).wx = wxRuntime;
(globalThis as typeof globalThis & { __REMOTE_CONN_OPS__?: unknown }).__REMOTE_CONN_OPS__ = {
gatewayUrl: "https://gateway.example.com",
gatewayToken: "gateway-token"
};
const { synthesizeTerminalSpeech } = require("./ttsGateway.js");
const result = await synthesizeTerminalSpeech({
text: "请先检查配置。"
});
expect(result.audioUrl).toBe("/userdata/tts-cache-cache-1.mp3");
expect(result.remoteAudioUrl).toContain("/api/miniprogram/tts/audio/cache-1");
expect(downloadFile).toHaveBeenCalledWith(
expect.objectContaining({
url: "https://gateway.example.com/api/miniprogram/tts/audio/cache-1?ticket=demo",
filePath: "/userdata/tts-cache-cache-1.mp3",
timeout: 15000
})
);
expect(wxRuntime.request).toHaveBeenLastCalledWith(
expect.objectContaining({
url: "https://gateway.example.com/api/miniprogram/tts/synthesize",
method: "POST",
timeout: 15000,
header: expect.objectContaining({
authorization: "Bearer sync-token"
})
})
);
});
it("应在后台合成时轮询状态直到音频就绪", async () => {
const requestCalls: Array<{ url: string; method: string; timeout?: number }> = [];
const downloadFile = vi.fn(
(options?: { success?: (value: { statusCode: number }) => void; filePath?: string }) => {
options?.success?.({
statusCode: 200
});
}
);
const wxRuntime = {
env: {
USER_DATA_PATH: "/userdata"
},
getStorageSync: vi.fn(() => ({
token: "sync-token",
expiresAt: "2099-01-01T00:00:00.000Z"
})),
setStorageSync: vi.fn(),
getFileSystemManager: vi.fn(() => ({
access: vi.fn((options?: { fail?: () => void }) => {
options?.fail?.();
})
})),
login: vi.fn(),
downloadFile,
request: vi.fn(
(options?: {
url?: string;
method?: string;
timeout?: number;
success?: (value: { statusCode: number; data: Record<string, unknown> }) => void;
}) => {
requestCalls.push({
url: String(options?.url || ""),
method: String(options?.method || "GET").toUpperCase(),
timeout: options?.timeout
});
if (String(options?.url).endsWith("/api/miniprogram/tts/synthesize")) {
options?.success?.({
statusCode: 200,
data: {
ok: true,
status: "pending",
cacheKey: "cache-2",
cached: false,
audioUrl: "https://gateway.example.com/api/miniprogram/tts/audio/cache-2?ticket=demo",
statusUrl: "https://gateway.example.com/api/miniprogram/tts/status/cache-2?ticket=demo",
expiresAt: "2099-01-01T00:00:00.000Z"
}
});
return;
}
options?.success?.({
statusCode: 200,
data:
requestCalls.filter((item) => item.url.includes("/api/miniprogram/tts/status/")).length >= 2
? { ok: true, status: "ready" }
: { ok: true, status: "pending" }
});
}
)
};
(global as typeof globalThis & { wx: typeof wxRuntime }).wx = wxRuntime;
(globalThis as typeof globalThis & { __REMOTE_CONN_OPS__?: unknown }).__REMOTE_CONN_OPS__ = {
gatewayUrl: "https://gateway.example.com",
gatewayToken: "gateway-token"
};
const { synthesizeTerminalSpeech } = require("./ttsGateway.js");
const result = await synthesizeTerminalSpeech({
text: "请先检查配置。",
timeoutMs: 10000
});
expect(result.audioUrl).toBe("/userdata/tts-cache-cache-2.mp3");
expect(result.remoteAudioUrl).toContain("/api/miniprogram/tts/audio/cache-2");
expect(requestCalls.filter((item) => item.url.includes("/api/miniprogram/tts/status/")).length).toBe(2);
expect(downloadFile).toHaveBeenCalledTimes(1);
});
it("应把网关返回的 TTS 地址重写到当前同步基地址同源", async () => {
const requestCalls: string[] = [];
const downloadFile = vi.fn(
(options?: { success?: (value: { statusCode: number }) => void; filePath?: string }) => {
options?.success?.({
statusCode: 200
});
}
);
const wxRuntime = {
env: {
USER_DATA_PATH: "/userdata"
},
getStorageSync: vi.fn(() => ({
token: "sync-token",
expiresAt: "2099-01-01T00:00:00.000Z"
})),
setStorageSync: vi.fn(),
getFileSystemManager: vi.fn(() => ({
access: vi.fn((options?: { fail?: () => void }) => {
options?.fail?.();
})
})),
login: vi.fn(),
downloadFile,
request: vi.fn(
(options?: {
url?: string;
success?: (value: { statusCode: number; data: Record<string, unknown> }) => void;
}) => {
requestCalls.push(String(options?.url || ""));
if (String(options?.url).endsWith("/api/miniprogram/tts/synthesize")) {
options?.success?.({
statusCode: 200,
data: {
ok: true,
status: "pending",
cacheKey: "cache-3",
cached: false,
audioUrl: "http://127.0.0.1:8787/api/miniprogram/tts/audio/cache-3?ticket=demo",
statusUrl: "http://127.0.0.1:8787/api/miniprogram/tts/status/cache-3?ticket=demo",
expiresAt: "2099-01-01T00:00:00.000Z"
}
});
return;
}
options?.success?.({
statusCode: 200,
data: { ok: true, status: "ready" }
});
}
)
};
(global as typeof globalThis & { wx: typeof wxRuntime }).wx = wxRuntime;
(globalThis as typeof globalThis & { __REMOTE_CONN_OPS__?: unknown }).__REMOTE_CONN_OPS__ = {
gatewayUrl: "https://gateway.example.com",
gatewayToken: "gateway-token"
};
const { synthesizeTerminalSpeech } = require("./ttsGateway.js");
const result = await synthesizeTerminalSpeech({
text: "请先检查配置。"
});
expect(result.audioUrl).toBe("/userdata/tts-cache-cache-3.mp3");
expect(result.remoteAudioUrl).toBe("https://gateway.example.com/api/miniprogram/tts/audio/cache-3?ticket=demo");
expect(requestCalls).toContain("https://gateway.example.com/api/miniprogram/tts/status/cache-3?ticket=demo");
expect(downloadFile).toHaveBeenCalledWith(
expect.objectContaining({
url: "https://gateway.example.com/api/miniprogram/tts/audio/cache-3?ticket=demo",
filePath: "/userdata/tts-cache-cache-3.mp3"
})
);
});
it("本地已命中 TTS 缓存时应直接复用,不再重复下载", async () => {
const downloadFile = vi.fn();
const wxRuntime = {
env: {
USER_DATA_PATH: "/userdata"
},
getStorageSync: vi.fn(() => ({
token: "sync-token",
expiresAt: "2099-01-01T00:00:00.000Z"
})),
setStorageSync: vi.fn(),
getFileSystemManager: vi.fn(() => ({
access: vi.fn((options?: { success?: () => void }) => {
options?.success?.();
})
})),
login: vi.fn(),
downloadFile,
request: vi.fn(
(options?: {
success?: (value: { statusCode: number; data: Record<string, unknown> }) => void;
}) => {
options?.success?.({
statusCode: 200,
data: {
ok: true,
cacheKey: "cache-4",
cached: true,
audioUrl: "https://gateway.example.com/api/miniprogram/tts/audio/cache-4?ticket=demo",
expiresAt: "2099-01-01T00:00:00.000Z"
}
});
}
)
};
(global as typeof globalThis & { wx: typeof wxRuntime }).wx = wxRuntime;
(globalThis as typeof globalThis & { __REMOTE_CONN_OPS__?: unknown }).__REMOTE_CONN_OPS__ = {
gatewayUrl: "https://gateway.example.com",
gatewayToken: "gateway-token"
};
const { synthesizeTerminalSpeech } = require("./ttsGateway.js");
const result = await synthesizeTerminalSpeech({
text: "请先检查配置。"
});
expect(result.audioUrl).toBe("/userdata/tts-cache-cache-4.mp3");
expect(downloadFile).not.toHaveBeenCalled();
});
it("应保留上游鉴权或权限失败提示", async () => {
const wxRuntime = {
env: {
USER_DATA_PATH: "/userdata"
},
getStorageSync: vi.fn(() => ({
token: "sync-token",
expiresAt: "2099-01-01T00:00:00.000Z"
})),
setStorageSync: vi.fn(),
getFileSystemManager: vi.fn(() => ({
access: vi.fn((options?: { fail?: () => void }) => {
options?.fail?.();
})
})),
login: vi.fn(),
downloadFile: vi.fn(),
request: vi.fn(
(options?: {
success?: (value: { statusCode: number; data: Record<string, unknown> }) => void;
}) => {
options?.success?.({
statusCode: 502,
data: {
ok: false,
message:
"TTS 上游鉴权或权限失败请检查密钥、地域和账号权限AuthFailure.InvalidSecretId: The SecretId is not found"
}
});
}
)
};
(global as typeof globalThis & { wx: typeof wxRuntime }).wx = wxRuntime;
(globalThis as typeof globalThis & { __REMOTE_CONN_OPS__?: unknown }).__REMOTE_CONN_OPS__ = {
gatewayUrl: "https://gateway.example.com",
gatewayToken: "gateway-token"
};
const { synthesizeTerminalSpeech } = require("./ttsGateway.js");
await expect(
synthesizeTerminalSpeech({
text: "请先检查配置。"
})
).rejects.toThrow("TTS 上游鉴权或权限失败,请检查密钥、地域和账号权限");
});
});