feat(platform): close AI expense value loop
Add tenant-safe value, telemetry, connector, commercial, and production-readiness foundations.
This commit is contained in:
140
web/tests/commercial-components-compile.test.mjs
Normal file
140
web/tests/commercial-components-compile.test.mjs
Normal file
@@ -0,0 +1,140 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import test from 'node:test'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import { compileScript, compileTemplate, parse } from '@vue/compiler-sfc'
|
||||
|
||||
const componentFiles = [
|
||||
'../src/components/commercial/CommercialWorkspace.vue',
|
||||
'../src/components/commercial/CommercialAccountOverview.vue',
|
||||
'../src/components/commercial/CommercialValueProofPanel.vue',
|
||||
'../src/components/commercial/CommercialPricingScenarioPanel.vue',
|
||||
'../src/components/commercial/CommercialMetricCard.vue',
|
||||
'../src/components/commercial/CommercialAdminConsole.vue',
|
||||
'../src/components/commercial/CommercialHistoryPanel.vue',
|
||||
'../src/components/commercial/CommercialMutationDialog.vue'
|
||||
]
|
||||
|
||||
for (const relativeFile of componentFiles) {
|
||||
test(`${relativeFile} 可以独立编译`, () => {
|
||||
const filename = fileURLToPath(new URL(relativeFile, import.meta.url))
|
||||
const source = readFileSync(filename, 'utf8')
|
||||
const parsed = parse(source, { filename })
|
||||
assert.deepEqual(parsed.errors, [])
|
||||
const id = `commercial-${relativeFile.replace(/\W+/gu, '-')}`
|
||||
const script = compileScript(parsed.descriptor, { id })
|
||||
const template = compileTemplate({
|
||||
source: parsed.descriptor.template?.content || '',
|
||||
filename,
|
||||
id,
|
||||
compilerOptions: { bindingMetadata: script.bindings }
|
||||
})
|
||||
assert.deepEqual(template.errors, [])
|
||||
})
|
||||
}
|
||||
|
||||
const workspace = readSource('../src/components/commercial/CommercialWorkspace.vue')
|
||||
const valuePanel = readSource('../src/components/commercial/CommercialValueProofPanel.vue')
|
||||
const pricingPanel = readSource('../src/components/commercial/CommercialPricingScenarioPanel.vue')
|
||||
const metricCard = readSource('../src/components/commercial/CommercialMetricCard.vue')
|
||||
const adminConsole = readSource('../src/components/commercial/CommercialAdminConsole.vue')
|
||||
const mutationDialog = readSource('../src/components/commercial/CommercialMutationDialog.vue')
|
||||
const historyPanel = readSource('../src/components/commercial/CommercialHistoryPanel.vue')
|
||||
const styles = readSource('../src/components/commercial/commercial-workspace.css')
|
||||
const overviewView = readSource('../src/views/OverviewView.vue')
|
||||
const appShell = readSource('../src/views/AppShellRouteView.vue')
|
||||
const topBarOverviewRange = readSource('../src/components/layout/useTopBarOverviewRange.js')
|
||||
const overviewModel = readSource('../src/composables/useOverviewView.js')
|
||||
|
||||
test('商业工作台具有显式租户选择、双账分离、权限和完整空错状态', () => {
|
||||
assert.match(workspace, /目标租户 ID/u)
|
||||
assert.match(workspace, /平台管理员必须显式选择租户/u)
|
||||
assert.match(workspace, /商业权益不能放宽安全规则/u)
|
||||
assert.match(valuePanel, /客户价值账/u)
|
||||
assert.match(valuePanel, /平台经营账/u)
|
||||
assert.match(valuePanel, /不同币种不会混算/u)
|
||||
assert.match(valuePanel, /!platformAdmin/u)
|
||||
assert.match(valuePanel, /role="alert"/u)
|
||||
assert.match(valuePanel, /这不是零成本或零价值/u)
|
||||
})
|
||||
|
||||
test('无真实财务证据显示不可用和缺失输入,不含演示或回退指标', () => {
|
||||
assert.match(metricCard, /commercial-unavailable-value">不可用/u)
|
||||
assert.match(metricCard, /查看缺失证据/u)
|
||||
assert.match(metricCard, /requiredInputs/u)
|
||||
assert.doesNotMatch(workspace, /demo|mock|fallback/iu)
|
||||
assert.doesNotMatch(valuePanel, /demo|mock|fallback/iu)
|
||||
})
|
||||
|
||||
test('定价场景具有参数确认、分币种走廊、部分可用与不写套餐语义', () => {
|
||||
assert.match(workspace, /CommercialPricingScenarioPanel/u)
|
||||
for (const label of ['目标贡献毛利率', '最大价值分享比例', '证据截止时间(as_of)', '确认计算条件']) {
|
||||
assert.match(pricingPanel, new RegExp(label, 'u'))
|
||||
}
|
||||
for (const label of ['可持续收费下限', '价值对齐收费上限', '成功费封顶', '推荐商业模式', '缺失输入']) {
|
||||
assert.match(pricingPanel, new RegExp(label, 'u'))
|
||||
}
|
||||
assert.match(pricingPanel, /v-for="row in view\.rows"/u)
|
||||
assert.match(pricingPanel, /row\.currency/u)
|
||||
assert.match(pricingPanel, /evidenceStatus/u)
|
||||
assert.match(pricingPanel, /不可用/u)
|
||||
assert.match(pricingPanel, /不会自动写入套餐/u)
|
||||
assert.match(pricingPanel, /aria-busy/u)
|
||||
assert.match(pricingPanel, /role="alert"/u)
|
||||
assert.match(pricingPanel, /inputmode="decimal"/u)
|
||||
})
|
||||
|
||||
test('所有商业写操作都有操作确认、忙碌态、幂等和版本化提示', () => {
|
||||
for (const label of ['创建套餐版本', '激活套餐版本', '创建订阅快照', '重新激活订阅', '变更订阅状态', '配置权益版本', '激活权益版本', '记录用量事件', '记录成本事件']) {
|
||||
assert.match(readSource('../src/components/commercial/commercialWorkspaceModel.js'), new RegExp(label, 'u'))
|
||||
}
|
||||
assert.match(adminConsole, /二次确认/u)
|
||||
assert.match(mutationDialog, /step === 'edit'/u)
|
||||
assert.match(mutationDialog, /确认写入/u)
|
||||
assert.match(mutationDialog, /幂等键/u)
|
||||
assert.match(mutationDialog, /版本化快照/u)
|
||||
assert.match(mutationDialog, /:disabled="busy"/u)
|
||||
assert.match(mutationDialog, /role="alertdialog"/u)
|
||||
})
|
||||
|
||||
test('五类历史有按需加载、刷新、空态与订阅状态操作入口', () => {
|
||||
for (const label of ['套餐版本', '订阅历史', '权益版本', '用量事件', '成本事件']) {
|
||||
assert.match(historyPanel, new RegExp(label, 'u'))
|
||||
}
|
||||
assert.match(historyPanel, /加载历史/u)
|
||||
assert.match(historyPanel, /刷新历史/u)
|
||||
assert.match(historyPanel, /这不是零用量或零成本/u)
|
||||
assert.match(historyPanel, /manage-subscription/u)
|
||||
assert.match(historyPanel, /变更状态/u)
|
||||
assert.match(historyPanel, /恢复/u)
|
||||
})
|
||||
|
||||
test('商业工作台满足键盘、触控、响应式和减少动画语义', () => {
|
||||
assert.match(styles, /min-height: 44px/u)
|
||||
assert.match(styles, /:focus-visible/u)
|
||||
assert.match(styles, /@media \(max-width: 760px\)/u)
|
||||
assert.match(styles, /@media \(prefers-reduced-motion: reduce\)/u)
|
||||
assert.match(mutationDialog, /@keydown\.esc/u)
|
||||
assert.match(mutationDialog, /aria-label="关闭对话框"/u)
|
||||
assert.match(pricingPanel, /tabindex="-1"/u)
|
||||
assert.match(pricingPanel, /:disabled="loading"/u)
|
||||
})
|
||||
|
||||
test('商业工作台已接入分析看板且不会误加载财务看板', () => {
|
||||
assert.match(overviewView, /CommercialWorkspace/u)
|
||||
assert.match(overviewView, /activeDashboard === 'commercial'/u)
|
||||
assert.match(overviewView, /currentUser: \{ type: Object/u)
|
||||
assert.match(appShell, /:current-user="currentUser"/u)
|
||||
assert.match(appShell, /'commercial'/u)
|
||||
assert.match(topBarOverviewRange, /\{ label: '商业化管理', value: 'commercial' \}/u)
|
||||
assert.match(overviewModel, /dashboard === 'commercial'/u)
|
||||
assert.match(
|
||||
overviewModel,
|
||||
/activeDashboardKey\.value === 'commercial' \|\| activeDashboardKey\.value === 'value'/u
|
||||
)
|
||||
})
|
||||
|
||||
function readSource(relativeFile) {
|
||||
return readFileSync(fileURLToPath(new URL(relativeFile, import.meta.url)), 'utf8')
|
||||
}
|
||||
Reference in New Issue
Block a user