diff --git a/src/api/overseas.js b/src/api/overseas.js index 0171768..c792931 100644 --- a/src/api/overseas.js +++ b/src/api/overseas.js @@ -40,10 +40,19 @@ async function tokenAfterUnauthorized(failedToken) { return refreshOverseasToken() } +function overseasUrl(path) { + const base = String(session.apiBase || '').replace(/\/$/, '') + const value = `/${String(path || '').replace(/^\/+/, '')}` + const normalized = /\/(?:api|overseas-api)$/.test(base) + ? value.replace(/^\/api(?=\/)/, '') + : value + return `${base}${normalized}` +} + function rawRequest(method, path, data, token) { return new Promise((resolve, reject) => { uni.request({ - url: `${session.apiBase}${path}`, + url: overseasUrl(path), method, data, header: { Accept: 'application/json', Authorization: `Bearer ${token}` }, @@ -72,7 +81,7 @@ export async function overseasRequest(method, path, data, retry = true) { function rawUpload(path, filePath, name, token) { return new Promise((resolve, reject) => { uni.uploadFile({ - url: `${session.apiBase}${path}`, + url: overseasUrl(path), filePath, name, header: { Authorization: `Bearer ${token}` }, @@ -82,14 +91,6 @@ function rawUpload(path, filePath, name, token) { }) } -function overseasUrl(path) { - const value = String(path || '') - const normalized = /\/(?:api|overseas-api)$/.test(session.apiBase) - ? value.replace(/^\/api(?=\/)/, '') - : value - return `${session.apiBase}${normalized}` -} - function rawDownload(path, token) { return new Promise((resolve, reject) => { uni.downloadFile({ diff --git a/src/api/pallet.js b/src/api/pallet.js index 50a596d..8393493 100644 --- a/src/api/pallet.js +++ b/src/api/pallet.js @@ -1,6 +1,6 @@ import { overseasRequest } from './overseas.js' -const BASE = '/api/pda/v1/last-leg/pallet-operations' +const BASE = '/pda/v1/last-leg/pallet-operations' export function resolvePallet(code) { return overseasRequest('POST', `${BASE}/pallets/resolve`, { code: String(code || '').trim() }) diff --git a/tests/overseas-request.test.mjs b/tests/overseas-request.test.mjs index c48eb39..aea38d3 100644 --- a/tests/overseas-request.test.mjs +++ b/tests/overseas-request.test.mjs @@ -2,6 +2,7 @@ import test from 'node:test' import assert from 'node:assert/strict' import { overseasDownload, overseasRequest, overseasUpload, resetOverseasRequestStateForTest } from '../src/api/overseas.js' +import { resolvePallet } from '../src/api/pallet.js' import { PDA_AUTHORITY, PDA_ROLE, session, signInWithTokens, signOut } from '../src/stores/session.js' const future = () => Math.floor(Date.now() / 1000) + 3600 @@ -34,6 +35,38 @@ const denied = (requestId = 'request-401', traceId = 'trace-401') => ({ statusCode: 401, data: { code: 'A0401', message: 'expired', requestId, traceId }, header: {} }) +test('H5 proxy and App API roots normalize request upload and download paths to one public api prefix', async () => { + prepareSession() + const requests = []; const uploads = []; const downloads = [] + globalThis.uni.request = (options) => { requests.push(options.url); options.success(ok({ palletCode: 'PLT-1' })) } + globalThis.uni.uploadFile = (options) => { uploads.push(options.url); options.success({ statusCode: 200, data: JSON.stringify({ code: '200', data: { fileId: '1' } }), header: {} }) } + globalThis.uni.downloadFile = (options) => { downloads.push(options.url); options.success({ statusCode: 200, tempFilePath: '/tmp/photo.jpg', header: {} }) } + + session.apiBase = '/overseas-api' + await resolvePallet('PLT-1') + await overseasUpload('/api/pda/v1/last-leg/photos/files', '/tmp/photo.jpg') + await overseasDownload('/api/pda/v1/last-leg/photos/files/1/download') + + session.apiBase = 'https://overseas.example.com/api/' + await resolvePallet('PLT-1') + await overseasUpload('/api/pda/v1/last-leg/photos/files', '/tmp/photo.jpg') + await overseasDownload('/api/pda/v1/last-leg/photos/files/1/download') + + assert.deepEqual(requests, [ + '/overseas-api/pda/v1/last-leg/pallet-operations/pallets/resolve', + 'https://overseas.example.com/api/pda/v1/last-leg/pallet-operations/pallets/resolve' + ]) + assert.deepEqual(uploads, [ + '/overseas-api/pda/v1/last-leg/photos/files', + 'https://overseas.example.com/api/pda/v1/last-leg/photos/files' + ]) + assert.deepEqual(downloads, [ + '/overseas-api/pda/v1/last-leg/photos/files/1/download', + 'https://overseas.example.com/api/pda/v1/last-leg/photos/files/1/download' + ]) + for (const url of [...requests, ...uploads, ...downloads]) assert.doesNotMatch(url, /\/api\/api\//) +}) + test('concurrent old-token 401 responses share one refresh and each replay once with the atomically replaced token', async () => { const old = prepareSession(); const next = tokens('new') let refreshCount = 0; let oldCalls = 0; let newCalls = 0 diff --git a/tests/pallet-operations.test.mjs b/tests/pallet-operations.test.mjs index cf357ac..a849faa 100644 --- a/tests/pallet-operations.test.mjs +++ b/tests/pallet-operations.test.mjs @@ -21,6 +21,8 @@ test('pallet API maps only the frozen overseas PDA operation endpoints', async ( assert.match(api, new RegExp(path.replace('/', '\\/'))) } assert.match(api, /overseasRequest/) + assert.match(api, /const BASE = '\/pda\/v1\/last-leg\/pallet-operations'/) + assert.doesNotMatch(api, /const BASE = '\/api\/pda/) for (const forbidden of ['customerId', 'warehouseId', 'locationId']) assert.doesNotMatch(api, new RegExp(forbidden)) })