修复:保障移动端批次模式可达
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { execFile } from 'node:child_process'
|
||||
import { lstat, readFile, realpath } from 'node:fs/promises'
|
||||
import { test } from 'node:test'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import path from 'node:path'
|
||||
import { promisify } from 'node:util'
|
||||
import * as testBatchContext from '../src/utils/testBatchContext.js'
|
||||
|
||||
const frontendRoot = fileURLToPath(new URL('..', import.meta.url))
|
||||
const execFileAsync = promisify(execFile)
|
||||
|
||||
const expectedRequests = {
|
||||
'src/api/master/strain.js': [
|
||||
@@ -837,6 +840,108 @@ function hasExpectedRequest(expectedRequest) {
|
||||
|
||||
const source = (relativePath) => readFile(path.join(frontendRoot, relativePath), 'utf8')
|
||||
|
||||
async function runRequestUnavailableInterceptorContract() {
|
||||
const harness = String.raw`
|
||||
const { readFile } = require('node:fs/promises')
|
||||
const vm = require('node:vm')
|
||||
|
||||
async function main() {
|
||||
const requestPath = process.argv[1]
|
||||
const requestSource = await readFile(requestPath, 'utf8')
|
||||
const interceptors = {}
|
||||
const recoveries = []
|
||||
const service = {
|
||||
interceptors: {
|
||||
request: { use: (fulfilled, rejected) => { interceptors.request = { fulfilled, rejected } } },
|
||||
response: { use: (fulfilled, rejected) => { interceptors.response = { fulfilled, rejected } } }
|
||||
},
|
||||
post: () => Promise.resolve()
|
||||
}
|
||||
const context = vm.createContext({ console, Promise, Error, JSON, Object, Date, setTimeout })
|
||||
const moduleFromExports = exports => new vm.SyntheticModule(
|
||||
Object.keys(exports),
|
||||
function setExports() {
|
||||
for (const [name, value] of Object.entries(exports)) this.setExport(name, value)
|
||||
},
|
||||
{ context }
|
||||
)
|
||||
const modules = {
|
||||
axios: { default: { defaults: { headers: {} }, create: () => service } },
|
||||
'element-plus': {
|
||||
ElNotification: { error: () => {} },
|
||||
ElMessageBox: { confirm: () => Promise.resolve() },
|
||||
ElMessage: () => {},
|
||||
ElLoading: { service: () => ({ close: () => {} }) }
|
||||
},
|
||||
'@/utils/auth': { getToken: () => 'token' },
|
||||
'@/utils/errorCode': { default: {} },
|
||||
'@/utils/shierfeng': { tansParams: () => '', blobValidate: () => false },
|
||||
'@/plugins/cache': { default: { session: { getJSON: () => undefined, setJSON: () => {} } } },
|
||||
'file-saver': { saveAs: () => {} },
|
||||
'@/store/modules/user': { default: () => ({ logOut: () => Promise.resolve() }) },
|
||||
'@/utils/testBatchContext': {
|
||||
applyTestBatchHeader: config => config,
|
||||
getStoredTestBatchId: () => '',
|
||||
recoverUnavailableTestBatch: async ({ notify, message }) => {
|
||||
recoveries.push(message)
|
||||
notify(message)
|
||||
}
|
||||
},
|
||||
'@/utils/transportCrypto': {
|
||||
decryptTransportErrorResponse: async error => error,
|
||||
decryptTransportResponse: async response => response,
|
||||
encryptTransportRequest: async config => config,
|
||||
invalidateTransportKeyMeta: () => {},
|
||||
resetTransportRequestConfig: () => {},
|
||||
shouldRetryTransportWithFreshKey: () => false
|
||||
}
|
||||
}
|
||||
const requestModule = new vm.SourceTextModule(requestSource, {
|
||||
context,
|
||||
identifier: requestPath,
|
||||
initializeImportMeta(meta) {
|
||||
meta.env = { VITE_APP_BASE_API: '' }
|
||||
}
|
||||
})
|
||||
await requestModule.link(specifier => {
|
||||
if (!modules[specifier]) throw new Error('未受控依赖: ' + specifier)
|
||||
return moduleFromExports(modules[specifier])
|
||||
})
|
||||
await requestModule.evaluate()
|
||||
|
||||
const invoke = async callback => {
|
||||
try {
|
||||
await callback()
|
||||
return { resolved: true }
|
||||
} catch (error) {
|
||||
return { resolved: false, message: error.message, reason: error.reason }
|
||||
}
|
||||
}
|
||||
const success = await invoke(() => interceptors.response.fulfilled({
|
||||
data: { reason: 'TEST_BATCH_UNAVAILABLE', msg: '成功响应批次不可用' },
|
||||
request: { responseType: '' }
|
||||
}))
|
||||
const failure = await invoke(() => interceptors.response.rejected({
|
||||
response: { data: { reason: 'TEST_BATCH_UNAVAILABLE', msg: '异常响应批次不可用' } },
|
||||
message: 'Request failed with status code 400'
|
||||
}))
|
||||
process.stdout.write(JSON.stringify({ recoveries, success, failure }))
|
||||
}
|
||||
|
||||
main().catch(error => {
|
||||
console.error(error)
|
||||
process.exitCode = 1
|
||||
})
|
||||
`
|
||||
const { stdout } = await execFileAsync(process.execPath, [
|
||||
'--experimental-vm-modules',
|
||||
'-e',
|
||||
harness,
|
||||
path.join(frontendRoot, 'src/utils/request.js')
|
||||
])
|
||||
return JSON.parse(stdout)
|
||||
}
|
||||
|
||||
test('Web 测试模式统一附加请求头并通过路由重建清空页面状态', async () => {
|
||||
const appMainSource = await source('src/layout/components/AppMain.vue')
|
||||
const navbarSource = await source('src/layout/components/Navbar.vue')
|
||||
@@ -928,6 +1033,22 @@ test('刷新失效批次复用统一回退提示,登出无论远端结果都
|
||||
assert.equal(testBatchContext.getStoredTestBatchId(), '')
|
||||
})
|
||||
|
||||
test('request.js 的成功与异常响应拦截器都会恢复不可用批次并拒绝原请求', async () => {
|
||||
const result = await runRequestUnavailableInterceptorContract()
|
||||
|
||||
assert.deepEqual(result.recoveries, ['成功响应批次不可用', '异常响应批次不可用'])
|
||||
assert.deepEqual(result.success, {
|
||||
resolved: false,
|
||||
message: '成功响应批次不可用',
|
||||
reason: 'TEST_BATCH_UNAVAILABLE'
|
||||
})
|
||||
assert.deepEqual(result.failure, {
|
||||
resolved: false,
|
||||
message: '异常响应批次不可用',
|
||||
reason: 'TEST_BATCH_UNAVAILABLE'
|
||||
})
|
||||
})
|
||||
|
||||
test('窄屏模式控件仍保留正式退出与测试批次切换入口', async () => {
|
||||
const switchSource = await source('src/components/TestBatchModeSwitch/index.vue')
|
||||
const navbarSource = await source('src/layout/components/Navbar.vue')
|
||||
@@ -940,6 +1061,8 @@ test('窄屏模式控件仍保留正式退出与测试批次切换入口', async
|
||||
assert.match(mobileSection, /padding-right:\s*2px/)
|
||||
assert.match(mobileSection, /padding-left:\s*2px/)
|
||||
assert.match(navbarMobileSection, /\.test-batch-mode\s*\{\s*padding:\s*0/)
|
||||
assert.match(navbarMobileSection, /\.breadcrumb-container\s*\{\s*display:\s*none/)
|
||||
assert.match(navbarMobileSection, /\.right-menu\s*\{\s*overflow:\s*visible/)
|
||||
})
|
||||
|
||||
test('17 个页面的真实权限门禁逐项匹配独立硬编码契约', async () => {
|
||||
|
||||
@@ -293,7 +293,14 @@ async function toggleTheme(event) {
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
.navbar .right-menu {
|
||||
.navbar {
|
||||
.breadcrumb-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.right-menu {
|
||||
overflow: visible;
|
||||
|
||||
.test-batch-mode {
|
||||
padding: 0;
|
||||
}
|
||||
@@ -307,4 +314,5 @@ async function toggleTheme(event) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user