Files
one-pipe-system/scripts/tests/octoberBatchOne.test.mjs
luo c31183205d
All checks were successful
构建并部署前端到测试环境 / deploy (push) Successful in 56s
fix: some
2026-10-13 17:21:46 +08:00

365 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import test from 'node:test'
import assert from 'node:assert/strict'
import { readFile } from 'node:fs/promises'
import { resolve } from 'node:path'
import { createRequire } from 'node:module'
const require = createRequire(import.meta.url)
const esbuild = createRequire(require.resolve('vite'))('esbuild')
async function loadModule(entry, stubService = false) {
const result = await esbuild.build({
entryPoints: [entry],
bundle: true,
write: false,
format: 'cjs',
platform: 'node',
alias: { '@': resolve('src') },
plugins: stubService
? [
{
name: 'stub-base-service',
setup(build) {
build.onResolve({ filter: /BaseService$/ }, () => ({
path: 'base-service',
namespace: 'stub'
}))
build.onLoad({ filter: /.*/, namespace: 'stub' }, () => ({
contents: `
export class BaseService {
static calls = [];
static put(url, data, config) { this.calls.push({url, data, config}); return Promise.resolve({code:0, data:{}}); }
static update(url, data) { return this.put(url, data); }
static post(url, data, config) { this.calls.push({url, data, config}); return Promise.resolve({code:0, data:{}}); }
}`
}))
}
}
]
: []
})
const module = { exports: {} }
new Function('require', 'module', 'exports', result.outputFiles[0].text)(
require,
module,
module.exports
)
return module.exports
}
test('new login password accepts only 8-12 ASCII letters/digits with all three classes', async () => {
const { getNewLoginPasswordError, createNewLoginPasswordRule } = await loadModule(
'src/utils/validation/loginPassword.ts'
)
for (const password of ['Abc12345', 'Abc123456789'])
assert.equal(getNewLoginPasswordError(password), undefined)
for (const password of [
'Abc1234',
'Abc123456789X',
'abcd1234',
'ABCD1234',
'AbcDefGh',
'Abc1234!',
'Abc1234_',
'Abc1234 ',
' Abc12345',
'Abc1234\n',
'Abc1234中',
'Abc123😀',
'Abc12345'
]) {
assert.ok(getNewLoginPasswordError(password), `must reject ${JSON.stringify(password)}`)
}
const rule = createNewLoginPasswordRule()
for (const value of ['', undefined, null])
rule.validator({}, value, (error) => assert.equal(error, undefined))
rule.validator({}, 'Abc1234!', (error) => assert.ok(error instanceof Error))
})
test('account password requests use the right field and omit unchanged optional password', async () => {
const { AccountService } = await loadModule('src/api/modules/account.ts', true)
AccountService.calls = []
await AccountService.updateAccountPassword(12, 'Abc12345')
assert.deepEqual(AccountService.calls[0].data, { new_password: 'Abc12345' })
await AccountService.updateAccount(12, { username: 'operator', password: '' })
assert.deepEqual(AccountService.calls[1].data, { username: 'operator' })
await assert.rejects(AccountService.updateAccount(12, { password: 'Abc1234!' }))
await assert.rejects(AccountService.updateAccountPassword(12, 'abcd1234'))
assert.equal(AccountService.calls.length, 2)
})
test('writeoff terminal failure blocks recovery but allows resubmission; unknown result stays recoverable', async () => {
const module = await loadModule(
'src/views/finance/employee-collection/employeeCollectionDisplay.ts'
)
assert.equal(module.canRecoverApplicationApproval({ status: 4, recoverable: true }), false)
assert.equal(module.canResubmitApplication({ status: 4 }), true)
assert.equal(
module.canRecoverApplicationApproval({
status: 0,
approval_submission_status: 4,
recoverable: true
}),
true
)
assert.equal(module.canRecoverApplicationApproval({ status: 0, recoverable: false }), false)
assert.equal(module.getApplicationStatusTagType(4), 'danger')
for (const [value, label] of [
[0, '待提交'],
[1, '提交中'],
[2, '已提交'],
[3, '提交失败'],
[4, '结果未知']
]) {
assert.equal(module.getApplicationSubmissionLabel({ approval_submission_status: value }), label)
}
})
test('refund submission failure uses resubmit, not recovery, without changing status 6 anomaly handling', async () => {
const module = await loadModule('src/config/constants/refund.ts')
assert.equal(module.canRecoverRefundApproval({ status: 7, approval_recoverable: true }), false)
assert.equal(module.canResubmitRefund({ status: 7 }), true)
assert.equal(module.canResubmitRefund({ status: 3 }), true)
assert.equal(
module.canRecoverRefundApproval({
status: 1,
submission_status: 4,
approval_recoverable: true
}),
true
)
assert.equal(module.canResubmitRefund({ status: 6, anomaly_flag: 1 }), false)
assert.equal(module.canResubmitRefund({ status: 6, anomaly_flag: 0 }), true)
assert.equal(module.RefundStatusNameMap[7], '审批提交失败')
})
test('admin agent registration does not recover terminal failures and provides status fallback', async () => {
const module = await loadModule('src/types/api/agentDistributionRegistration.ts')
assert.equal(module.canRecoverAgentRegistration({ status: 3, recoverable: true }), false)
assert.equal(module.canRecoverAgentRegistration({ status: 0, recoverable: true }), true)
assert.equal(module.getAgentRegistrationStatusName({ status: 3 }), '审批提交失败')
})
test('offline recharge failure keeps read actions and starts a new order only with existing role/permission', async () => {
const { buildAgentRechargeActions } = await loadModule(
'src/views/finance/agent-recharge/agentRechargeActions.ts'
)
const row = { id: 8, status: 7, payment_method: 'offline', payment_voucher_key: ['voucher'] }
let restarted = false
const options = {
hasAuth: () => true,
canCreateOffline: true,
onRestart: () => {
restarted = true
},
onViewPaymentVoucher() {},
onConfirmPayment() {},
onReject() {},
onTriggerApproval() {}
}
const actions = buildAgentRechargeActions(row, options)
assert.deepEqual(
actions.map((action) => action.label),
['支付凭证', '重新发起']
)
actions[1].handler()
assert.equal(restarted, true)
assert.deepEqual(
buildAgentRechargeActions(row, { ...options, canCreateOffline: false }).map(
(action) => action.label
),
['支付凭证']
)
assert.deepEqual(buildAgentRechargeActions(row, { ...options, hasAuth: () => false }), [])
})
test('withdrawal failure is independently typed from commission clawback and supports resubmit', async () => {
const module = await loadModule('src/config/constants/commission.ts')
assert.equal(module.WithdrawalStatusMap[5].label, '审批提交失败')
assert.equal(module.canResubmitWithdrawal(5), true)
assert.equal(module.canResubmitWithdrawal(3), true)
assert.equal(module.canResubmitWithdrawal(1), false)
assert.equal(module.getCommissionStatusLabel(5), '回溯')
assert.equal(
module.WITHDRAWAL_QUALIFICATION_STATUS_OPTIONS.find((item) => item.value === 4).label,
'审批提交失败'
)
})
test('order payment record status is independent from order status and channels preserve unknown codes', async () => {
const module = await loadModule('src/utils/business/orderPayment.ts')
assert.equal(module.getOrderPaymentRecordStatusName(0), '待支付')
assert.equal(module.getOrderPaymentRecordStatusName(1), '已支付')
assert.equal(module.getOrderPaymentRecordStatusName(2), '已失败')
assert.equal(module.getOrderPaymentRecordStatusName(3), '已退款')
assert.equal(module.getOrderPaymentRecordTagType(2), 'danger')
assert.equal(module.getOrderPaymentChannelName('wechat'), '微信直连 v3')
assert.equal(module.getOrderPaymentChannelName('wechat_v2'), '微信直连 v2')
assert.equal(module.getOrderPaymentChannelName('fuiou'), '富友')
assert.equal(module.getOrderPaymentChannelName(''), '-')
assert.equal(module.getOrderPaymentChannelName('future-channel'), 'future-channel')
})
test('exchange phone conflicts support HTTP error and business response without inventing a missing number', async () => {
const module = await loadModule('src/utils/business/exchangeError.ts')
const failure = { code: 1202, data: { phone: '13800000000' } }
assert.match(module.getExchangePhoneConflictMessage(failure), /13800000000/)
assert.equal(
module.getExchangePhoneConflictMessage({ response: { data: failure } }),
module.getExchangePhoneConflictMessage(failure)
)
assert.match(module.getExchangePhoneConflictMessage({ code: 1202 }), /请先到/)
assert.doesNotMatch(module.getExchangePhoneConflictMessage({ code: 1202 }), /undefined|null/)
assert.equal(module.getExchangePhoneConflictMessage({ code: 1206 }), undefined)
assert.equal(
module.getExchangeBusinessErrorMessage({ response: { data: { message: '迁移失败' } } }),
'迁移失败'
)
const { ExchangeService } = await loadModule('src/api/modules/exchange.ts', true)
ExchangeService.calls = []
const config = { requestOptions: { errorMessageMode: 'none' } }
await ExchangeService.completeExchange(9, config)
assert.deepEqual(ExchangeService.calls[0], {
url: '/api/admin/exchanges/9/complete',
data: {},
config
})
})
test('exchange association handoff carries only the new asset and its bound phone as strings', async () => {
const { getExchangePhoneAssociationQuery } = await loadModule(
'src/utils/business/exchangeError.ts'
)
const newAsset = { asset_identifier: '89860624590014594318', associated_phones: ['19232713115'] }
assert.deepEqual(
getExchangePhoneAssociationQuery({ code: 1202, data: { phone: '19232713115' } }, newAsset),
{
asset_identifier: '89860624590014594318',
phone: '19232713115'
}
)
assert.deepEqual(
getExchangePhoneAssociationQuery({ response: { data: { code: 1202 } } }, newAsset),
{
asset_identifier: '89860624590014594318',
phone: '19232713115'
}
)
assert.equal(getExchangePhoneAssociationQuery({ code: 1206 }, newAsset), undefined)
assert.equal(
getExchangePhoneAssociationQuery({ code: 1202, data: { phone: '19232713115' } }, {}),
undefined
)
assert.equal(
getExchangePhoneAssociationQuery(
{ code: 1202 },
{ asset_identifier: '89860624590014594318', associated_phones: [] }
),
undefined
)
assert.deepEqual(
getExchangePhoneAssociationQuery({ code: 1202, data: { phone: '13900000000' } }, newAsset),
{
asset_identifier: '89860624590014594318',
phone: '13900000000'
}
)
})
test('association preselection excludes old assets, different phones and invalid historical rows', async () => {
const {
getPhoneAssetAssociationTarget,
getMatchingPhoneAssetAssociations,
getPhoneAssetUnbindResultName
} = await loadModule('src/utils/business/phoneAssetAssociation.ts')
const target = getPhoneAssetAssociationTarget({
asset_identifier: ' 89860624590014594318 ',
phone: '19232713115'
})
const rows = [
{ id: 1, asset_identifier: target.asset_identifier, phone: target.phone, status: 1 },
{ id: 2, asset_identifier: '89860625770070732445', phone: target.phone, status: 1 },
{ id: 3, asset_identifier: target.asset_identifier, phone: '13900000000', status: 1 },
{ id: 4, asset_identifier: target.asset_identifier, phone: target.phone, status: 0 }
]
assert.deepEqual(
getMatchingPhoneAssetAssociations(rows, target).map((row) => row.id),
[1]
)
assert.deepEqual(getMatchingPhoneAssetAssociations(rows, { ...target, phone: '' }), [])
assert.deepEqual(
getPhoneAssetAssociationTarget({ asset_identifier: ['a', 'b'], phone: ['19232713115'] }),
{ asset_identifier: '', phone: '' }
)
assert.equal(getPhoneAssetUnbindResultName(true), '解绑成功')
assert.equal(getPhoneAssetUnbindResultName(false), '解绑失败')
})
test('association page watches handoff changes, clears stale selections and retains manual unbind confirmation', async () => {
const page = await readFile(
'src/views/asset-management/phone-asset-association/index.vue',
'utf8'
)
assert.match(page, /getPhoneAssetAssociationTarget\(route.query\)/)
assert.match(page, /toggleRowSelection\(row, true\)/)
assert.match(page, /requestId !== listRequestId/)
assert.match(page, /getPhoneAssetUnbindResultName\(row.success\)/)
assert.doesNotMatch(page, /prop="success"/)
assert.match(page, /batchForm.confirmed = false/)
assert.match(page, /batchFormRef.value\?\.validate/)
const table = await readFile('src/components/core/tables/ArtTable.vue', 'utf8')
assert.match(table, /clearSelection:.*clearSelection\(\)/)
})
test('password validation errors accept backend message as well as msg', async () => {
const { normalizeApiError } = await loadModule('src/utils/business/apiError.ts')
for (const field of ['msg', 'message']) {
assert.equal(
normalizeApiError({
isAxiosError: true,
response: { status: 400, data: { [field]: '新密码不合法' } }
}).message,
'新密码不合法'
)
}
})
test('traffic snapshots preserve zero, missing values and MB precision', async () => {
const { formatTrafficAlertMb } = await loadModule('src/utils/business/trafficAlert.ts')
assert.equal(formatTrafficAlertMb(0), '0.00')
assert.equal(formatTrafficAlertMb(1234.567), '1234.57')
for (const value of [undefined, null, NaN]) assert.equal(formatTrafficAlertMb(value), '-')
})
test('CSV download has the new exact four-column header and preserves phone identifiers as text', async () => {
const { SHOP_BUSINESS_OWNER_CSV_TEMPLATE } = await loadModule(
'src/utils/business/shopBusinessOwnerImport.ts'
)
assert.equal(SHOP_BUSINESS_OWNER_CSV_TEMPLATE.charCodeAt(0), 0xfeff)
const [header, instruction] = SHOP_BUSINESS_OWNER_CSV_TEMPLATE.slice(1).trimEnd().split('\r\n')
assert.deepEqual(header.split(','), ['店铺编码', '操作类型', '平台登录账号', '备注'])
assert.equal(instruction.split(',').length, 4)
assert.match(instruction, /手机号/)
assert.match(instruction, /清空留空/)
assert.doesNotMatch(SHOP_BUSINESS_OWNER_CSV_TEMPLATE, /业务员登录账号/)
})
test('affected view contracts preserve raw payment history, package quota and device template', async () => {
const order = await readFile('src/views/order-management/order-list/detail.vue', 'utf8')
assert.match(order, /detailData\.payments \|\| \[\]/)
assert.doesNotMatch(order, /payments\.(filter|sort)/)
const rules = await readFile('src/views/package-management/traffic-alert-rules/index.vue', 'utf8')
assert.match(rules, /item\.real_data_mb/)
assert.doesNotMatch(rules, /row\.real_data_mb|prop: 'real_data_mb'/)
const devices = await readFile(
'src/views/asset-management/task-management/device-task/index.vue',
'utf8'
)
assert.ok(
/独立的 IMEI\s+列/.test(devices),
'import instructions must require the standalone IMEI column'
)
assert.match(devices, /@\/template\/设备导入模板\.xlsx/)
assert.match(devices, /failed_items/)
})