import test from 'node:test'; import assert from 'node:assert/strict'; import { billingAdminConfigInternals, createBillingAdminConfigService, toComputeBillingConfig, toCostEstimateConfig, } from './billing-admin-config.mjs'; function createMemoryPool(initialRows = [], userFormulas = new Map()) { const rows = new Map( initialRows.map((row) => [row.config_scope, { ...row }]), ); return { async query(sql, params = []) { if (/CREATE TABLE/i.test(sql)) return [{}, undefined]; if (/SELECT billing_formula FROM h5_users/i.test(sql)) { const userId = params[0]; return [[{ billing_formula: userFormulas.get(userId) ?? 'A' }], undefined]; } if (/SELECT/i.test(sql) && /config_scope IN/i.test(sql)) { const scopes = params.slice(0, 3); return [scopes.map((scope) => rows.get(scope)).filter(Boolean), undefined]; } if (/SELECT/i.test(sql)) { const scope = params[0]; const row = rows.get(scope); return [row ? [row] : [], undefined]; } if (/INSERT INTO/i.test(sql)) { const [scope, configJson, updatedBy, updatedAt] = params; rows.set(scope, { config_scope: scope, config_json: configJson, updated_by: updatedBy, updated_at: updatedAt, }); return [{ affectedRows: 1 }, undefined]; } if (/UPDATE h5_users SET billing_formula/i.test(sql)) { const [formula, , ...ids] = params; ids.forEach((id) => userFormulas.set(id, formula)); return [{ affectedRows: ids.length }, undefined]; } return [{}, undefined]; }, _rows: rows, _userFormulas: userFormulas, }; } test('mergePatch validates margin and FX', () => { const next = billingAdminConfigInternals.mergePatch( billingAdminConfigInternals.defaultConfigShape({}), { marginMultiplier: 1.5, usdCnyRate: 7.1, useBackendCost: true, }, {}, ); assert.equal(next.marginMultiplier, 1.5); assert.equal(next.usdCnyRate, 7.1); assert.equal(next.useBackendCost, true); }); test('toComputeBillingConfig maps admin shape', () => { const compute = toComputeBillingConfig({ useBackendCost: true, usdCnyRate: 7.2, marginMultiplier: 1.2, inputCentsPer1k: 2, outputCentsPer1k: 6, minBillCents: 1, }); assert.deepEqual(compute, { useBackendCost: true, usdCnyRate: 7.2, marginMultiplier: 1.2, inputCentsPer1k: 2, outputCentsPer1k: 6, minBillCents: 1, }); }); test('toCostEstimateConfig disables when cost mode off', () => { const estimate = toCostEstimateConfig({ useBackendCost: false, costEstimateFromTokens: true, costEstimateInputUsdPer1M: 0.27, costEstimateOutputUsdPer1M: 1.1, }); assert.equal(estimate.enabled, false); }); test('admin-db config wins over env for effective billing', async () => { const pool = createMemoryPool(); const env = { H5_USE_BACKEND_COST: '1', H5_USD_CNY_RATE: '7.2', H5_MARGIN_MULTIPLIER: '1.2', }; const service = createBillingAdminConfigService(pool, { env, cacheTtlMs: 0 }); await service.updateAdminConfig( { marginMultiplier: 2, usdCnyRate: 7.5, useBackendCost: true }, { updatedBy: 'admin-1', formula: 'A' }, ); const effective = await service.getEffectiveBillingConfig({ userId: 'user-a' }); assert.equal(effective.marginMultiplier, 2); assert.equal(effective.usdCnyRate, 7.5); const admin = await service.getAdminConfig(); assert.equal(admin.source, 'admin-db'); assert.equal(admin.updatedBy, 'admin-1'); assert.equal(admin.formulas?.A.marginMultiplier, 2); }); test('formula A and B can diverge', async () => { const pool = createMemoryPool([], new Map([['user-b', 'B']])); const service = createBillingAdminConfigService(pool, { env: {}, cacheTtlMs: 0 }); await service.updateAdminConfig({ marginMultiplier: 1.1 }, { formula: 'A' }); await service.updateAdminConfig({ marginMultiplier: 2.2 }, { formula: 'B' }); const admin = await service.getAdminConfig(); assert.equal(admin.formulas.A.marginMultiplier, 1.1); assert.equal(admin.formulas.B.marginMultiplier, 2.2); const userA = await service.getEffectiveBillingConfig({ userId: 'user-a' }); const userB = await service.getEffectiveBillingConfig({ userId: 'user-b' }); assert.equal(userA.marginMultiplier, 1.1); assert.equal(userB.marginMultiplier, 2.2); }); test('assignBillingFormula updates users', async () => { const pool = createMemoryPool(); const service = createBillingAdminConfigService(pool, { env: {}, cacheTtlMs: 0 }); const result = await service.assignBillingFormula(['u1', 'u2'], 'B'); assert.equal(result.ok, true); assert.equal(result.updated, 2); assert.equal(pool._userFormulas.get('u1'), 'B'); }); test('H5_BILLING_CONFIG_SOURCE=env locks admin writes', async () => { const pool = createMemoryPool(); const service = createBillingAdminConfigService(pool, { env: { H5_BILLING_CONFIG_SOURCE: 'env', H5_MARGIN_MULTIPLIER: '1.2' }, cacheTtlMs: 0, }); await assert.rejects( () => service.updateAdminConfig({ marginMultiplier: 9 }), /H5_BILLING_CONFIG_SOURCE=env/, ); const admin = await service.getAdminConfig(); assert.equal(admin.source, 'env-override'); assert.equal(admin.config.marginMultiplier, 1.2); }); test('env fallback used when no admin row', async () => { const pool = createMemoryPool(); const service = createBillingAdminConfigService(pool, { env: { H5_USE_BACKEND_COST: '1', H5_MARGIN_MULTIPLIER: '1.2', H5_USD_CNY_RATE: '7.2', }, cacheTtlMs: 0, }); const admin = await service.getAdminConfig(); assert.equal(admin.config.marginMultiplier, 1.2); assert.equal(admin.config.useBackendCost, true); assert.equal(admin.formulas.A.marginMultiplier, 1.2); assert.equal(admin.formulas.B.marginMultiplier, 1.2); });