import assert from 'node:assert/strict' import test from 'node:test' import { activateCommercialEntitlement, activateCommercialPlan, activateCommercialSubscription, buildCommercialAnalyticsSearch, buildCommercialPricingScenario, createCommercialPlan, createCommercialSubscription, fetchCommercialAccount, fetchCommercialAnalytics, fetchCommercialCostEvents, fetchCommercialEntitlements, fetchCommercialPlans, fetchCommercialSubscriptions, fetchCommercialUsageEvents, normalizeCommercialPayload, recordCommercialCost, recordCommercialUsage, serializeCommercialPayload, transitionCommercialSubscription, upsertCommercialEntitlement } from '../src/services/commercial.js' function installFetch(handler) { const original = globalThis.fetch globalThis.fetch = handler return () => { globalThis.fetch = original } } function jsonResponse(payload, status = 200) { return new Response(JSON.stringify(payload), { status, headers: { 'content-type': 'application/json' } }) } test('商业接口递归转换 snake_case 与 camelCase 且保留数组和金额字符串', () => { const normalized = normalizeCommercialPayload({ data_status: 'partial', customer_roi: { required_inputs: ['收费'], ratios: [{ reporting_currency: 'CNY', ratio: '1.20' }] } }) assert.equal(normalized.dataStatus, 'partial') assert.equal(normalized.customerRoi.requiredInputs[0], '收费') assert.equal(normalized.customerRoi.ratios[0].reportingCurrency, 'CNY') assert.equal(normalized.customerRoi.ratios[0].ratio, '1.20') assert.deepEqual(serializeCommercialPayload({ planCode: 'enterprise', configJson: { billingMode: 'overage' }, requiredInputs: ['invoice'] }), { plan_code: 'enterprise', config_json: { billing_mode: 'overage' }, required_inputs: ['invoice'] }) }) test('普通账号只读取自身账户,平台管理员显式选择租户后才使用跨租户路径', async () => { const urls = [] const restore = installFetch(async (url) => { urls.push(String(url)) return jsonResponse({ tenant_id: 'tenant-a', data_status: 'unavailable', quotas: [] }) }) try { await fetchCommercialAccount({ platformAdmin: false, tenantId: 'should-not-leak' }) await fetchCommercialAccount({ platformAdmin: true, tenantId: 'tenant/a', asOf: '2026-07-16T00:00:00Z' }) } finally { restore() } assert.equal(urls[0], '/api/v1/commercial/account') assert.match(urls[1], /\/commercial\/admin\/tenants\/tenant%2Fa\/account\?as_of=2026-07-16T00%3A00%3A00Z$/u) }) test('商业分析时间参数显式序列化且不附加客户端汇率或合计字段', async () => { const search = buildCommercialAnalyticsSearch({ start: '2026-04-01T00:00:00Z', end: '2026-07-01T00:00:00Z', asOf: '2026-07-01T00:00:00Z', fxRate: '7.2' }) assert.equal(search.get('as_of'), '2026-07-01T00:00:00Z') assert.equal(search.has('fx_rate'), false) let requestedUrl = '' const restore = installFetch(async (url) => { requestedUrl = String(url) return jsonResponse({ tenant_id: 'tenant-a', data_quality_status: 'unavailable', customer_charges: {}, internal_costs: {}, contribution_margin: {}, verified_cash_savings: {}, customer_roi: {}, customer_labor_value: {} }) }) try { await fetchCommercialAnalytics('tenant-a', Object.fromEntries(search)) } finally { restore() } assert.match(requestedUrl, /\/commercial\/admin\/tenants\/tenant-a\/analytics/u) assert.match(requestedUrl, /start=2026-04-01T00%3A00%3A00Z/u) }) test('定价场景使用平台租户 POST 契约且只提交服务端 schema 字段', async () => { let request = null const restore = installFetch(async (url, options = {}) => { request = { url: String(url), method: options.method, body: JSON.parse(options.body) } return jsonResponse({ tenant_id: 'tenant-a', recommended_model: 'pilot_collecting', evidence_status: 'unavailable', scenarios: [], notes: ['不会自动修改套餐'] }) }) try { const response = await buildCommercialPricingScenario('tenant/a', { start: '2026-04-01T00:00:00Z', end: '2026-07-01T23:59:59Z', asOf: '2026-07-02T00:00:00Z', targetContributionMarginRate: '0.65', maxVerifiedSavingsShare: '0.25' }) assert.equal(response.recommendedModel, 'pilot_collecting') } finally { restore() } assert.equal(request.method, 'POST') assert.match(request.url, /\/commercial\/admin\/tenants\/tenant%2Fa\/pricing-scenarios$/u) assert.deepEqual(Object.keys(request.body).sort(), [ 'as_of', 'end', 'max_verified_savings_share', 'start', 'target_contribution_margin_rate' ]) assert.equal(request.body.target_contribution_margin_rate, '0.65') assert.equal(request.body.max_verified_savings_share, '0.25') }) test('商业写接口覆盖套餐、订阅、权益、激活、用量与成本并统一递归序列化', async () => { const requests = [] const restore = installFetch(async (url, options = {}) => { requests.push({ url: String(url), method: options.method, headers: options.headers, body: JSON.parse(options.body) }) return jsonResponse({ id: 'resource-1', created: true }) }) try { await createCommercialPlan('tenant-a', { planCode: 'enterprise', contractTermsJson: { approvedBy: 'cfo' } }) await activateCommercialPlan('tenant-a', 'plan-1', 2, '启用新套餐版本') await createCommercialSubscription('tenant-a', { subscriptionKey: 'sub-1', autoRenew: true }) await activateCommercialSubscription('tenant-a', 'sub-1', 3, '恢复客户订阅') await transitionCommercialSubscription('tenant-a', 'sub-1', { expectedVersion: 4, targetStatus: 'suspended', reason: '合同暂停' }) await upsertCommercialEntitlement('tenant-a', { entitlementKey: 'ocr', configJson: { billingMode: 'all_usage' } }) await activateCommercialEntitlement('tenant-a', 'ent-1', 4, '恢复 OCR 权益') await recordCommercialUsage('tenant-a', { idempotencyKey: 'usage-1', subjectType: 'claim' }) await recordCommercialCost('tenant-a', { idempotencyKey: 'cost-1', originalCurrency: 'CNY' }) } finally { restore() } assert.deepEqual(requests.map((item) => item.method), ['POST', 'POST', 'POST', 'POST', 'POST', 'PUT', 'POST', 'POST', 'POST']) assert.match(requests[0].url, /\/plans$/u) assert.equal(requests[0].body.contract_terms_json.approved_by, 'cfo') assert.equal(requests[1].body.expected_version, 2) assert.equal(requests[1].body.reason, '启用新套餐版本') assert.ok(requests.every((item) => item.headers['X-Request-Id'])) assert.equal(requests[2].body.auto_renew, true) assert.match(requests[4].url, /\/subscriptions\/sub-1\/transition$/u) assert.equal(requests[4].body.target_status, 'suspended') assert.equal(requests[5].body.config_json.billing_mode, 'all_usage') assert.match(requests[7].url, /\/usage-events$/u) assert.match(requests[8].url, /\/cost-events$/u) }) test('五类商业历史按最终分资源契约读取并正确序列化筛选', async () => { const urls = [] const restore = installFetch(async (url) => { urls.push(String(url)) return jsonResponse([]) }) try { await fetchCommercialPlans('tenant-a', { planStatus: 'active', limit: 20, offset: 1 }) await fetchCommercialSubscriptions('tenant-a', { subscriptionStatus: 'suspended' }) await fetchCommercialEntitlements('tenant-a', { subscriptionId: 'sub-1', entitlementStatus: 'active' }) await fetchCommercialUsageEvents('tenant-a', { subscriptionId: 'sub-1', start: '2026-07-01T00:00:00Z', end: '2026-08-01T00:00:00Z' }) await fetchCommercialCostEvents('tenant-a', { limit: 50 }) } finally { restore() } assert.match(urls[0], /\/plans\?plan_status=active&limit=20&offset=1$/u) assert.match(urls[1], /\/subscriptions\?subscription_status=suspended$/u) assert.match(urls[2], /\/entitlements\?subscription_id=sub-1&entitlement_status=active$/u) assert.match(urls[3], /\/usage-events\?subscription_id=sub-1&start=.*&end=.*/u) assert.match(urls[4], /\/cost-events\?limit=50$/u) }) test('后端权限和冲突错误保留状态与可执行提示', async () => { const restore = installFetch(async () => jsonResponse({ detail: '只有平台管理员可以执行该操作。' }, 403)) try { await assert.rejects( () => fetchCommercialAnalytics('tenant-a'), (error) => error.status === 403 && /平台管理员/u.test(error.message) ) } finally { restore() } })