This commit is contained in:
2026-08-25 20:50:01 +08:00
parent f6d4ed6c3c
commit 6572771227
12 changed files with 341 additions and 86 deletions
+59
View File
@@ -0,0 +1,59 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import { readFile } from 'node:fs/promises'
const read = (path) => readFile(new URL(path, import.meta.url), 'utf8')
test('authenticated PDA requests refresh once and replay once after unauthorized', async () => {
const http = await read('../api/http.js')
assert.match(http, /let refreshPromise = null/)
assert.match(http, /refreshPromise = \(async \(\) =>/)
assert.match(http, /session\.token && session\.token !== accessToken \? session\.token : await refreshAccessToken\(\)/)
assert.match(http, /response = await execute\(accessToken\)[\s\S]*?isUnauthorized\(response\)[\s\S]*?response = await execute\(accessToken\)/)
assert.match(http, /apiRequest/)
assert.match(http, /apiDownload/)
assert.match(http, /apiUpload/)
})
test('refresh rotation persists the complete token pair and preserves operator context', async () => {
const [session, auth] = await Promise.all([read('../stores/session.js'), read('../api/auth.js')])
assert.match(session, /export function updateTokenPair\(tokens\)/)
assert.match(session, /session\.refreshToken = tokens\.refreshToken/)
assert.match(session, /const previousUser = session\.currentUser \|\| \{\}/)
assert.match(session, /persistSession\(\)/)
assert.match(session, /isAccessTokenExpiring/)
assert.match(auth, /authRequest\('\/auth\/refresh', \{ refreshToken \}\)/)
})
test('app launch restores and validates persisted tokens before entering the workbench', async () => {
const [login, http] = await Promise.all([read('../pages/login/login.vue'), read('../api/http.js')])
assert.match(login, /async onLoad\(\)[\s\S]*?restoreSession\(\)[\s\S]*?await ensureAccessToken\(\)[\s\S]*?pages\/home\/home/)
assert.match(http, /export function ensureAccessToken\(\)/)
assert.match(login, /暂时无法恢复登录状态/)
})
test('confirmed session expiry clears sensitive state and redirects only once', async () => {
const http = await read('../api/http.js')
assert.match(http, /observedRefreshToken !== session\.refreshToken/)
assert.match(http, /signOut\(\)[\s\S]*?redirectAfterSessionExpiry\(\)/)
assert.match(http, /if \(redirectingToLogin\) return/)
assert.match(http, /url: '\/pages\/login\/login'/)
assert.match(http, /if \(isUnauthorized\(error\)\) return expireSession/)
})
test('manual logout revokes the refresh token without delaying local cleanup', async () => {
const [profile, auth] = await Promise.all([read('../pages/profile/profile.vue'), read('../api/auth.js')])
assert.match(auth, /export function logout\(refreshToken, accessToken\)/)
assert.match(profile, /const refreshToken = session\.refreshToken; const accessToken = session\.token; signOut\(\); uni\.reLaunch/)
assert.match(profile, /logoutRequest\(refreshToken, accessToken\)\.catch/)
})
test('development login remembers only the account preference, never the entered password', async () => {
const [login, session] = await Promise.all([read('../pages/login/login.vue'), read('../stores/session.js')])
assert.match(login, /form: \{ userId: 'platform_admin', password: 'ChangeMe123!' \}/)
assert.match(session, /export function saveLoginPreference\(rememberAccount, account = ''\)/)
const savePreference = session.match(/export function saveLoginPreference[\s\S]*?\n\}/)?.[0] || ''
assert.match(savePreference, /account: rememberAccount/)
assert.doesNotMatch(savePreference, /password/)
assert.match(login, /if \(!this\.rememberAccount\) saveLoginPreference\(false\)/)
})
+4 -3
View File
@@ -5,10 +5,11 @@ import { readFile } from 'node:fs/promises'
const read = (path) => readFile(new URL(path, import.meta.url), 'utf8')
test('remote PDA mutations use the shared online gate', async () => {
const [api, network, page] = await Promise.all([
read('../api/pda-loading.js'), read('../utils/network.js'), read('../pages/picking/picking.vue')
const [api, http, network, page] = await Promise.all([
read('../api/pda-loading.js'), read('../api/http.js'), read('../utils/network.js'), read('../pages/picking/picking.vue')
])
assert.match(api, /function request\(options\) \{[\s\S]*?requireOnline\(\)/)
assert.match(api, /const request = apiRequest/)
assert.match(http, /async function runAuthenticated\(execute\) \{[\s\S]*?requireOnline\(\)/)
assert.match(api, /uploadEvidenceFile[\s\S]*?requireOnline\('当前网络不可用,无法上传装柜照片'\)/)
assert.match(network, /uni\.onNetworkStatusChange/)
assert.match(network, /result\.networkType !== 'none'/)
+5
View File
@@ -31,7 +31,12 @@ test('login uses the real auth endpoint and relaunches to the modular workbench'
assert.match(auth, /\/auth\/login/)
assert.match(login, /loginRequest\(userId, this\.form\.password\)/)
assert.match(login, /signInWithTokens\(tokens\)/)
assert.match(login, /userId: 'platform_admin', password: 'ChangeMe123!'/)
assert.match(login, /记住账号/)
assert.match(login, /loadLoginPreference\(\)/)
assert.match(login, /saveLoginPreference\(this\.rememberAccount, userId\)/)
assert.match(session, /pda-auth-session/)
assert.match(session, /pda-login-account-preference/)
assert.match(session, /claims\.app_code !== 'PLATFORM'/)
assert.match(runtime, /import\.meta\.env\.VITE_API_BASE_URL/)
assert.doesNotMatch(runtime, /typeof\s+import\.meta/)
+8 -5
View File
@@ -3,6 +3,7 @@ import assert from 'node:assert/strict'
import { readFile } from 'node:fs/promises'
const source = await readFile(new URL('../api/pda-loading.js', import.meta.url), 'utf8')
const http = await readFile(new URL('../api/http.js', import.meta.url), 'utf8')
const page = await readFile(new URL('../pages/picking/picking.vue', import.meta.url), 'utf8')
test('PDA loading adapter keeps C04 read and write paths', () => {
@@ -17,7 +18,8 @@ test('PDA loading adapter keeps C04 read and write paths', () => {
assert.match(source, /'Idempotency-Key'/)
assert.match(source, /PHOTO_STEP_1[\s\S]*PHOTO_STEP_4[\s\S]*SEAL/)
for (const label of ['空仓照片', '半仓照片', '满仓照片', '锁仓照片', '带锁码照']) assert.match(source, new RegExp(label))
assert.match(source, /uni\.uploadFile/)
assert.match(source, /apiUpload\(/)
assert.match(http, /uni\.uploadFile/)
assert.match(source, /\/files\/loading-evidence\//)
assert.match(source, /loading\/evidence\/pda\?tenantId=/)
assert.match(source, /loading\/evidence\/\$\{encodeURIComponent\(evidenceId\)\}\/void\/pda\?tenantId=/)
@@ -27,8 +29,8 @@ test('PDA loading adapter keeps C04 read and write paths', () => {
})
test('PDA adapter does not treat non-200 response bodies or network failures as success', () => {
assert.match(source, /if \(code !== '200'\)/)
assert.match(source, /next\.network = true/)
assert.match(http, /if \(code !== '200'\)/)
assert.match(http, /next\.network = true/)
assert.match(source, /throw new Error\('缺少客户隔离标识 tenantId/)
})
@@ -81,8 +83,9 @@ test('PDA downloads authenticated evidence previews returned by the backend', ()
assert.match(source, /export function getEvidencePreviewUrl\(inboundId, evidenceId, tenantId\)/)
assert.match(source, /loading\/evidence\/\$\{encodeURIComponent\(evidenceId\)\}\/content\/pda\?tenantId=/)
assert.match(source, /export function downloadEvidencePreview\(previewUrl\)/)
assert.match(source, /uni\.downloadFile\(/)
assert.match(source, /headers\.Authorization = `Bearer \$\{session\.token\}`/)
assert.match(source, /apiDownload\(\{ url \}\)/)
assert.match(http, /uni\.downloadFile\(/)
assert.match(http, /headers\.Authorization = `Bearer \$\{accessToken\}`/)
assert.match(source, /response\.tempFilePath/)
assert.match(source, /evidence\.preview\.response/)
assert.match(source, /\^image\\\//)