#!/usr/bin/env node const assert = require('assert'); const fs = require('fs'); const os = require('os'); const path = require('path'); const { GroupOperationsStore } = require('../mcp/src/core/group-operations-store'); const { __testing: groupOpsTesting } = require('../mcp/src/tools/qiwei-group-operations-run'); const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'qiwei-group-ops-')); const store = new GroupOperationsStore(path.join(tempDir, 'test.db')); try { const account = 'account-a'; const otherAccount = 'account-b'; const directoryAccount = 'directory-account'; assert.equal(groupOpsTesting.syncCustomerGroupsToOperations(store, directoryAccount, [ { roomId: 'confirmed-room', roomName: 'Confirmed room', reviewStatus: 'CONFIRMED' }, { roomId: 'auto-room', roomName: 'Auto room', reviewStatus: 'AUTO_CONFIRMED' }, { roomId: 'suggested-room', roomName: 'Suggested room', reviewStatus: 'SUGGESTED' }, ]), 2); assert.deepEqual(store.listGroups(directoryAccount).map(group => group.room_id).sort(), ['auto-room', 'confirmed-room']); assert.equal(groupOpsTesting.syncCustomerGroupsToOperations(store, directoryAccount, [ { roomId: 'confirmed-room', roomName: 'Confirmed room', reviewStatus: 'CONFIRMED' }, { roomId: 'auto-room', roomName: 'Auto room', reviewStatus: 'AUTO_CONFIRMED' }, ]), 0, 'directory reconciliation must be idempotent'); const playbook = store.createPlaybook(account, { name: '新客群服务 SOP', groupType: 'new_customer', content: { cadence: [{ time: '09:30', type: 'welcome', objective: '欢迎新客户', template: '欢迎加入 {{room_name}}' }] } }, 'test'); assert.equal(playbook.status, 'draft'); const published = store.publishPlaybook(account, playbook.id, 1, 'test'); assert.equal(published.status, 'published'); store.upsertGroup(account, { roomId: 'room-1', roomName: '客户服务群', storeId: '门店A', groupType: 'new_customer', playbookId: playbook.id }, 'test'); store.upsertGroup(account, { roomId: 'room-2', roomName: '会员服务群', storeId: '门店B', groupType: 'new_customer', playbookId: playbook.id }, 'test'); assert.equal(store.listGroups(account).length, 2); assert.equal(store.listGroups(otherAccount).length, 0, '账号数据必须隔离'); const version2 = store.addPlaybookVersion(account, playbook.id, { cadence: [{ time: '10:00', type: 'welcome', objective: '新版欢迎', template: '新版欢迎 {{room_name}}' }] }, '升级欢迎话术', 'test'); assert.equal(version2.version, 2); const stillPublished = store.getPlaybook(account, playbook.id); assert.equal(stillPublished.status, 'published', '创建草稿版本不能停用当前已发布 SOP'); assert.equal(stillPublished.version, 1, '创建草稿版本后默认读取仍应是当前已发布版本'); const planBeforeV2Publish = store.generatePlan(account, { roomId: 'room-1', planDate: '2026-07-17' }, 'test'); assert.equal(planBeforeV2Publish.items[0].draft_content, '欢迎加入 客户服务群', '未发布草稿不能进入运营计划'); assert.equal(planBeforeV2Publish.playbook_version_id, stillPublished.current_version_id, '计划审计版本必须与实际内容一致'); const impact = store.previewPlaybookPublish(account, playbook.id, 2); assert.equal(impact.affectedGroupCount, 2); assert.equal(impact.changes.length > 0, true); store.publishPlaybook(account, playbook.id, 2, 'test'); store.publishPlaybook(account, playbook.id, 1, 'test'); const plan = store.generatePlan(account, { roomId: 'room-1', planDate: '2026-07-18' }, 'test'); assert.equal(plan.items.length, 1); assert.match(plan.items[0].draft_content, /客户服务群/); const duplicate = store.generatePlan(account, { roomId: 'room-1', planDate: '2026-07-18' }, 'test'); assert.equal(duplicate.id, plan.id, '同群同日计划必须幂等'); const approved = store.updatePlanItem(account, plan.items[0].id, 'approve', {}, 'test'); assert.equal(approved.status, 'approved'); store.updatePlanItem(account, plan.items[0].id, 'mark_sent', { externalMessageId: 'manual-1' }, 'test'); const batch = store.batchGeneratePlans(account, { planDate: '2026-07-19', storeId: '门店B' }, 'test'); assert.equal(batch.total, 1); assert.equal(batch.success, 1); const automationPlan = store.getPlan(batch.results[0].planId); const automationItem = store.updatePlanItem(account, automationPlan.items[0].id, 'approve', {}, 'test'); const policy = store.createAutomationPolicy(account, { policy: { enabled: true, mode: 'dry_run', allowedGroupIds: ['room-2'], allowedTypes: [automationItem.type], windows: [{ days: [1], start: '09:00', end: '18:00' }], dailyLimitPerGroup: 1, weeklyLimitPerGroup: 7, consecutiveFailureLimit: 3, grayPercent: 100, requireNoOpenHighRisk: true } }, 'test'); assert.equal(policy.status, 'draft'); store.activateAutomationPolicy(account, policy.id, 'test'); const automationAt = new Date('2026-07-20T10:00:00+08:00'); assert.equal(store.evaluateAutomation(account, store.getPlanItemWithContext(account, automationItem.id), automationAt).allowed, false, '激活后必须保持暂停'); store.updateAutomationState(account, 'resume', 'test'); const decision = store.evaluateAutomation(account, store.getPlanItemWithContext(account, automationItem.id), automationAt); assert.equal(decision.allowed, true); assert.equal(store.createSendAttempt(account, store.getPlanItemWithContext(account, automationItem.id), decision, 'dry_run').status, 'dry_run'); for (let index = 0; index < 3; index++) { const attempt = store.createSendAttempt(account, store.getPlanItemWithContext(account, automationItem.id), decision, 'live'); store.finishSendAttempt(account, attempt.id, { success: false, error: `模拟失败 ${index + 1}` }); } assert.equal(store.getAutomationState(account).breaker_open, 1, '连续失败必须熔断'); assert.equal(store.evaluateAutomation(account, store.getPlanItemWithContext(account, automationItem.id), automationAt).reasons.includes('熔断器已打开'), true); store.updateAutomationState(account, 'reset_breaker', 'test'); assert.equal(store.getAutomationState(account).breaker_open, 0); const ambiguousAttempt = store.createSendAttempt(account, store.getPlanItemWithContext(account, automationItem.id), decision, 'live'); assert.equal(store.finishSendAttempt(account, ambiguousAttempt.id, { success: false, ambiguous: true, error: '发送回执超时' }).status, 'unknown'); assert.equal(store.evaluateAutomation(account, store.getPlanItemWithContext(account, automationItem.id), automationAt).reasons.some(reason => reason.includes('结果待确认')), true, '回执未知时必须锁住重试'); store.updateAutomationState(account, 'pause', 'test'); const review = store.reviewMessages(account, { roomId: 'room-1', slaMinutes: 30, messages: [ { messageId: 'm1', senderId: 'customer-1', senderRole: 'customer', content: '这个问题怎么退款?', sentAt: '2026-07-18T08:00:00.000Z' } ] }, 'test'); assert.equal(review.findingCount, 2, '应识别退款风险和超时未回应'); const unrelated = store.reviewMessages(account, { roomId: 'room-2', slaMinutes: 30, messages: [ { messageId: 'q-unrelated', senderId: 'customer-2', senderRole: 'customer', content: '退款什么时候到账?', sentAt: '2026-07-18T08:00:00.000Z' }, { messageId: 'a-unrelated', senderId: 'staff-2', senderRole: 'staff', content: '今天天气不错', sentAt: '2026-07-18T08:01:00.000Z' } ] }, 'test'); assert.equal(unrelated.findings.some(item => item.type === 'unanswered_question'), true, '无关员工消息不能算有效回应'); const answered = store.reviewMessages(account, { roomId: 'room-2', slaMinutes: 30, messages: [ { messageId: 'a-related', senderId: 'staff-2', senderRole: 'staff', content: '退款已提交,预计今天下午原路到账。', sentAt: '2026-07-18T08:02:00.000Z' } ] }, 'test'); assert.equal(answered.findings.filter(item => item.type === 'unanswered_question').length, 0, '有效回复后本次质检不应新增未回应结论'); const configured = store.setQualitySettings(account, { slaMinutes: 45, riskTerms: ['投诉', '退款'], forbiddenClaims: ['保证'] }, 'test'); assert.equal(configured.sla_minutes, 45); const overview = store.overview(account, '2026-07-18'); assert.equal(overview.metrics.executionRate, 100); assert.equal(overview.metrics.highRisk >= 1, true); assert.equal(Array.isArray(overview.scores), true); assert.equal(overview.scores.length, 2); assert.equal(overview.scores.every(item => item.coverage >= 0 && item.coverage <= 100), true); store.updateFinding(account, overview.findings[0].id, { status: 'false_positive', resolution: '测试误报' }, 'test'); assert.equal(store.feedbackSummary(account).falsePositives, 1); const task = store.createTask(account, { roomId: 'room-1', findingId: overview.findings[1].id, title: '跟进客户退款问题', owner: '测试负责人', dueAt: '2026-07-20T18:00:00+08:00', priority: 'high' }, 'test'); assert.equal(task.status, 'open'); assert.equal(store.updateTask(account, task.id, { status: 'done' }, 'test').status, 'done'); assert.equal(store.storeBreakdown(account, '2026-07-18').length, 2); store.reviewMessages(account, { roomId: 'room-1', messages: [ { messageId: 'recent-activity', senderId: 'customer-1', senderRole: 'customer', content: '收到,谢谢', sentAt: new Date().toISOString() } ] }, 'test'); assert.equal(store.groupInsights(account, 'room-1')[0].activity.currentMessages >= 1, true); assert.equal(store.excellentScripts(account).length, 1); assert.equal(store.overview(otherAccount, '2026-07-18').metrics.groups, 0); console.log('group-operations smoke: ok'); } finally { store.close(); fs.rmSync(tempDir, { recursive: true, force: true }); }