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

633 lines
26 KiB
JavaScript
Raw Permalink 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'
import ts from 'typescript'
import * as vue from 'vue'
import { compileScript, parse } from '@vue/compiler-sfc'
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 allows special characters and requires at least three of four classes', async () => {
const { getNewLoginPasswordError, createNewLoginPasswordRule } = await loadModule(
'src/utils/validation/loginPassword.ts'
)
for (const password of [
'Abc12345',
'Abc123456789',
'Abc1234!',
'Abc1234_',
'ABC1234!',
'abc1234!',
'Abcdefg!',
'Ab1~`-=[]{}',
'Ab1;:\\|,<>/?'
])
assert.equal(getNewLoginPasswordError(password), undefined)
for (const password of [
'Abc1234',
'Abc123456789X',
'abcd1234',
'ABCD1234',
'AbcDefGh',
'ABCDEF!!',
'abcdef!!',
'123456!!',
'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.equal(error, undefined))
rule.validator({}, 'abcdef!!', (error) => assert.ok(error instanceof Error))
for (const symbol of Array.from('!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~')) {
assert.equal(getNewLoginPasswordError(`Ab12345${symbol}`), undefined)
}
})
test('admin and scan-registration new-password forms share validation, hints and the 12-character input limit', async () => {
const forms = [
{
path: 'src/components/core/layouts/art-header-bar/ChangePasswordDialog.vue',
models: ['formData.new_password', 'formData.confirm_password'],
fields: ['new_password', 'confirm_password'],
hint: "t('passwordChange.passwordRule')"
},
{
path: 'src/views/account-management/account/index.vue',
models: ['formData.password'],
fields: ['password']
},
{
path: 'src/views/common/account-list.vue',
models: ['accountForm.password'],
fields: ['password']
},
{
path: 'src/views/shop-management/list/index.vue',
models: ['formData.init_password'],
fields: ['init_password']
},
{
path: 'src/views/account-management/enterprise-customer/index.vue',
models: ['form.password', 'passwordForm.password'],
fields: ['password', 'password']
},
{
path: 'src/views/agent-registration/index.vue',
models: ['form.password'],
fields: ['password'],
hint: null
}
]
for (const { path, models, fields, hint = 'NEW_LOGIN_PASSWORD_HINT' } of forms) {
const source = await readFile(path, 'utf8')
assert.match(source, /from '@\/utils\/validation\/loginPassword'/, path)
const inputs = [...source.matchAll(/<ElInput\b[\s\S]*?\/>/g)].map(([input]) => input)
for (const model of models) {
// Exact v-model also rejects a silent .trim modifier on password inputs.
const matches = inputs.filter((input) => input.includes(`v-model="${model}"`))
assert.equal(matches.length, 1, `${path}: ${model}`)
assert.match(matches[0], /type="password"/, `${path}: ${model}`)
assert.match(matches[0], /maxlength="12"/, `${path}: ${model}`)
if (hint) {
assert.ok(matches[0].includes(`:placeholder="${hint}"`), `${path}: ${model} hint`)
} else {
assert.match(matches[0], /aria-describedby="registration-password-rule"/, path)
assert.match(source, /id="registration-password-rule"[\s\S]*?NEW_LOGIN_PASSWORD_HINT/, path)
}
}
const rules = [
...source.matchAll(
/\b(password|new_password|confirm_password|init_password):\s*\[([\s\S]*?)\n\s*\]/g
)
]
assert.deepEqual(rules.map(([, field]) => field).sort(), [...fields].sort(), path)
for (const [, field, rule] of rules) {
assert.match(rule, /required: true/, `${path}: ${field}`)
assert.match(rule, /createNewLoginPasswordRule\(/, `${path}: ${field}`)
assert.doesNotMatch(rule, /\b(?:min|max|pattern):/, `${path}: ${field} stale rule`)
}
}
})
test('scan registration validates password and locally handles errors without changing public endpoint, payload or SMS behavior', async () => {
const { AgentDistributionService } = await loadModule(
'src/api/modules/agentDistribution.ts',
true
)
AgentDistributionService.calls = []
const params = {
distribution_code: 'local-test',
phone: '13800000000',
code: '123456',
shop_name: '本地测试店铺',
shop_code: 'LOCAL_TEST',
username: 'local_test'
}
for (const password of ['Abc12345', 'Abc123456789', 'ABC1234!', 'abc1234!', 'Abcdefg!']) {
await AgentDistributionService.registerAgent({ ...params, password })
assert.deepEqual(AgentDistributionService.calls.at(-1), {
url: '/api/c/v1/agent-distribution-registrations',
data: { ...params, password },
config: { requestOptions: { withToken: false, errorMessageMode: 'none' } }
})
}
for (const password of ['', 'Abc1234', 'Abc123456789X', 'abcd1234', 'Abc1234 ', 'Abc1234中']) {
await assert.rejects(AgentDistributionService.registerAgent({ ...params, password }))
}
assert.equal(AgentDistributionService.calls.length, 5)
await AgentDistributionService.sendCode({ phone: params.phone, scene: 'bind_phone' })
assert.deepEqual(AgentDistributionService.calls.at(-1), {
url: '/api/c/v1/auth/send-code',
data: { phone: params.phone, scene: 'bind_phone' },
config: { requestOptions: { withToken: false } }
})
})
test('scan registration masks template errors in both business and HTTP responses and uses only local feedback', async () => {
const { getAgentRegistrationFailureMessage } = await loadModule(
'src/utils/business/agentRegistrationError.ts'
)
for (const code of [1050, 1001, '1050', '1001']) {
const failure = { code, data: null, msg: '企业微信审批模板无效或不可访问' }
for (const value of [
failure,
{ ...failure, data: { detail: 'internal template settings' } },
{ isAxiosError: true, response: { status: 400, data: failure } },
{ isAxiosError: true, response: { status: 500, data: failure } },
{ data: failure }
]) {
assert.equal(getAgentRegistrationFailureMessage(value), '注册失败, 请联系管理员')
}
}
assert.equal(getAgentRegistrationFailureMessage({ code: 1203, msg: '验证码错误' }), '验证码错误')
assert.equal(
getAgentRegistrationFailureMessage({ code: 1204, message: '分销码不可用' }),
'分销码不可用'
)
assert.equal(
getAgentRegistrationFailureMessage({ isAxiosError: true, code: 'ECONNABORTED' }),
'请求超时,结果可能未知,请刷新确认'
)
for (const value of [null, undefined, new Error('network')]) {
assert.equal(typeof getAgentRegistrationFailureMessage(value), 'string')
}
const page = await readFile('src/views/agent-registration/index.vue', 'utf8')
assert.match(page, /ElMessage.error\(getAgentRegistrationFailureMessage\(res\)\)/)
assert.match(
page,
/catch \(error\)\s*\{\s*ElMessage.error\(getAgentRegistrationFailureMessage\(error\)\)/
)
assert.doesNotMatch(page, /ElMessage.error\('分销码不可用'\)/)
assert.match(page, /if \(!valid \|\| submitting.value\) return/)
})
test('scan registration emits exactly one masked message per template failure and retains the form for retry', async () => {
const { getAgentRegistrationFailureMessage } = await loadModule(
'src/utils/business/agentRegistrationError.ts'
)
const passwordValidation = await loadModule('src/utils/validation/loginPassword.ts')
const { descriptor } = parse(await readFile('src/views/agent-registration/index.vue', 'utf8'))
const script = compileScript(descriptor, { id: 'scan-registration-error-test' }).content
const compiled = ts.transpileModule(script, {
compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS }
}).outputText
for (const code of [1050, 1001]) {
for (const rejected of [false, true]) {
const messages = []
const failure = { code, data: null, msg: '企业微信审批模板无效或不可访问' }
const imports = {
vue: { ...vue, onBeforeUnmount() {} },
'vue-router': { useRoute: () => ({ query: {} }) },
'element-plus': { ElMessage: { error: (message) => messages.push(message) } },
'@/api/modules/agentDistribution': {
AgentDistributionService: {
registerAgent: async () => {
if (rejected) throw { isAxiosError: true, response: { status: 400, data: failure } }
return failure
}
}
},
'@/utils/constants/regionData': { regionData: [] },
'@/utils/validation/loginPassword': passwordValidation,
'@/utils/business/agentRegistrationError': { getAgentRegistrationFailureMessage }
}
const module = { exports: {} }
new Function('require', 'module', 'exports', compiled)(
(name) => {
assert.ok(imports[name], `Unexpected dependency: ${name}`)
return imports[name]
},
module,
module.exports
)
const state = module.exports.default.setup({}, { expose() {} })
Object.assign(state.form, { distribution_code: 'local-test', password: 'Abc1234!' })
let submission
state.formRef.value = {
validate: (callback) => {
submission = callback(true)
}
}
state.handleSubmit()
state.handleSubmit()
await submission
assert.deepEqual(messages, ['注册失败, 请联系管理员'])
assert.equal(state.submitted.value, false)
assert.equal(state.submitting.value, false)
assert.equal(state.form.distribution_code, 'local-test')
assert.equal(state.form.password, 'Abc1234!')
}
}
})
test('scan registration password feedback uses the shared rule rather than treating spaces or Chinese as symbols', async () => {
const page = await readFile('src/views/agent-registration/index.vue', 'utf8')
assert.match(page, /return getNewLoginPasswordError\(pwd\) \? 1 : 3/)
assert.match(page, /密码符合规则/)
assert.match(page, /aria-live="polite"/)
assert.match(page, /password-form-item :deep\(\.el-form-item__error\)[\s\S]*?position: static/)
assert.doesNotMatch(page, /8-32|maxlength="32"/)
assert.match(page, /password: form.password,/)
})
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 AccountService.updateAccount(12, { password: 'Abc1234!' })
assert.deepEqual(AccountService.calls[2].data, { password: 'Abc1234!' })
await AccountService.updateAccountPassword(12, 'ABC1234!')
assert.deepEqual(AccountService.calls[3].data, { new_password: 'ABC1234!' })
await assert.rejects(AccountService.updateAccount(12, { password: 'abcdef!!' }))
await assert.rejects(AccountService.updateAccountPassword(12, 'abcd1234'))
assert.equal(AccountService.calls.length, 4)
})
test('change-password dialog is appended to body so its overlay covers the sidebar stacking context', async () => {
const dialog = await readFile(
'src/components/core/layouts/art-header-bar/ChangePasswordDialog.vue',
'utf8'
)
assert.match(dialog, /append-to-body/)
assert.match(dialog, /createNewLoginPasswordRule\(t\('passwordChange.passwordRule'\)\)/)
for (const language of ['zh', 'en']) {
const locale = JSON.parse(await readFile(`src/locales/langs/${language}.json`, 'utf8'))
assert.doesNotMatch(locale.passwordChange.passwordRule, /不允许特殊符号|no special characters/)
}
})
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' },
msg: '新资产已绑定手机号,请先在后台解除该资产的手机号绑定,再重新提交换货申请'
}
assert.equal(module.getExchangePhoneConflictMessage(failure), failure.msg)
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.equal(
getExchangePhoneAssociationQuery({ response: { data: { code: 1202 } } }, newAsset),
undefined
)
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('1202 with data null keeps the original msg and cannot navigate to unbind despite asset phone history', async () => {
const { getExchangePhoneConflictMessage, getExchangePhoneAssociationQuery } = await loadModule(
'src/utils/business/exchangeError.ts'
)
const failure = {
code: 1202,
data: null,
msg: '该资产已完成过换货,如需再次换货请使用换货后的新资产'
}
const newAsset = { asset_identifier: '89860624590014594318', associated_phones: ['19232713115'] }
for (const value of [failure, { response: { data: failure } }]) {
assert.equal(getExchangePhoneConflictMessage(value), failure.msg)
assert.equal(getExchangePhoneAssociationQuery(value, newAsset), undefined)
}
for (const phone of ['', ' ', null, 19232713115]) {
assert.equal(
getExchangePhoneAssociationQuery({ ...failure, data: { phone } }, newAsset),
undefined
)
}
const page = await readFile('src/views/asset-management/exchange-management/index.vue', 'utf8')
assert.match(page, /if \(!canManageAssociations\)/)
assert.match(page, /ElMessageBox.alert\(conflictMessage, '换货提示'/)
})
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/)
})