'use strict'; const assert = require('node:assert/strict'); const { buildWorkflowPlans } = require('../mcp/src/features/voc-cost/workflow-planner'); const { checkBudget } = require('../mcp/src/core/budget-guard'); const { runVocCostEstimate } = require('../mcp/src/tools/voc-cost-estimate-run'); const { runVocUsageReport } = require('../mcp/src/tools/voc-usage-report-run'); const fs = require('fs'); const os = require('os'); const path = require('path'); async function main() { const plans = buildWorkflowPlans({ workflowType: 'xhs_account_monitor', accountCount: 280, unresolvedAccountCount: 105 }); assert.equal(plans.length, 3); assert.equal(plans[0].operations.find(item => item.path.includes('search_user')).count, 105); assert.equal(plans[0].operations.find(item => item.path.includes('get_user_note')).count, 280); const trendPlans = buildWorkflowPlans({ workflowType: 'douyin_trend_report', keywordCount: 25, videosPerKeyword: 2, maxCommentPages: 0 }); assert.equal(trendPlans.length, 3); assert.equal(trendPlans[0].workflowType, 'douyin_trend_report'); assert.equal(trendPlans[0].operations.find(item => item.path.includes('fetch_general_search')).count, 25); assert.equal(trendPlans[0].operations.some(item => item.path.includes('fetch_video_comments')), false); const guard = checkBudget({ estimate: { amountCny: 100, quota: 1000 }, balance: { availableCny: 100 }, budgetLimitCny: 110, nextCostCny: 90 }); assert.equal(guard.status, 'warning'); const blocked = checkBudget({ estimate: { amountCny: 100 }, budgetLimitCny: 50, nextCostCny: 60 }); assert.equal(blocked.status, 'blocked'); const oldFetch = global.fetch; global.fetch = async (_url, options) => { const body = JSON.parse(options.body); if (Array.isArray(body.include) && body.include.includes('usage')) { return new Response(JSON.stringify({ code: 200, data: { usage: { quality: 'exact', totals: { calls: 3, billedCalls: 2, quota: 120, costCny: 0.24 }, groups: [{ endpoint: 'xhs.search', calls: 3, billedCalls: 2, quota: 120, costCny: 0.24 }] } } }), { status: 200 }); } return new Response(JSON.stringify({ code: 200, data: { balance: { availableQuota: 100000 }, catalog: [], estimate: { quota: 100, amountCny: 1, sufficient: true, operations: body.operations.map(item => ({ model: item.model, path: item.path, count: item.count, quotaPerCall: 1, quota: item.count, priceCnyPerCall: 0.01, amountCny: item.count * 0.01 })) }, balanceUrl: null } }), { status: 200 }); }; const estimate = await runVocCostEstimate({ newapiToken: 'sk-test', baseUrl: 'https://billing.test', workflowType: 'tmall_listing_audit', productCount: 134 }); assert.equal(estimate.status, 'ok'); assert.equal(estimate.data.plans.length, 3); assert.equal(estimate.data.plans[0].assumptions.shopListPages, 5); assert.doesNotMatch(JSON.stringify(estimate), /sk-test/); const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'voc-usage-smoke-')); const usagePath = path.join(tmp, 'usage.json'); fs.writeFileSync(usagePath, JSON.stringify({ usageTraceId: 'usage_smoke', usage: { quality: 'exact', totals: { calls: 2, billedCalls: 2, quota: 20, costCny: 0.2 }, items: [{ path: 'xhs.search', calls: 2, quota: 20, costCny: 0.2 }] } })); const usage = await runVocUsageReport({ reportPath: usagePath }); assert.equal(usage.status, 'ok'); assert.equal(usage.summary.costCny, 0.2); global.fetch = oldFetch; console.log('cost estimate and usage report smoke passed'); } main().catch(error => { console.error(error); process.exitCode = 1; });