Files
pda-overseas-uniapp/tests/dispatch-loading-documents.test.mjs
T

78 lines
6.4 KiB
JavaScript
Raw Normal View History

2026-10-21 17:27:13 +08:00
import test from 'node:test'
import assert from 'node:assert/strict'
import { readFile } from 'node:fs/promises'
import { loadingPhotoSlots, normalizeLoadingDocuments, getLoadingDocuments, bindLoadingPhoto, uploadLoadingPhoto } from '../src/api/dispatch-loading-documents.js'
import { session } from '../src/stores/session.js'
const source = (await readFile(new URL('../src/components/DispatchLoadingDocuments.vue', import.meta.url), 'utf8')).match(/<script>([\s\S]*?)<\/script>/)[1]
const data = extra => ({ dispatchNo: 'PC/1', recordVersion: '0', dispatchRecordVersion: '3', stage: 'AFTER_LOADING', editable: true, complete: false, photos: [], ...extra })
const photo = { slot: 'LOADING_SCENE', fileId: '1001', fileName: 'loading.png', contentType: 'image/png' }
function component(overrides = {}) {
const values = new Map(), events = []
globalThis.uni = { setStorageSync: (key, value) => values.set(key, value), removeStorageSync: key => values.delete(key), getStorageSync: key => values.get(key), chooseImage: async () => ({ tempFilePaths: ['local-image'], tempFiles: [{ size: 123 }] }), showToast() {} }
const dependencies = { session: { currentUser: { id: 'user', warehouseId: 'warehouse' } }, loadingPhotoSlots, toDispatchError: cause => cause,
getLoadingDocuments: async () => data(), uploadLoadingPhoto: async () => photo, recoverLoadingPhoto: async () => photo,
bindLoadingPhoto: async () => data({ recordVersion: '1', photos: [photo] }), readLoadingPhoto: async () => 'downloaded-image', ...overrides }
const definition = new Function(...Object.keys(dependencies), source.replace(/^import .*$/gm, '').replace('export default', 'return'))(...Object.values(dependencies))
const state = { ...definition.data.call({ dispatchNo: 'PC/1' }), dispatchNo: 'PC/1', $emit: (...args) => events.push(args) }
for (const [key, method] of Object.entries(definition.methods)) state[key] = method.bind(state)
for (const [key, getter] of Object.entries(definition.computed)) Object.defineProperty(state, key, { get: () => getter.call(state) })
return { state, events, values, definition }
}
test('PDA reads and binds through its own namespace without customer or warehouse input', async () => {
const calls = []; session.apiBase = '/gateway-api'; session.token = 'test-token'
globalThis.uni = { request(options) { calls.push(options); options.success({ statusCode: 200, data: { code: '200', data: data() } }) } }
await getLoadingDocuments('PC/1')
const body = { recordVersion: '9007199254740993', idempotencyKey: 'stable-key', fileId: '1001' }
await bindLoadingPhoto('PC/1', 'LOADING_SCENE', body)
assert.match(calls[0].url, /\/pda\/v1\/last-leg\/dispatches\/PC%2F1\/loading-documents$/)
assert.equal(calls[1].method, 'PUT'); assert.deepEqual(calls[1].data, body)
})
test('PDA upload preserves the original request key through the authenticated upload helper', async () => {
const calls = []; session.apiBase = '/gateway-api'; session.token = 'test-token'
globalThis.uni = { uploadFile(options) { calls.push(options); options.success({ statusCode: 200, data: JSON.stringify({ code: '200', data: photo }) }) } }
await uploadLoadingPhoto('PC/1', 'LOADING_SCENE', 'local-image', 'same-key')
assert.match(calls[0].url, /loading-documents\/LOADING_SCENE\/uploads\?idempotencyKey=same-key$/)
assert.equal(calls[0].filePath, 'local-image')
})
test('wrong dispatch, duplicate slots and invalid versions never become saved facts', () => {
assert.throws(() => normalizeLoadingDocuments(data(), 'PC/2'), /不一致/)
assert.throws(() => normalizeLoadingDocuments(data({ photos: [photo, photo] }), 'PC/1'), /重复/)
assert.throws(() => normalizeLoadingDocuments(data({ recordVersion: null }), 'PC/1'), /不一致/)
})
test('unknown binding keeps the same file, version and key and does not reupload', async () => {
let uploads = 0; const bindings = []
const { state, events } = component({ uploadLoadingPhoto: async () => { uploads++; return photo }, bindLoadingPhoto: async (_no, _slot, body) => { bindings.push(structuredClone(body)); if (bindings.length === 1) throw { network: true, message: 'unknown' }; return data({ stage: 'DELIVERING', complete: true, recordVersion: '5' }) } })
await state.load(); await state.choose('LOADING_SCENE')
assert.ok(state.pending); assert.equal(events.some(event => event[0] === 'saved'), false)
await state.recover()
assert.equal(uploads, 1); assert.deepEqual(bindings[0], bindings[1]); assert.equal(state.pending, null)
assert.equal(state.snapshot.stage, 'DELIVERING'); assert.equal(events.filter(event => event[0] === 'saved').length, 1)
})
test('a failed upload does not replace a saved photo or advance the stage', async () => {
const { state } = component({ getLoadingDocuments: async () => data({ photos: [photo] }), uploadLoadingPhoto: async () => { throw { network: true, message: 'unknown' } } })
await state.load(); await state.choose('LOADING_SCENE')
assert.equal(state.snapshot.photos[0].fileId, '1001'); assert.equal(state.snapshot.stage, 'AFTER_LOADING')
assert.ok(state.pending.key); assert.equal(state.pending.photo, undefined)
})
test('definitive file rejection allows selecting a new image while retaining the saved one', async () => {
let uploads = 0
const { state } = component({ getLoadingDocuments: async () => data({ photos: [photo] }), uploadLoadingPhoto: async () => {
if (++uploads === 1) throw { code: 'A0400', message: '图片内容无效' }
return photo
} })
await state.load(); await state.choose('LOADING_SCENE')
assert.equal(state.pending, null); assert.equal(state.snapshot.photos[0].fileId, photo.fileId)
assert.equal(state.error.message, '图片内容无效')
await state.choose('LOADING_SCENE'); assert.equal(uploads, 2); assert.equal(state.pending, null)
})
2026-10-21 17:27:13 +08:00
test('loading documents can be saved during LOADING without claiming departure', async () => {
assert.equal(normalizeLoadingDocuments(data({ stage: 'LOADING' }), 'PC/1').stage, 'LOADING')
assert.throws(() => normalizeLoadingDocuments(data({ stage: 'BEFORE_LOADING' }), 'PC/1'), /不一致/)
const { state, events } = component({ getLoadingDocuments: async () => data({ stage: 'LOADING' }), bindLoadingPhoto: async () => data({ stage: 'LOADING', recordVersion: '1', photos: [photo] }) })
await state.load(); await state.choose('LOADING_SCENE')
assert.equal(state.snapshot.stage, 'LOADING'); assert.equal(state.pending, null)
assert.equal(events.filter(event => event[0] === 'saved').length, 1)
})