26846a0274
Memind CI / Test, build, and release guards (push) Successful in 3m27s
When passive Recognition is empty, convert AMR to mp3 and call WeChat addvoicetorecofortext before the existing asr.tkmind.cn fallback. Co-authored-by: Cursor <cursoragent@cursor.com>
139 lines
4.5 KiB
JavaScript
139 lines
4.5 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import {
|
|
buildWechatVoiceRecoVoiceId,
|
|
queryWechatVoiceRecoResult,
|
|
transcribeWechatVoiceViaRecoApi,
|
|
uploadWechatVoiceForReco,
|
|
} from './wechat-voice-reco.mjs';
|
|
|
|
test('buildWechatVoiceRecoVoiceId prefers msgId', () => {
|
|
assert.equal(
|
|
buildWechatVoiceRecoVoiceId({ msgId: '7670473258902224896', mediaId: 'media-1' }),
|
|
'7670473258902224896',
|
|
);
|
|
});
|
|
|
|
test('transcribeWechatVoiceViaRecoApi uploads mp3 and polls reco result', async () => {
|
|
const calls = [];
|
|
let queryCount = 0;
|
|
const mp3Buffer = Buffer.from('fake-mp3');
|
|
|
|
const text = await transcribeWechatVoiceViaRecoApi({
|
|
accessToken: 'token-1',
|
|
voiceBuffer: Buffer.from('fake-amr'),
|
|
format: 'amr',
|
|
voiceId: 'voice-1',
|
|
wechatFetch: async (url, init = {}) => {
|
|
calls.push([String(url), init.method ?? 'GET']);
|
|
if (String(url).includes('/addvoicetorecofortext')) {
|
|
assert.equal(init.method, 'POST');
|
|
return new Response(JSON.stringify({ errcode: 0, errmsg: 'ok' }), {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
});
|
|
}
|
|
if (String(url).includes('/queryrecoresultfortext')) {
|
|
queryCount += 1;
|
|
const result = queryCount >= 2 ? '帮我看看最近的天气' : '';
|
|
return new Response(JSON.stringify({ errcode: 0, errmsg: 'ok', result }), {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
});
|
|
}
|
|
throw new Error(`unexpected url: ${url}`);
|
|
},
|
|
convertToMp3: async () => mp3Buffer,
|
|
pollIntervalMs: 1,
|
|
pollTimeoutMs: 50,
|
|
now: () => Date.now(),
|
|
});
|
|
|
|
assert.equal(text, '帮我看看最近的天气');
|
|
assert.equal(calls.some(([url]) => url.includes('/addvoicetorecofortext')), true);
|
|
assert.ok(queryCount >= 2);
|
|
});
|
|
|
|
test('transcribeWechatVoiceViaRecoApi returns empty when mp3 conversion fails', async () => {
|
|
let fetchCalled = false;
|
|
const text = await transcribeWechatVoiceViaRecoApi({
|
|
accessToken: 'token-1',
|
|
voiceBuffer: Buffer.from('fake-amr'),
|
|
voiceId: 'voice-1',
|
|
wechatFetch: async () => {
|
|
fetchCalled = true;
|
|
return new Response('{}', { status: 200 });
|
|
},
|
|
convertToMp3: async () => null,
|
|
});
|
|
|
|
assert.equal(text, '');
|
|
assert.equal(fetchCalled, false);
|
|
});
|
|
|
|
test('uploadWechatVoiceForReco throws on WeChat business error', async () => {
|
|
await assert.rejects(
|
|
() =>
|
|
uploadWechatVoiceForReco({
|
|
accessToken: 'token-1',
|
|
voiceId: 'voice-1',
|
|
mp3Buffer: Buffer.from('fake-mp3'),
|
|
wechatFetch: async () =>
|
|
new Response(JSON.stringify({ errcode: 40010, errmsg: 'invalid voice size' }), {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}),
|
|
}),
|
|
/invalid voice size/,
|
|
);
|
|
});
|
|
|
|
test('queryWechatVoiceRecoResult retries not-ready as empty result during polling', async () => {
|
|
let queryCount = 0;
|
|
const text = await transcribeWechatVoiceViaRecoApi({
|
|
accessToken: 'token-1',
|
|
voiceBuffer: Buffer.from('fake-amr'),
|
|
voiceId: 'voice-1',
|
|
wechatFetch: async (url) => {
|
|
if (String(url).includes('/addvoicetorecofortext')) {
|
|
return new Response(JSON.stringify({ errcode: 0, errmsg: 'ok' }), {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
});
|
|
}
|
|
if (String(url).includes('/queryrecoresultfortext')) {
|
|
queryCount += 1;
|
|
if (queryCount === 1) {
|
|
return new Response(JSON.stringify({ errcode: -1, errmsg: 'system error' }), {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
});
|
|
}
|
|
return new Response(JSON.stringify({ errcode: 0, errmsg: 'ok', result: '识别完成' }), {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
});
|
|
}
|
|
throw new Error(`unexpected url: ${url}`);
|
|
},
|
|
convertToMp3: async () => Buffer.from('fake-mp3'),
|
|
pollIntervalMs: 1,
|
|
pollTimeoutMs: 200,
|
|
});
|
|
assert.equal(text, '识别完成');
|
|
assert.ok(queryCount >= 2);
|
|
});
|
|
|
|
test('queryWechatVoiceRecoResult returns trimmed result', async () => {
|
|
const result = await queryWechatVoiceRecoResult({
|
|
accessToken: 'token-1',
|
|
voiceId: 'voice-1',
|
|
wechatFetch: async () =>
|
|
new Response(JSON.stringify({ errcode: 0, errmsg: 'ok', result: ' 你好 ' }), {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}),
|
|
});
|
|
assert.equal(result, '你好');
|
|
});
|