test(wechat): simulate tang image report and follow-up edit flow
Memind CI / Test, build, and release guards (push) Failing after 3m27s
Memind CI / Test, build, and release guards (push) Failing after 3m27s
Add a four-step webhook simulation covering reset, image ack, report page turn, and polluted-session rotation on content-edit follow-ups. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -5658,6 +5658,184 @@ test('wechat mp rotates polluted image session before a content-edit follow-up',
|
||||
assert.equal(activeSessionId, submitCalls[0].sessionId);
|
||||
});
|
||||
|
||||
test('wechat mp simulates tang image report page and desensitized follow-up', async () => {
|
||||
const token = 'token';
|
||||
const timestamp = '1710000000';
|
||||
const nonce = 'nonce';
|
||||
const testUserId = 'test-user-tang-report-followup';
|
||||
const submitCalls = [];
|
||||
const customerReplies = [];
|
||||
let activeSessionId = 'session-1';
|
||||
let nextSessionId = 2;
|
||||
let routeCleared = false;
|
||||
let reportPageCompleted = false;
|
||||
let reportSessionId = null;
|
||||
const pollutedConversation = [
|
||||
{
|
||||
id: 'assistant-report',
|
||||
role: 'assistant',
|
||||
metadata: { userVisible: true },
|
||||
content: [{ type: 'text', text: '已解读化验报告并生成页面链接。' }],
|
||||
},
|
||||
{
|
||||
id: 'assistant-image',
|
||||
role: 'assistant',
|
||||
metadata: { userVisible: false },
|
||||
content: [{ type: 'image_url', image_url: { url: 'https://example.com/report.png' } }],
|
||||
},
|
||||
];
|
||||
|
||||
const service = createBoundWechatService({
|
||||
token,
|
||||
config: {
|
||||
mediaAnalysisGrayUsers: [testUserId],
|
||||
},
|
||||
userAuth: {
|
||||
async findWechatUserByOpenid() {
|
||||
return { userId: testUserId, status: 'active', nickname: '唐' };
|
||||
},
|
||||
async getWechatAgentRoute() {
|
||||
if (routeCleared || !activeSessionId) return null;
|
||||
return { agentSessionId: activeSessionId, status: 'active', updatedAt: Date.now() };
|
||||
},
|
||||
async upsertWechatAgentRoute({ agentSessionId }) {
|
||||
activeSessionId = agentSessionId;
|
||||
routeCleared = false;
|
||||
},
|
||||
async clearWechatAgentRoute() {
|
||||
routeCleared = true;
|
||||
},
|
||||
},
|
||||
startAgentSession: async () => ({ id: `session-${nextSessionId++}` }),
|
||||
sessionApiFetch: async (sessionId, pathname) => {
|
||||
if (pathname === `/sessions/${sessionId}`) {
|
||||
const conversation =
|
||||
reportSessionId && sessionId === reportSessionId && reportPageCompleted
|
||||
? pollutedConversation
|
||||
: [];
|
||||
return new Response(JSON.stringify({ conversation }), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
if (pathname === `/sessions/${sessionId}/events`) {
|
||||
const isReportStream = submitCalls.length === 0;
|
||||
const text = isReportStream
|
||||
? '报告页面已生成:https://m.tkmind.cn/MindSpace/test/public/report.html'
|
||||
: '已按你的要求脱敏并补充报告细节。';
|
||||
return new Response(
|
||||
[
|
||||
`data: {"type":"Message","message":{"id":"assistant-${isReportStream ? 'report' : 'edit'}","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"${text}"}]}}\n\n`,
|
||||
'data: {"type":"Finish","token_state":{"inputTokens":1,"outputTokens":2}}\n\n',
|
||||
].join(''),
|
||||
{ status: 200, headers: { 'Content-Type': 'text/event-stream' } },
|
||||
);
|
||||
}
|
||||
if (pathname === '/agent/harness_remember' || pathname === '/agent/harness_bootstrap') {
|
||||
return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
|
||||
}
|
||||
throw new Error(`unexpected api path: ${sessionId} ${pathname}`);
|
||||
},
|
||||
submitSessionReply: async (input) => {
|
||||
submitCalls.push(input);
|
||||
if (!reportSessionId) {
|
||||
reportSessionId = input.sessionId;
|
||||
reportPageCompleted = true;
|
||||
}
|
||||
assert.equal(input.options?.requireHistoricalImageIsolation, true);
|
||||
return { ok: true };
|
||||
},
|
||||
wechatFetch: async (url, init = {}) => {
|
||||
if (String(url).includes('/cgi-bin/stable_token')) {
|
||||
return new Response(JSON.stringify({ access_token: 'access-1', expires_in: 7200 }), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
if (String(url).includes('/cgi-bin/media/get')) {
|
||||
return new Response(Buffer.from([0x89, 0x50, 0x4e, 0x47]), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'image/png' },
|
||||
});
|
||||
}
|
||||
if (String(url).includes('/cgi-bin/message/custom/send')) {
|
||||
customerReplies.push(JSON.parse(init.body ?? '{}'));
|
||||
return new Response(JSON.stringify({ errcode: 0, errmsg: 'ok' }), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
throw new Error(`unexpected wechat url: ${url}`);
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const resetResult = await service.handleInboundMessage(
|
||||
inboundXml({ msgType: 'text', content: '换新会话', extraFields: { MsgId: 'tang-1' } }),
|
||||
{ timestamp, nonce, signature: signatureFor(token, timestamp, nonce) },
|
||||
);
|
||||
if (resetResult.task) await resetResult.task;
|
||||
assert.equal(submitCalls.length, 0);
|
||||
|
||||
const imageResult = await service.handleInboundMessage(
|
||||
inboundXml({
|
||||
msgType: 'image',
|
||||
content: '',
|
||||
extraFields: {
|
||||
MsgId: 'tang-2',
|
||||
MediaId: 'media-report',
|
||||
PicUrl: 'https://wx.example.com/report.png',
|
||||
},
|
||||
}),
|
||||
{ timestamp, nonce, signature: signatureFor(token, timestamp, nonce) },
|
||||
);
|
||||
assert.equal(submitCalls.length, 0);
|
||||
assert.match(imageResult.body ?? '', /已收到图片/);
|
||||
|
||||
const reportResult = await service.handleInboundMessage(
|
||||
inboundXml({
|
||||
msgType: 'text',
|
||||
content: '解读报告并生成页面',
|
||||
extraFields: { MsgId: 'tang-3' },
|
||||
}),
|
||||
{ timestamp, nonce, signature: signatureFor(token, timestamp, nonce) },
|
||||
);
|
||||
await reportResult.task;
|
||||
|
||||
const editResult = await service.handleInboundMessage(
|
||||
inboundXml({
|
||||
msgType: 'text',
|
||||
content: '帮我把真名脱敏,报告再详细点',
|
||||
extraFields: { MsgId: 'tang-4' },
|
||||
}),
|
||||
{ timestamp, nonce, signature: signatureFor(token, timestamp, nonce) },
|
||||
);
|
||||
await editResult.task;
|
||||
} finally {
|
||||
fs.rmSync(path.join(process.cwd(), 'MindSpace', testUserId), { recursive: true, force: true });
|
||||
}
|
||||
|
||||
assert.ok(submitCalls.length >= 2, `submitCalls=${submitCalls.length}`);
|
||||
const firstReportSubmit = submitCalls.find(
|
||||
(call) => Array.isArray(call.userMessage.metadata?.imageUrls) && call.userMessage.metadata.imageUrls.length > 0,
|
||||
);
|
||||
assert.ok(firstReportSubmit, 'missing report submit with imageUrls');
|
||||
assert.equal(firstReportSubmit.sessionId, reportSessionId);
|
||||
assert.notEqual(submitCalls.at(-1).sessionId, reportSessionId);
|
||||
assert.ok(customerReplies.length >= 2);
|
||||
const replyTexts = customerReplies.map((payload) => String(payload?.text?.content ?? ''));
|
||||
for (const text of replyTexts) {
|
||||
assert.doesNotMatch(text, /unknown variant [`']?image_url/i);
|
||||
assert.doesNotMatch(text, /Ran into this error:/i);
|
||||
}
|
||||
assert.ok(replyTexts.some((text) => /脱敏/.test(text)), replyTexts.join(' | '));
|
||||
assert.ok(
|
||||
replyTexts.some((text) => /report\.html/.test(text))
|
||||
|| submitCalls.some((call) => Array.isArray(call.userMessage.metadata?.imageUrls)),
|
||||
replyTexts.join(' | '),
|
||||
);
|
||||
});
|
||||
|
||||
test('wechat mp groups consecutive images for one follow-up and clears the consumed batch', async () => {
|
||||
const token = 'token';
|
||||
const timestamp = '1710000000';
|
||||
|
||||
Reference in New Issue
Block a user