修复:保障移动端批次模式可达
This commit is contained in:
@@ -1,11 +1,14 @@
|
|||||||
import assert from 'node:assert/strict'
|
import assert from 'node:assert/strict'
|
||||||
|
import { execFile } from 'node:child_process'
|
||||||
import { lstat, readFile, realpath } from 'node:fs/promises'
|
import { lstat, readFile, realpath } from 'node:fs/promises'
|
||||||
import { test } from 'node:test'
|
import { test } from 'node:test'
|
||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from 'node:url'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
|
import { promisify } from 'node:util'
|
||||||
import * as testBatchContext from '../src/utils/testBatchContext.js'
|
import * as testBatchContext from '../src/utils/testBatchContext.js'
|
||||||
|
|
||||||
const frontendRoot = fileURLToPath(new URL('..', import.meta.url))
|
const frontendRoot = fileURLToPath(new URL('..', import.meta.url))
|
||||||
|
const execFileAsync = promisify(execFile)
|
||||||
|
|
||||||
const expectedRequests = {
|
const expectedRequests = {
|
||||||
'src/api/master/strain.js': [
|
'src/api/master/strain.js': [
|
||||||
@@ -837,6 +840,108 @@ function hasExpectedRequest(expectedRequest) {
|
|||||||
|
|
||||||
const source = (relativePath) => readFile(path.join(frontendRoot, relativePath), 'utf8')
|
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 () => {
|
test('Web 测试模式统一附加请求头并通过路由重建清空页面状态', async () => {
|
||||||
const appMainSource = await source('src/layout/components/AppMain.vue')
|
const appMainSource = await source('src/layout/components/AppMain.vue')
|
||||||
const navbarSource = await source('src/layout/components/Navbar.vue')
|
const navbarSource = await source('src/layout/components/Navbar.vue')
|
||||||
@@ -928,6 +1033,22 @@ test('刷新失效批次复用统一回退提示,登出无论远端结果都
|
|||||||
assert.equal(testBatchContext.getStoredTestBatchId(), '')
|
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 () => {
|
test('窄屏模式控件仍保留正式退出与测试批次切换入口', async () => {
|
||||||
const switchSource = await source('src/components/TestBatchModeSwitch/index.vue')
|
const switchSource = await source('src/components/TestBatchModeSwitch/index.vue')
|
||||||
const navbarSource = await source('src/layout/components/Navbar.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-right:\s*2px/)
|
||||||
assert.match(mobileSection, /padding-left:\s*2px/)
|
assert.match(mobileSection, /padding-left:\s*2px/)
|
||||||
assert.match(navbarMobileSection, /\.test-batch-mode\s*\{\s*padding:\s*0/)
|
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 () => {
|
test('17 个页面的真实权限门禁逐项匹配独立硬编码契约', async () => {
|
||||||
|
|||||||
@@ -293,17 +293,25 @@ async function toggleTheme(event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 767px) {
|
@media screen and (max-width: 767px) {
|
||||||
.navbar .right-menu {
|
.navbar {
|
||||||
.test-batch-mode {
|
.breadcrumb-container {
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-container .avatar-wrapper .user-nickname {
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-container .avatar-wrapper .user-avatar {
|
.right-menu {
|
||||||
margin-right: 0;
|
overflow: visible;
|
||||||
|
|
||||||
|
.test-batch-mode {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-container .avatar-wrapper .user-nickname {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-container .avatar-wrapper .user-avatar {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user