/** * Live DingTalk conversation smoke test. * * Automated conversation path is covered by vitest: * pnpm --filter @supen-ai/daemon test tests/gateway-dingtalk-conversation.test.ts * pnpm --filter @supen-ai/gateway test:dingtalk * * Live outbound check (optional): * SUPEN_DINGTALK_TEST_JID='dingtalk:appId:cid...' \ * pnpm --filter @supen-ai/daemon exec tsx --env-file=.env scripts/e2e-dingtalk-conversation.ts */ import { Gateway, setGatewayInstance } from '../src/core/gateway.js'; import { sendExternalChannelNotification } from '../src/core/channel-notify.js'; type JsonRecord = Record; async function fetchJson(url: string): Promise { const response = await fetch(url); const body = await response.json().catch(() => ({})); if (!response.ok) { throw new Error(`${url} -> ${response.status} ${JSON.stringify(body)}`); } return body as JsonRecord; } async function main(): Promise { const daemonBase = process.env.SUPEN_DAEMON_URL?.trim() || 'http://127.0.0.1:2756'; const hubBase = process.env.SUPEN_HUB_URL?.trim() || 'https://hub.supen.ai'; const testJid = process.env.SUPEN_DINGTALK_TEST_JID?.trim(); console.log('== DingTalk conversation live smoke =='); const hubRuntime = await fetchJson(`${hubBase}/api/gateway/channels/runtime?channel=dingtalk`); const dingtalk = Array.isArray(hubRuntime.channels) ? hubRuntime.channels[0] as JsonRecord : null; if (!dingtalk?.connected) { throw new Error(`Hub DingTalk channel is not connected: ${JSON.stringify(hubRuntime)}`); } console.log(`✓ Hub DingTalk connected (${dingtalk.configured_accounts} account(s))`); const uplink = await fetchJson(`${daemonBase}/api/computers/local/gateway-uplink`); if (!uplink.connected) { throw new Error(`Daemon gateway uplink is not connected: ${JSON.stringify(uplink)}`); } if (!String(uplink.gateway_url || '').includes('hub.supen.ai')) { throw new Error(`Daemon gateway uplink is not using hub.supen.ai: ${JSON.stringify(uplink)}`); } console.log(`✓ Daemon uplink connected (${uplink.gateway_url})`); const channelTestEnabled = ['1', 'true', 'yes'].includes( (process.env.SUPEN_ENABLE_CHANNEL_TEST || '').trim().toLowerCase(), ); if (channelTestEnabled) { console.log('Sending live outbound message via running daemon channel test API...'); const response = await fetch(`${daemonBase}/api/computers/local/channels/test-reply`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify( testJid ? { jid: testJid } : {}, ), }); const body = await response.json().catch(() => ({})); if (!response.ok) { throw new Error(`channel test-reply failed: ${response.status} ${JSON.stringify(body)}`); } console.log(`✓ Live outbound DingTalk message sent (${body.jid || testJid || 'default binding'})`); return; } if (!testJid) { console.log('○ Skipping live DingTalk delivery (set SUPEN_DINGTALK_TEST_JID to enable outbound smoke)'); console.log(' Automated conversation e2e already covers inbound -> agent -> channel_reply.'); return; } const gateway = new Gateway({ onMessage: () => {}, onChatMetadata: () => {}, }); setGatewayInstance(gateway); console.log('Connecting daemon gateway client for outbound smoke...'); await gateway.connect(); if (!gateway.isConnected()) { throw new Error('Failed to connect daemon gateway client for live smoke'); } const text = [ '**Supen E2E** — 钉钉对话出站测试', '如果你看到这条消息,说明 Gateway → 钉钉主动推送正常。', `时间: ${new Date().toISOString()}`, ].join('\n'); console.log(`Sending live smoke message to ${testJid} ...`); await sendExternalChannelNotification(testJid, text, { traceId: 'e2e-dingtalk-conversation' }); console.log('✓ Live outbound DingTalk message sent'); await gateway.disconnect(); } main().catch((error) => { console.error(error instanceof Error ? error.message : String(error)); process.exit(1); });