fix: preserve WeChat attachment task context
This commit is contained in:
1 parent
e7aa58a203
commit
2360506607
5 files changed
+219
-17
No files matched your search
@@ -108,6 +108,13 @@ class FakeSocket {
|
||||
}
|
||||
}
|
||||
|
||||
async function waitFor(predicate: () => boolean, timeoutMs = 1_000): Promise<void> {
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
while (!predicate() && Date.now() < deadline) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 5));
|
||||
}
|
||||
}
|
||||
|
||||
function testConfig() {
|
||||
return loadConfig({
|
||||
NODE_ENV: 'test',
|
||||
@@ -188,6 +195,21 @@ test('WeChat transport envelope exposes only its business text to the global par
|
||||
const extracted = extractAgentBusBusinessText(wrapped);
|
||||
assert.equal(extracted, businessText);
|
||||
assert.equal(resolveBusinessRoute(extracted).routeId, 'team_order_create');
|
||||
const envelopeConversation = parseAgentBusFrame(JSON.stringify({
|
||||
id: 'wechat-envelope-conversation',
|
||||
type: 'event',
|
||||
from: 'channel:wechat:user-1',
|
||||
payload: { text: wrapped }
|
||||
}));
|
||||
assert.equal(envelopeConversation?.conversation_id, 'conversation-example-1');
|
||||
const explicitConversation = parseAgentBusFrame(JSON.stringify({
|
||||
id: 'wechat-explicit-conversation',
|
||||
type: 'event',
|
||||
from: 'channel:wechat:user-1',
|
||||
conversation_id: 'conversation-explicit-1',
|
||||
payload: { text: wrapped }
|
||||
}));
|
||||
assert.equal(explicitConversation?.conversation_id, 'conversation-explicit-1');
|
||||
|
||||
const missingConversation = `New WeChat message\nConversation:\nText: ${businessText}`;
|
||||
const wrongTextLabel = `New WeChat message\nConversation: conversation-example-1\nBody: ${businessText}`;
|
||||
@@ -510,7 +532,7 @@ test('AgentBus attachment references omit archived visitor XLS and keep only XLS
|
||||
}]);
|
||||
});
|
||||
|
||||
test('AgentBus listener connects with the documented Authorization header and returns one final result', async () => {
|
||||
test('AgentBus listener connects with the documented Authorization header and returns one final result', async (t) => {
|
||||
const config = testConfig();
|
||||
const socket = new FakeSocket();
|
||||
let capturedUrl = '';
|
||||
@@ -608,6 +630,7 @@ test('AgentBus listener connects with the documented Authorization header and re
|
||||
}
|
||||
}
|
||||
});
|
||||
t.after(() => listener.stop());
|
||||
|
||||
listener.start();
|
||||
assert.match(capturedUrl, /[?&]ready=1/);
|
||||
@@ -645,11 +668,11 @@ test('AgentBus listener connects with the documented Authorization header and re
|
||||
payload: { text: wrappedBusinessText, reply_policy: { progress: true } }
|
||||
}));
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
await waitFor(() => socket.sent.length === 2);
|
||||
assert.equal(received.length, 1);
|
||||
assert.equal(capturedContext.source, 'agentbus');
|
||||
assert.equal(received[0].message, wrappedBusinessText.split('\n').slice(2).join('\n').replace(/^Text:\s*/, ''));
|
||||
assert.equal(received[0].conversationId, 'agentbus:channel:wechat:user-1');
|
||||
assert.equal(received[0].conversationId, 'conversation-wechat-1');
|
||||
assert.equal(received[0].idempotencyKey, 'agentbus:channel-event-1');
|
||||
assert.equal(socket.sent.length, 2);
|
||||
assert.equal((socket.sent[0].payload as Record<string, unknown>).event, 'task.progress');
|
||||
@@ -675,7 +698,80 @@ test('AgentBus listener connects with the documented Authorization header and re
|
||||
assert.ok(logs.some((entry) => entry.metadata.agentbus_event === 'outbound_progress_sent'));
|
||||
assert.ok(logs.some((entry) => entry.metadata.agentbus_event === 'outbound_result_sent'));
|
||||
assert.doesNotMatch(JSON.stringify(logs), /test-ws-token/);
|
||||
listener.stop();
|
||||
});
|
||||
|
||||
test('WeChat attachment placeholder without file metadata fails closed before task ingestion', async (t) => {
|
||||
const socket = new FakeSocket();
|
||||
const events = new EventEmitter();
|
||||
const logs: Array<{ level: string; metadata: Record<string, unknown>; message?: string }> = [];
|
||||
let ingestCalls = 0;
|
||||
const tasks: AgentBusTaskGateway = {
|
||||
events,
|
||||
async ingestMessage() {
|
||||
ingestCalls += 1;
|
||||
return { task: makeTask('parse_queued'), attached: false, created: true };
|
||||
},
|
||||
async getTask() {
|
||||
return makeTask('failed');
|
||||
}
|
||||
};
|
||||
const listener = new AgentBusListener({
|
||||
config: testConfig(),
|
||||
tasks,
|
||||
organizationId: 'org-1',
|
||||
scheduleParseQueue: async () => {},
|
||||
socketFactory: () => socket as unknown as AgentBusSocket,
|
||||
logger: {
|
||||
info(metadata, message) {
|
||||
logs.push({ level: 'info', metadata, message });
|
||||
},
|
||||
warn(metadata, message) {
|
||||
logs.push({ level: 'warn', metadata, message });
|
||||
},
|
||||
error(metadata, message) {
|
||||
logs.push({ level: 'error', metadata, message });
|
||||
}
|
||||
}
|
||||
});
|
||||
t.after(() => listener.stop());
|
||||
|
||||
listener.start();
|
||||
socket.readyState = 1;
|
||||
socket.emit('open');
|
||||
socket.emit('message', JSON.stringify({
|
||||
id: 'ready-attachment-placeholder',
|
||||
type: 'event',
|
||||
session_id: 'session-attachment-placeholder',
|
||||
epoch: 1,
|
||||
to: 'bot:test:listener',
|
||||
payload: { event: 'session.ready' }
|
||||
}));
|
||||
socket.emit('message', JSON.stringify({
|
||||
id: 'wechat-attachment-placeholder',
|
||||
type: 'event',
|
||||
from: 'channel:wechat:user-1',
|
||||
payload: {
|
||||
text: [
|
||||
'New WeChat message',
|
||||
'Conversation: thread:conversation-attachment-1',
|
||||
'Text: [WeChat attachment: synthetic-roster.xlsx]'
|
||||
].join('\n')
|
||||
}
|
||||
}));
|
||||
|
||||
await waitFor(() => socket.sent.length === 1);
|
||||
assert.equal(ingestCalls, 0);
|
||||
assert.equal(socket.sent.length, 1);
|
||||
assert.equal(socket.sent[0].conversation_id, 'thread:conversation-attachment-1');
|
||||
assert.equal((socket.sent[0].payload as Record<string, unknown>).event, 'task.result');
|
||||
assert.equal((socket.sent[0].payload as Record<string, unknown>).status, 'failed');
|
||||
assert.equal(
|
||||
(socket.sent[0].payload as Record<string, unknown>).text,
|
||||
'附件内容未传到平台,原任务仍在等待附件。请检查微信桥接器的文件转发后重新发送。'
|
||||
);
|
||||
assert.ok(logs.some((entry) => entry.metadata.agentbus_event === 'task_processing_failed'
|
||||
&& entry.metadata.error_code === 'roster_attachment_metadata_missing'));
|
||||
assert.ok(logs.some((entry) => entry.metadata.agentbus_event === 'outbound_result_sent'));
|
||||
});
|
||||
|
||||
test('listener reload stop does not overwrite the channel status as disabled', () => {
|
||||
|
||||
Reference in new issue
Block a user