Files
one-pipe-system/scripts/tests/assetInfoState.test.mjs
luo 9a636a953b
Some checks failed
构建并部署前端到测试环境 / build-and-deploy (push) Failing after 37m49s
fix: 新增字段
2026-10-12 14:53:27 +08:00

745 lines
26 KiB
JavaScript

import test from 'node:test'
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import ts from 'typescript'
import * as vue from 'vue'
import { parse } from '@vue/compiler-sfc'
const sourcePath = 'src/views/asset-management/asset-information/composables/useAssetInfo.ts'
const compiledSource = ts.transpileModule(readFileSync(sourcePath, 'utf8'), {
compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS }
}).outputText
const response = (data) => ({ code: 0, data })
const deferred = () => {
let resolve, reject
const promise = new Promise((resolvePromise, rejectPromise) => {
resolve = resolvePromise
reject = rejectPromise
})
return { promise, resolve, reject }
}
const boundCards = () => [
{
card_id: 11,
iccid: 'CARD-1',
msisdn: '13800000001',
carrier_name: '运营商账户',
carrier_type: 'CTCC',
slot_position: 1,
is_current: true,
network_status: 1,
real_name_status: 1,
real_name_at: '2026-10-01T01:00:00Z',
realname_policy: 'before_order',
gateway_extend: 'old reason'
},
{
card_id: 12,
iccid: 'CARD-2',
msisdn: '13800000002',
carrier_name: '另一运营商账户',
carrier_type: 'CMCC',
slot_position: 2,
is_current: false,
network_status: 0,
real_name_status: 0,
real_name_at: null
}
]
const detail = (identifier = 'DEVICE-A', cards = boundCards()) => ({
asset_type: 'device',
asset_id: identifier === 'DEVICE-A' ? 101 : 202,
virtual_no: identifier,
bound_card_count: cards.length,
cards,
real_total_mb: 1024,
real_used_mb: 100
})
function harness(t, overrides = {}) {
const user = vue.reactive({ info: { id: 1, user_type: 1 }, isLogin: true })
const messages = []
const api = {
resolveAsset: async (identifier) => response(detail(identifier)),
getRealtimeStatus: async () => response(null),
getCurrentPackage: async () => response(null),
getAssetWallet: async () => response(null),
getAssetPackages: async () => response({ items: [], total: 0 }),
refreshAsset: async () => response(null),
...overrides
}
const imports = {
vue,
'element-plus': {
ElMessage: Object.fromEntries(
['error', 'success', 'warning'].map((type) => [
type,
(message) => messages.push({ type, message })
])
)
},
'@/api/modules': { AssetService: api },
'@/utils/business/apiRateLimit': { FrontendRateLimitError: class extends Error {} },
'@/utils/business/apiError': {
normalizeApiError: (error) => ({ message: error?.message || 'unknown error' })
},
'@/store/modules/user': { useUserStore: () => user }
}
const module = { exports: {} }
new Function('require', 'module', 'exports', compiledSource)(
(name) => {
assert.ok(imports[name], `Unexpected runtime dependency: ${name}`)
return imports[name]
},
module,
module.exports
)
const scope = vue.effectScope()
const state = scope.run(() => module.exports.useAssetInfo())
t.after(() => scope.stop())
return { state, api, user, messages, scope }
}
test('partial realtime responses retain the complete resolved binding-card roster', async (t) => {
const { state } = harness(t, {
getRealtimeStatus: async () =>
response({
asset_type: 'device',
asset_id: 101,
cards: [{ iccid: 'CARD-1', network_status: 0 }]
})
})
await state.fetchAssetDetail('DEVICE-A')
assert.deepEqual(
state.cardInfo.value.cards.map((card) => card.iccid),
['CARD-1', 'CARD-2']
)
assert.equal(state.cardInfo.value.cards[0].network_status, 0)
assert.equal(state.cardInfo.value.cards[1].msisdn, '13800000002')
})
test('realtime fields cannot erase resolved card identity, carrier, slot, policy, or real-name time', async (t) => {
const { state } = harness(t, {
getRealtimeStatus: async () =>
response({
asset_type: 'device',
asset_id: 101,
cards: [
{
iccid: 'CARD-1',
card_id: 0,
msisdn: '',
carrier_name: '',
carrier_type: '',
slot_position: 0,
realname_policy: '',
real_name_at: 'untrusted realtime time',
network_status: 0,
real_name_status: 0,
is_current: false,
gateway_extend: ''
}
]
})
})
await state.fetchAssetDetail('DEVICE-A')
assert.deepEqual(state.cardInfo.value.cards[0], {
...boundCards()[0],
network_status: 0,
real_name_status: 0,
is_current: false,
gateway_extend: ''
})
})
test('empty or omitted realtime cards preserve the detail roster; detail reload can remove bindings', async (t) => {
const { state, api } = harness(t)
await state.fetchAssetDetail('DEVICE-A')
for (const cards of [[], undefined, null]) {
api.getRealtimeStatus = async () => response({ asset_type: 'device', asset_id: 101, cards })
await state.loadRealtimeStatus('DEVICE-A', 'device')
assert.equal(state.cardInfo.value.cards.length, 2)
}
api.resolveAsset = async () => response(detail('DEVICE-A', []))
await state.fetchAssetDetail('DEVICE-A')
assert.deepEqual(state.cardInfo.value.cards, [])
})
test('unknown realtime cards do not introduce bindings absent from the authorized detail response', async (t) => {
const { state } = harness(t, {
getRealtimeStatus: async () =>
response({ asset_type: 'device', asset_id: 101, cards: [{ iccid: 'UNRESOLVED-CARD' }] })
})
await state.fetchAssetDetail('DEVICE-A')
assert.deepEqual(
state.cardInfo.value.cards.map((card) => card.iccid),
['CARD-1', 'CARD-2']
)
})
test('realtime card order does not change the resolved roster; explicit nullable timestamps can clear', async (t) => {
const cards = boundCards()
cards[0].last_data_check_at = 'old timestamp'
const { state } = harness(t, {
resolveAsset: async () => response(detail('DEVICE-A', cards)),
getRealtimeStatus: async () =>
response({
asset_type: 'device',
asset_id: 101,
cards: [
{ iccid: 'CARD-2' },
{ iccid: 'CARD-1', last_data_check_at: null, real_name_status: null }
]
})
})
await state.fetchAssetDetail('DEVICE-A')
assert.deepEqual(
state.cardInfo.value.cards.map((card) => card.iccid),
['CARD-1', 'CARD-2']
)
assert.equal(state.cardInfo.value.cards[0].last_data_check_at, null)
assert.equal(state.cardInfo.value.cards[0].real_name_status, 1)
})
test('a late resolve response cannot replace a newer search', async (t) => {
const oldDetail = deferred()
const { state } = harness(t, {
resolveAsset: (identifier) =>
identifier === 'DEVICE-A' ? oldDetail.promise : Promise.resolve(response(detail(identifier)))
})
const oldSearch = state.fetchAssetDetail('DEVICE-A')
await state.fetchAssetDetail('DEVICE-B')
oldDetail.resolve(response(detail('DEVICE-A')))
await oldSearch
assert.equal(state.cardInfo.value.identifier, 'DEVICE-B')
})
test('late realtime responses cannot write old card data into a different asset', async (t) => {
const { state, api } = harness(t)
await state.fetchAssetDetail('DEVICE-A')
const oldRealtime = deferred()
api.getRealtimeStatus = () => oldRealtime.promise
const oldLoad = state.loadRealtimeStatus('DEVICE-A', 'device')
api.getRealtimeStatus = async () => response(null)
await state.fetchAssetDetail('DEVICE-B')
oldRealtime.resolve(
response({ asset_type: 'device', asset_id: 101, cards: [{ iccid: 'OLD-CARD' }] })
)
await oldLoad
assert.equal(state.cardInfo.value.identifier, 'DEVICE-B')
assert.deepEqual(
state.cardInfo.value.cards.map((card) => card.iccid),
['CARD-1', 'CARD-2']
)
})
test('the newest same-asset realtime request wins and stale completion cannot end its loading state', async (t) => {
const { state, api } = harness(t)
await state.fetchAssetDetail('DEVICE-A')
const older = deferred()
const newer = deferred()
api.getRealtimeStatus = () => older.promise
const oldLoad = state.loadRealtimeStatus('DEVICE-A', 'device')
api.getRealtimeStatus = () => newer.promise
const newLoad = state.loadRealtimeStatus('DEVICE-A', 'device')
older.resolve(
response({
asset_type: 'device',
asset_id: 101,
cards: [{ iccid: 'CARD-1', network_status: 0 }]
})
)
await oldLoad
assert.equal(state.realtimeLoading.value, true)
assert.equal(state.cardInfo.value.cards[0].network_status, 1)
newer.resolve(
response({
asset_type: 'device',
asset_id: 101,
cards: [{ iccid: 'CARD-1', network_status: 1 }]
})
)
await newLoad
assert.equal(state.realtimeLoading.value, false)
assert.equal(state.cardInfo.value.cards[0].network_status, 1)
})
test('realtime responses identifying a different asset are ignored', async (t) => {
const { state, api } = harness(t)
await state.fetchAssetDetail('DEVICE-A')
for (const identity of [
{ asset_type: 'device', asset_id: 202 },
{ asset_type: 'card', asset_id: 101 }
]) {
api.getRealtimeStatus = async () =>
response({ ...identity, cards: [{ iccid: 'CARD-1', network_status: 0 }] })
await state.loadRealtimeStatus('DEVICE-A', 'device')
assert.equal(state.cardInfo.value.cards[0].network_status, 1)
}
})
test('starting a new search clears previous device, package, wallet, and binding data immediately', async (t) => {
const { state, api } = harness(t)
await state.fetchAssetDetail('DEVICE-A')
state.deviceRealtime.value = { current_iccid: 'CARD-1' }
state.currentPackage.value = { package_name: 'old package' }
state.packageList.value = [{ id: 1 }]
state.walletInfo.value = { total_balance: 123 }
const newDetail = deferred()
api.resolveAsset = () => newDetail.promise
const nextSearch = state.fetchAssetDetail('DEVICE-B')
assert.equal(state.cardInfo.value, null)
assert.equal(state.deviceRealtime.value, null)
assert.equal(state.currentPackage.value, null)
assert.equal(state.walletInfo.value, null)
assert.deepEqual(state.packageList.value, [])
newDetail.resolve({ code: 403, msg: 'forbidden' })
await nextSearch
assert.equal(state.cardInfo.value, null)
})
test('changing account invalidates pending requests even when searching the same identifier', async (t) => {
const oldDetail = deferred()
const { state, user, api } = harness(t, { resolveAsset: () => oldDetail.promise })
const oldSearch = state.fetchAssetDetail('DEVICE-A')
user.info = { id: 2, user_type: 3 }
api.resolveAsset = async () => response(detail('DEVICE-A', [boundCards()[1]]))
await state.fetchAssetDetail('DEVICE-A')
oldDetail.resolve(response(detail('DEVICE-A')))
await oldSearch
assert.deepEqual(
state.cardInfo.value.cards.map((card) => card.iccid),
['CARD-2']
)
})
test('logout clears visible data and invalidates pending realtime responses', async (t) => {
const { state, user, api } = harness(t)
await state.fetchAssetDetail('DEVICE-A')
const pending = deferred()
api.getRealtimeStatus = () => pending.promise
const load = state.loadRealtimeStatus('DEVICE-A', 'device')
user.isLogin = false
assert.equal(state.cardInfo.value, null)
pending.resolve(response({ asset_type: 'device', asset_id: 101, cards: boundCards() }))
await load
assert.equal(state.cardInfo.value, null)
})
test('a late failure of an old search does not clear the current successful asset', async (t) => {
const oldDetail = deferred()
const { state } = harness(t, {
resolveAsset: (identifier) =>
identifier === 'DEVICE-A' ? oldDetail.promise : Promise.resolve(response(detail(identifier)))
})
const oldSearch = state.fetchAssetDetail('DEVICE-A')
await state.fetchAssetDetail('DEVICE-B')
oldDetail.reject(new Error('old request failed'))
await oldSearch
assert.equal(state.cardInfo.value?.identifier, 'DEVICE-B')
})
test('account types 1/2/3/4 retain the same resolved bindings despite sparse realtime fields', async (t) => {
for (const userType of [1, 2, 3, 4]) {
const { state, user } = harness(t, {
getRealtimeStatus: async () =>
response({
asset_type: 'device',
asset_id: 101,
cards: [{ iccid: 'CARD-1', carrier_name: '' }]
})
})
user.info = { id: userType, user_type: userType }
await state.fetchAssetDetail('DEVICE-A')
assert.deepEqual(state.cardInfo.value.cards, boundCards())
}
})
test('a current-card change clears the previous current flag even for a partial realtime list', async (t) => {
const { state } = harness(t, {
getRealtimeStatus: async () =>
response({
asset_type: 'device',
asset_id: 101,
cards: [{ iccid: 'CARD-2', is_current: true }]
})
})
await state.fetchAssetDetail('DEVICE-A')
assert.deepEqual(
state.cardInfo.value.cards.map((card) => card.is_current),
[false, true]
)
})
test('same-identifier re-search never takes a real-name timestamp from a previous search or realtime', async (t) => {
const { state, api } = harness(t)
await state.fetchAssetDetail('DEVICE-A')
api.resolveAsset = async () =>
response(detail('DEVICE-A', [{ ...boundCards()[0], real_name_at: null }]))
api.getRealtimeStatus = async () =>
response({
asset_type: 'device',
asset_id: 101,
cards: [{ iccid: 'CARD-1', real_name_at: 'stale time' }]
})
await state.fetchAssetDetail('DEVICE-A')
assert.equal(state.cardInfo.value.cards[0].real_name_at, null)
})
test('manual refresh resolves new binding relations and preserves the existing package page', async (t) => {
let resolveCalls = 0
const { state } = harness(t, {
resolveAsset: async () => response(detail('DEVICE-A', ++resolveCalls === 1 ? boundCards() : []))
})
await state.fetchAssetDetail('DEVICE-A')
state.packageList.value = [{ id: 88, children: [{ id: 89 }] }]
assert.equal(await state.refreshAsset('DEVICE-A', 'device'), true)
assert.equal(resolveCalls, 2)
assert.deepEqual(state.cardInfo.value.cards, [])
assert.deepEqual(state.packageList.value, [{ id: 88, children: [{ id: 89 }] }])
})
test('a pending refresh does not re-search or overwrite a newer asset', async (t) => {
const pending = deferred()
const calls = []
const { state } = harness(t, {
refreshAsset: () => pending.promise,
resolveAsset: async (identifier) => {
calls.push(identifier)
return response(detail(identifier))
}
})
await state.fetchAssetDetail('DEVICE-A')
const refresh = state.refreshAsset('DEVICE-A', 'device')
await state.fetchAssetDetail('DEVICE-B')
pending.resolve(response(null))
assert.equal(await refresh, false)
assert.equal(state.cardInfo.value.identifier, 'DEVICE-B')
assert.deepEqual(calls, ['DEVICE-A', 'DEVICE-B'])
})
test('late current-package, wallet, and package-list responses do not contaminate a new asset', async (t) => {
const { state, api } = harness(t)
await state.fetchAssetDetail('DEVICE-A')
const pending = { package: deferred(), wallet: deferred(), list: deferred() }
api.getCurrentPackage = () => pending.package.promise
api.getAssetWallet = () => pending.wallet.promise
api.getAssetPackages = () => pending.list.promise
const oldLoads = [
state.loadCurrentPackage('DEVICE-A'),
state.loadAssetWallet('DEVICE-A'),
state.loadPackageList('DEVICE-A')
]
api.getCurrentPackage = async () => response(null)
api.getAssetWallet = async () => response(null)
await state.fetchAssetDetail('DEVICE-B')
pending.package.resolve(response({ package_name: 'old package' }))
pending.wallet.resolve(response({ total_balance: 123 }))
pending.list.resolve(response({ items: [{ id: 1 }], total: 1 }))
const results = await Promise.all(oldLoads)
assert.equal(state.currentPackage.value, null)
assert.equal(state.walletInfo.value, null)
assert.deepEqual(state.packageList.value, [])
assert.equal(results[2], null)
})
test('a newer same-asset package page wins even if the older page returns last', async (t) => {
const { state, api } = harness(t)
await state.fetchAssetDetail('DEVICE-A')
const older = deferred()
api.getAssetPackages = () => older.promise
const firstPage = state.loadPackageList('DEVICE-A', 1)
api.getAssetPackages = async () =>
response({ items: [{ id: 2, children: [{ id: 3 }] }], total: 9, page: 2 })
await state.loadPackageList('DEVICE-A', 2)
older.resolve(response({ items: [{ id: 1 }], total: 1, page: 1 }))
assert.equal(await firstPage, null)
assert.deepEqual(state.packageList.value, [{ id: 2, children: [{ id: 3 }] }])
})
test('unmount invalidates outstanding resolve requests', async (t) => {
const pending = deferred()
const { state, scope } = harness(t, { resolveAsset: () => pending.promise })
const search = state.fetchAssetDetail('DEVICE-A')
scope.stop()
pending.resolve(response(detail('DEVICE-A')))
assert.equal(await search, null)
assert.equal(state.cardInfo.value, null)
})
test('failed realtime loading preserves resolved bindings and a sparse identified request can update status', async (t) => {
t.mock.method(console, 'error', () => {})
const { state, api } = harness(t, {
getRealtimeStatus: async () => {
throw new Error('realtime unavailable')
}
})
await state.fetchAssetDetail('DEVICE-A')
assert.deepEqual(state.cardInfo.value.cards, boundCards())
assert.equal(state.realtimeLoading.value, false)
api.getRealtimeStatus = async () => response({ cards: [{ iccid: 'CARD-1', network_status: 0 }] })
await state.loadRealtimeStatus('DEVICE-A', 'device')
assert.equal(state.cardInfo.value.cards[0].network_status, 0)
assert.equal(state.cardInfo.value.cards.length, 2)
})
test('updating user metadata without changing the account or data scope does not clear asset data', async (t) => {
const { state, user } = harness(t)
await state.fetchAssetDetail('DEVICE-A')
const asset = state.cardInfo.value
user.info = { ...user.info, username: 'updated display name' }
await vue.nextTick()
assert.equal(state.cardInfo.value, asset)
})
test('a disposed asset composable cannot restart loads after an operation finishes late', async (t) => {
let calls = 0
const { state, scope } = harness(t, {
resolveAsset: async () => {
calls += 1
return response(detail('DEVICE-A'))
}
})
scope.stop()
assert.equal(await state.fetchAssetDetail('DEVICE-A'), null)
assert.equal(calls, 0)
})
test('standalone card realtime status still updates while the resolved gateway IMEI remains authoritative', async (t) => {
const { state } = harness(t, {
resolveAsset: async () =>
response({
...detail('CARD-1', []),
asset_type: 'card',
iccid: 'CARD-1',
gateway_card_imei: 'resolved-imei'
}),
getRealtimeStatus: async () =>
response({
asset_type: 'card',
asset_id: 202,
network_status: 0,
real_name_status: 1,
gateway_card_imei: 'realtime-imei'
})
})
await state.fetchAssetDetail('CARD-1')
assert.equal(state.cardInfo.value.network_status, 0)
assert.equal(state.cardInfo.value.real_name_status, 1)
assert.equal(state.cardInfo.value.gateway_card_imei, 'resolved-imei')
})
function pageHandlers(state) {
const { descriptor } = parse(
readFileSync('src/views/asset-management/asset-information/index.vue', 'utf8')
)
const source = ts.createSourceFile(
'asset-information.ts',
descriptor.scriptSetup.content,
ts.ScriptTarget.Latest,
true,
ts.ScriptKind.TS
)
const wanted = new Set([
'handleSearch',
'handleRefresh',
'handleSpeedTierConfirm',
'showUpdateRealnameStatusDialog',
'handleUpdateRealnameStatusSuccess',
'handleUpdateBindingCardRealnameStatus',
'handleBindingCardRealnameStatusSuccess'
])
const declarations = []
function visit(node) {
if (ts.isVariableDeclaration(node) && wanted.has(node.name.getText(source)))
declarations.push(node.getText(source))
ts.forEachChild(node, visit)
}
visit(source)
assert.equal(declarations.length, wanted.size)
const bindings = {
cardInfo: state.cardInfo,
fetchAssetDetail: state.fetchAssetDetail,
loadPackageList: state.loadPackageList,
refreshAsset: state.refreshAsset,
refreshSubmitting: vue.ref(false),
refreshDisabled: vue.ref(false),
updateRateLimitNow: () => {},
loadRealtimeStatus: state.loadRealtimeStatus,
hasAuth: () => true,
JULY_PERMISSIONS: { speedTier: { update: 'speed:update' } },
CardService: {
setSpeedTier: async () => response({ speed_tier_name: 'test tier' })
},
ElMessage: { success: () => {}, error: () => {} },
normalizeApiError: (error) => ({ message: error?.message || 'unknown error' }),
speedLimitDialogVisible: vue.ref(false),
speedTierSubmitting: vue.ref(false),
assetSearchCardRef: vue.ref(null),
startFailureMessage: vue.ref(''),
packagePagination: vue.ref({ page: 1, total: 0, pageSize: 5, status: undefined }),
walletTransactionSearchToken: vue.ref(0),
pollingEnabled: vue.ref(false),
updateRealnameStatusDialogVisible: vue.ref(false),
bindingCardRealnameStatusDialogVisible: vue.ref(false),
bindingCardRealnameStatusIccid: vue.ref(''),
bindingCardRealnameStatusValue: vue.ref(0),
nextTick: vue.nextTick
}
const compiled = ts.transpileModule(
`let searchRequestId = 0, pollingInitializing = false, realnameStatusAsset = null, bindingCardRealnameStatusAsset = null\n${declarations.map((declaration) => `const ${declaration}`).join('\n')}\nreturn { ${[...wanted].join(', ')} }`,
{ compilerOptions: { target: ts.ScriptTarget.ES2022 } }
).outputText
return {
...new Function(...Object.keys(bindings), compiled)(...Object.values(bindings)),
...bindings
}
}
test('page search resolves a device only once and uses its canonical identifier for related requests', async (t) => {
const calls = []
const { state } = harness(t, {
resolveAsset: async (identifier) => {
calls.push(['resolve', identifier])
return response(detail('DEVICE-A'))
},
getRealtimeStatus: async (identifier) => {
calls.push(['realtime', identifier])
return response(null)
},
getAssetPackages: async (identifier) => {
calls.push(['packages', identifier])
return response({ items: [], total: 0 })
}
})
await pageHandlers(state).handleSearch({ identifier: 'IMEI-ALIAS' })
assert.deepEqual(calls, [
['resolve', 'IMEI-ALIAS'],
['realtime', 'DEVICE-A'],
['packages', 'DEVICE-A']
])
})
test('active gateway synchronization is called only by the sync button, never by searches or data reloads', async (t) => {
const syncCalls = []
const { state } = harness(t, {
refreshAsset: async (identifier) => {
syncCalls.push(identifier)
return response(null)
}
})
const page = pageHandlers(state)
await page.handleRefresh()
await page.handleSearch({ identifier: 'DEVICE-A' })
await page.handleSearch({ identifier: 'DEVICE-A' })
await state.loadRealtimeStatus('DEVICE-A', 'device')
page.handleUpdateBindingCardRealnameStatus({ card: boundCards()[0] })
await page.handleBindingCardRealnameStatusSuccess()
page.showUpdateRealnameStatusDialog()
await page.handleUpdateRealnameStatusSuccess()
assert.deepEqual(syncCalls, [])
await page.handleRefresh()
assert.deepEqual(syncCalls, ['DEVICE-A'])
await state.fetchAssetDetail('DEVICE-A')
page.refreshDisabled.value = true
await page.handleRefresh()
assert.deepEqual(syncCalls, ['DEVICE-A'])
})
test('successful speed-tier updates reload saved status without calling active gateway synchronization', async (t) => {
const syncCalls = []
const realtimeCalls = []
const { state } = harness(t, {
resolveAsset: async () =>
response({ ...detail('CARD-1', []), asset_type: 'card', iccid: 'CARD-1' }),
refreshAsset: async (identifier) => {
syncCalls.push(identifier)
return response(null)
},
getRealtimeStatus: async (identifier) => {
realtimeCalls.push(identifier)
return response(null)
}
})
await state.fetchAssetDetail('CARD-1')
realtimeCalls.length = 0
await pageHandlers(state).handleSpeedTierConfirm({ code: 1 })
assert.deepEqual(syncCalls, [])
assert.deepEqual(realtimeCalls, ['CARD-1'])
})
test('main retains the legacy 19-digit ICCID fallback without triggering synchronization', async (t) => {
const identifiers = []
let syncCalls = 0
const { state } = harness(t, {
resolveAsset: async (identifier) => {
identifiers.push(identifier)
if (identifier.length === 19) return { code: 1006, msg: 'not found' }
return response({ ...detail('CARD-1', []), asset_type: 'card', iccid: identifier })
},
refreshAsset: async () => {
syncCalls += 1
return response(null)
}
})
await state.fetchAssetDetail('8986000000000000000')
assert.equal(identifiers.length, 2)
assert.equal(identifiers[0].length, 19)
assert.equal(identifiers[1].length, 20)
assert.equal(state.cardInfo.value.identifier, identifiers[1])
assert.equal(syncCalls, 0)
})
test('a stale page search cannot start additional loads or reset the newer search pagination', async (t) => {
const pending = deferred()
const { state } = harness(t, {
resolveAsset: (identifier) =>
identifier === 'DEVICE-A' ? pending.promise : Promise.resolve(response(detail(identifier))),
getAssetPackages: async () => response({ items: [{ id: 2 }], total: 22 })
})
const page = pageHandlers(state)
const older = page.handleSearch({ identifier: 'DEVICE-A' })
await page.handleSearch({ identifier: 'DEVICE-B' })
pending.resolve(response(detail('DEVICE-A')))
await older
assert.equal(page.packagePagination.value.total, 22)
assert.equal(page.walletTransactionSearchToken.value, 1)
assert.equal(state.cardInfo.value.identifier, 'DEVICE-B')
})
test('successful bound-card real-name update reloads detail fields but an old dialog cannot reload a new asset', async (t) => {
let resolveCalls = 0
const { state, api } = harness(t, {
resolveAsset: async (identifier) => {
resolveCalls += 1
return response(detail(identifier))
}
})
await state.fetchAssetDetail('DEVICE-A')
const page = pageHandlers(state)
page.handleUpdateBindingCardRealnameStatus({ card: boundCards()[0] })
api.resolveAsset = async () => {
resolveCalls += 1
return response(
detail('DEVICE-A', [{ ...boundCards()[0], real_name_status: 0, real_name_at: null }])
)
}
await page.handleBindingCardRealnameStatusSuccess()
assert.equal(state.cardInfo.value.cards[0].real_name_status, 0)
assert.equal(state.cardInfo.value.cards[0].real_name_at, null)
assert.equal(resolveCalls, 2)
api.resolveAsset = async (identifier) => {
resolveCalls += 1
return response(detail(identifier))
}
page.handleUpdateBindingCardRealnameStatus({ card: boundCards()[0] })
await state.fetchAssetDetail('DEVICE-B')
await page.handleBindingCardRealnameStatusSuccess()
assert.equal(state.cardInfo.value.identifier, 'DEVICE-B')
assert.equal(resolveCalls, 3)
})