360 lines
12 KiB
TypeScript
360 lines
12 KiB
TypeScript
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 上游鉴权或权限失败,请检查密钥、地域和账号权限");
|
||
});
|
||
});
|