Files
shanxia2.0/web-admin/scripts/quota-statistics.test.ts
T

292 lines
9.3 KiB
TypeScript

import test from 'node:test'
import assert from 'node:assert/strict'
import { createStatisticsLoader } from '../src/views/pl/quota-statistics/loader'
test('fixed contract skips global list and retains each decimal balance', async () => {
const calls: string[] = []
const loader = createStatisticsLoader({
contracts: async () => {
calls.push('global')
},
contract: async (id: string) => {
calls.push(id)
return { certificates: [{ availableQty: '0.10000001' }, { availableQty: '7.2' }] }
}
} as any)
const result = await loader.load({ mode: 'contract', contractId: 'c1', allowed: true })
assert.deepEqual(calls, ['c1'])
assert.deepEqual(
result.certificates.map((c: any) => c.availableQty),
['0.10000001', '7.2']
)
})
test('permission denied never requests statistics', async () => {
const loader = createStatisticsLoader(
new Proxy(
{},
{
get: () => () => {
throw Error('unexpected request')
}
}
) as any
)
assert.equal(await loader.load({ mode: 'contract', allowed: false }), undefined)
})
test('context invalidation discards pending response', async () => {
let resolve!: (v: any) => void
const loader = createStatisticsLoader({
contract: () =>
new Promise((r) => {
resolve = r
})
} as any)
const pending = loader.load({ mode: 'contract', contractId: 'old', allowed: true })
loader.invalidate()
resolve({ id: 'old' })
assert.equal(await pending, undefined)
})
import { readFileSync } from 'node:fs'
import { parse } from '@vue/compiler-sfc'
import ts from 'typescript'
import * as vue from 'vue'
import { plRoutes } from '../src/router/modules/pl'
import { usePersistedListColumns } from '../src/composables/usePersistedListColumns'
import { useServerColumnFilters } from '../src/composables/useServerColumnFilters'
const flush = async () => {
for (let i = 0; i < 30; i++) await Promise.resolve()
}
function deferred() {
let resolve!: (value: any) => void, reject!: (error: any) => void
const promise = new Promise((a, b) => {
resolve = a
reject = b
})
return { promise, resolve, reject }
}
function harness(fixed?: string) {
const calls: any[] = [],
permissions: any[] = [],
unmount: Function[] = []
const props = vue.reactive({
mode: 'contract',
contractId: fixed,
active: true,
embedded: !!fixed
})
const user = vue.reactive({ accessToken: 'token', getUserInfo: { tenantNo: 'a' } })
const queue = (method: string, args: any[]) => {
const pending = deferred()
calls.push({ method, args, ...pending })
return pending.promise
}
const api = Object.fromEntries(
['contracts', 'contract', 'certificates', 'certificate'].map((method) => [
method,
(...args: any[]) => queue(method, args)
])
)
const env = {
...Object.fromEntries(
Object.entries(vue).filter(([key]) => /^[a-zA-Z_$][\w$]*$/.test(key) && key !== 'default')
),
defineProps: () => props,
withDefaults: (v: any) => v,
useUserStore: () => user,
fetchGetMenuCodes: () => {
const p = deferred()
permissions.push(p)
return p.promise
},
useListTableViewportHeight: () => ({ tableHeight: vue.ref(400) }),
usePersistedListColumns,
useServerColumnFilters,
createStatisticsLoader,
quotaStatisticsApi: api,
onBeforeUnmount: (fn: Function) => unmount.push(fn)
}
const src = ts.createSourceFile(
'panel.ts',
parse(
readFileSync(
new URL('../src/views/pl/quota-statistics/StatisticsPanel.vue', import.meta.url),
'utf8'
)
).descriptor.scriptSetup!.content,
ts.ScriptTarget.Latest,
true
)
const code = ts.transpile(
src.statements
.filter((s) => !ts.isImportDeclaration(s))
.map((s) => s.getText(src))
.join('\n'),
{ target: ts.ScriptTarget.ESNext }
)
const scope = vue.effectScope()
const state = scope.run(() =>
new Function(
...Object.keys(env),
`${code}; return { rows, fixedDetail, details, load, loadDetail, allowed, canEdit }`
)(...Object.values(env))
)
return {
state,
calls,
permissions,
props,
user,
stop: () => {
unmount.forEach((fn) => fn())
scope.stop()
}
}
}
test('five leaves retain IDs and individual permissions', () => {
const group = plRoutes[0].children!.find((r) => r.id === 'PL_GROUP_CERT')!
assert.deepEqual(
group.children!.map((r) => r.path),
['quota-cert', 'contract-quota', 'certificate-usage', 'plan-cert', 'legal-use-document']
)
assert.equal(group.children![0].id, 'PL_QUOTA_CERT')
assert.equal(group.children![3].id, 'PL00000000000000000000000000015')
assert.deepEqual(
group.children!.map((r) => r.meta!.menuCode),
[
'pl:quota-cert:list',
'pl:quota-cert:list',
'pl:quota-cert:list',
'pl:plan-cert:list',
'pl:legal-use-document:list'
]
)
})
test('actual fixed panel loads only current contract and drops old response', async () => {
const h = harness('one')
h.permissions[0].resolve({ menuCodes: ['pl:quota-cert:list'] })
await flush()
assert.equal(h.calls[0].method, 'contract')
assert.deepEqual(h.calls[0].args, ['one'])
h.props.contractId = 'two'
h.permissions[1].resolve({ all: true })
await flush()
h.calls[1].resolve({ id: 'two', certificates: [] })
await flush()
h.calls[0].resolve({ id: 'one', certificates: [] })
await flush()
assert.equal(h.state.fixedDetail.value.id, 'two')
h.stop()
})
for (const change of ['tenant', 'logout', 'unmount'])
test(`actual panel invalidates data on ${change}`, async () => {
const h = harness()
h.permissions[0].resolve({ all: true })
await flush()
if (change === 'tenant') h.user.getUserInfo.tenantNo = 'b'
if (change === 'logout') h.user.accessToken = ''
if (change === 'unmount') h.stop()
h.calls[0].resolve({ list: [{ id: 'stale' }], total: 1 })
await flush()
assert.deepEqual(h.state.rows.value, [])
assert.equal(h.state.allowed.value, false)
h.stop()
})
test('proof-only permission cannot request quota', async () => {
const h = harness()
h.permissions[0].resolve({ menuCodes: ['pl:legal-use-document:list'] })
await flush()
assert.equal(h.calls.length, 0)
h.stop()
})
test('expand failure clears stale data and retry works', async () => {
const h = harness()
h.permissions[0].resolve({ all: true })
await flush()
h.calls[0].resolve({ list: [{ id: 'c' }], total: 1 })
await flush()
const first = h.state.loadDetail({ id: 'c' })
h.calls[1].resolve({ id: 'c', certificates: [] })
await first
assert.equal(h.state.details.c.data.id, 'c')
const failure = h.state.loadDetail({ id: 'c' })
h.calls[2].reject(Error('failed'))
await failure
assert.equal(h.state.details.c.data, undefined)
assert.equal(h.state.details.c.error, true)
const retry = h.state.loadDetail({ id: 'c' })
h.calls[3].resolve({ id: 'c', certificates: [] })
await retry
assert.equal(h.state.details.c.data.id, 'c')
h.stop()
})
test('statistics API maps each view to the server endpoint and preserves response decimals', async () => {
const calls: any[] = []
const raw = { list: [{ availableQty: '1.00000001' }], total: 1 }
const source = ts.createSourceFile(
'api.ts',
readFileSync(new URL('../src/api/pl/quota-cert.ts', import.meta.url), 'utf8'),
ts.ScriptTarget.Latest,
true
)
const code = ts.transpile(
source.statements
.filter((s) => !ts.isImportDeclaration(s))
.map((s) => s.getText(source).replace(/^export /, ''))
.join('\n'),
{ target: ts.ScriptTarget.ESNext }
)
const api = new Function('request', 'pickBxData', `${code};return quotaStatisticsApi`)(
{
get: (url: string, options: any) => {
calls.push({ url, options })
return Promise.resolve(raw)
}
},
(v: any) => v
)
assert.equal(
(await api.contracts({ page: 2, keyword: '合同' })).list[0].availableQty,
'1.00000001'
)
await api.certificates({ certType: 'transport', pageSize: 20 })
await api.contract('c')
await api.certificate('purchase', 'p')
assert.deepEqual(
calls.map((c) => c.url),
[
'/transport/quota-certs/statistics/contracts',
'/transport/quota-certs/statistics/certificates',
'/transport/quota-certs/contracts/c/statistics',
'/transport/quota-certs/purchase/p/statistics'
]
)
assert.deepEqual(calls[1].options.params, { certType: 'transport', pageSize: 20 })
})
import * as statisticsLabels from '../src/views/pl/quota-statistics/labels'
test('contract type tags translate all server values and legacy framework', () => {
const template = parse(
readFileSync(
new URL('../src/views/pl/quota-statistics/StatisticsPanel.vue', import.meta.url),
'utf8'
)
).descriptor.template!.content
const expression = template.match(
/v-else-if="column.prop === 'contractType'"[^>]*>\s*\{\{([\s\S]*?)\}\}/
)![1]
const renderLabel = new Function(
'row',
...Object.keys(statisticsLabels),
`return (${expression})`
)
for (const [contractType, label] of Object.entries({
sale: '销售合同',
purchase: '采购合同',
framework_sale: '框架销售合同',
framework_purchase: '框架采购合同',
framework: '框架合同',
'': '—'
})) {
assert.equal(renderLabel({ contractType }, ...Object.values(statisticsLabels)), label)
}
})