import assert from 'node:assert/strict'; import test from 'node:test'; import { createHealthChannelSessionStore } from './health-channel-session-store.mjs'; import { createInMemoryHealthObservationStore } from './health-observation-store.mjs'; import { createInMemoryHealthEventStore } from './health-event-store.mjs'; import { createHealthObservationService } from './health-observation-service.mjs'; import { HEALTH_MENU_TEXT, applyHealthChannelTurn, applyHealthWechatTurn, isHealthEnterText, shouldFallThroughWechatHealthMenuEvent, } from './health-wechat-turn.mjs'; import { createIdleHealthChannelState } from './health-channel-state.mjs'; import { handleWechatHealthChannel } from './wechat/handlers/health.mjs'; test('health menu event only falls through when the feature flag is on', () => { const inbound = { event: 'click', eventKey: 'MEMIND_HEALTH' }; assert.equal(shouldFallThroughWechatHealthMenuEvent(inbound, {}), false); assert.equal( shouldFallThroughWechatHealthMenuEvent(inbound, { MEMIND_HEALTH_ENABLED: '1' }), true, ); }); test('wechat health channel requires confirm before writing observations', async () => { const store = createHealthChannelSessionStore(); const observations = createInMemoryHealthObservationStore(); const observationService = createHealthObservationService({ store: observations }); const userId = 'user-1'; const entered = await handleWechatHealthChannel({ enabled: true, userId, intent: { msgType: 'text', agentText: '健康助手' }, inbound: {}, store, observationService, }); assert.match(entered, /专用通道/); const parsed = await handleWechatHealthChannel({ enabled: true, userId, intent: { msgType: 'text', agentText: '137/82' }, inbound: {}, store, observationService, }); assert.match(parsed, /确认保存/); assert.equal((await observations.list(userId)).length, 0); const saved = await handleWechatHealthChannel({ enabled: true, userId, intent: { msgType: 'text', agentText: '1' }, inbound: {}, store, observationService, }); assert.match(saved, /已记录/); const rows = await observations.list(userId); assert.equal(rows.length, 2); assert.equal(rows.some((row) => row.metricType === 'bp_systolic' && row.valueNum === 137), true); }); test('wechat health channel prepends unread alerts on enter', async () => { const store = createHealthChannelSessionStore(); const eventStore = createInMemoryHealthEventStore(); await eventStore.insertIfNew('user-alert', { severity: 'alert', eventType: 'threshold_breach', ruleId: 'bp_sys_high', metricType: 'bp_systolic', message: '收缩压触发阈值', }); const entered = await handleWechatHealthChannel({ enabled: true, userId: 'user-alert', intent: { msgType: 'text', agentText: '健康助手' }, inbound: {}, store, eventStore, }); assert.match(entered, /未读健康提醒/); assert.match(entered, /专用通道/); }); test('disabled health channel does not intercept ordinary wechat chat', async () => { const reply = await handleWechatHealthChannel({ enabled: false, userId: 'user-1', intent: { msgType: 'text', agentText: '健康助手' }, inbound: {}, store: createHealthChannelSessionStore(), }); assert.equal(reply, null); }); test('enter keywords are specific and photo does not auto-commit', () => { assert.equal(isHealthEnterText('健康助手'), true); assert.equal(isHealthEnterText('健康小助手'), true); assert.equal(isHealthEnterText('健康饮食怎么做'), false); const inValue = applyHealthWechatTurn({ session: { channelState: { sessionKind: 'health', channel: 'wechat', step: 'await_value', action: 'record', metricSet: 'blood_pressure', context: 'morning', pendingDraftId: null, lastPrompt: null, }, pendingValues: { context: 'morning' }, }, msgType: 'image', text: '', }); assert.equal(inValue.handled, true); assert.equal(inValue.commit, null); assert.match(inValue.reply, /不会|手输|确认/); }); test('unconfirmed insert is rejected by the observation store', async () => { const store = createInMemoryHealthObservationStore(); await assert.rejects( () => store.insert('user-1', { metricType: 'bp_systolic', valueNum: 120 }), (error) => error.code === 'health_unconfirmed', ); }); test('h5 channel can enter without menu event key', () => { const entered = applyHealthChannelTurn({ channel: 'h5', forceEnter: true, text: '', }); assert.equal(entered.handled, true); assert.match(entered.reply, /专用通道/); assert.equal(entered.session?.channelState?.channel, 'h5'); }); test('menu item 2 enters document upload mode', () => { const picked = applyHealthChannelTurn({ channel: 'h5', session: { channelState: createIdleHealthChannelState({ channel: 'h5' }), pendingValues: null, }, text: '2', }); assert.match(picked.reply, /报告照片/); assert.equal(picked.session?.channelState?.action, 'document'); }); test('document image archives without device extraction commit shape', () => { const uploaded = applyHealthChannelTurn({ channel: 'h5', session: { channelState: { ...createIdleHealthChannelState({ channel: 'h5' }), action: 'document', }, pendingValues: null, }, msgType: 'image', text: '', }); assert.equal(uploaded.commit?.type, 'document'); assert.match(uploaded.reply, /归档/); }); test('menu text is the exclusive wechat prompt', () => { assert.match(HEALTH_MENU_TEXT, /0 退出健康通道/); }); test('h5 idle free text falls through to agent instead of replaying menu', () => { const result = applyHealthChannelTurn({ channel: 'h5', session: { channelState: createIdleHealthChannelState({ channel: 'h5' }), pendingValues: null, }, text: '帮我生成一份健康报告,分析一下最近血压', }); assert.equal(result.handled, false); }); test('wechat idle unknown text still replays menu', () => { const result = applyHealthChannelTurn({ channel: 'wechat', session: { channelState: createIdleHealthChannelState({ channel: 'wechat' }), pendingValues: null, }, text: '帮我生成一份健康报告', }); assert.equal(result.handled, true); assert.match(result.reply, /专用通道/); });