8cab9d4bad
- 服务端:对「输入值(未保存)」的检测结果按 uid+模块+值指纹暂存 Redis(TTL 30min, Redis 异常降级为需重新检测,不阻断保存);保存同一个值时落库该结果 (passed/failed/error 一并沿用),改过值或从未检测则维持未检测 - 前端:保存后清理本地「输入值(未保存)」绿字,展示统一走服务端快照,避免展示与门禁矛盾; 门禁提示改列「模块名(掩码):未检测 / 检测失败:原因 / 未配置」,同名掩码也能分辨模块 - 测试:UserApiSecretServiceTest 补沿用 / 值不一致 / Redis 降级用例; 新增前后端一致性守卫测试(保存后必须清检测结果、提示必须带模块名)
60 lines
2.7 KiB
TypeScript
60 lines
2.7 KiB
TypeScript
import { test } from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { readFileSync } from 'node:fs'
|
|
import { fileURLToPath } from 'node:url'
|
|
import { resolve, dirname } from 'node:path'
|
|
|
|
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
const read = (rel: string) => readFileSync(resolve(repoRoot, rel), 'utf-8')
|
|
|
|
/** 截取函数体:先按括号配平跳过参数列表,再按花括号配平取函数体(模板字面量里的 ${} 也是配对的,不影响计数)。 */
|
|
function functionBody(source: string, signature: string): string {
|
|
const start = source.indexOf(signature)
|
|
assert.ok(start >= 0, `未找到 ${signature}`)
|
|
const openParen = source.indexOf('(', start)
|
|
let parenDepth = 0
|
|
let cursor = openParen
|
|
for (; cursor < source.length; cursor += 1) {
|
|
if (source[cursor] === '(') parenDepth += 1
|
|
else if (source[cursor] === ')') {
|
|
parenDepth -= 1
|
|
if (parenDepth === 0) break
|
|
}
|
|
}
|
|
const openBrace = source.indexOf('{', cursor)
|
|
let depth = 0
|
|
for (let i = openBrace; i < source.length; i += 1) {
|
|
if (source[i] === '{') depth += 1
|
|
else if (source[i] === '}') {
|
|
depth -= 1
|
|
if (depth === 0) return source.slice(openBrace, i + 1)
|
|
}
|
|
}
|
|
throw new Error('函数体未闭合')
|
|
}
|
|
|
|
/**
|
|
* 密钥卡片保存后必须清掉本地「检测输入值(未保存)」的结果:
|
|
* 保存后输入框已清空,那份绿字不再代表当前配置,继续展示就会出现
|
|
* 「三项都显示检测通过、页面却提示尚未通过检测」的自相矛盾状态(2026-09-14 修复)。
|
|
*/
|
|
test('密钥面板保存后清理过期检测结果', () => {
|
|
const body = functionBody(read('src/shared/components/ApiSecretSettingsPanel.vue'), 'async function saveAll(')
|
|
const saveIndex = body.indexOf('await saveApiSecret(')
|
|
assert.ok(saveIndex >= 0, 'saveAll 应逐个保存模块输入值')
|
|
const afterSave = body.slice(saveIndex)
|
|
assert.match(afterSave, /state\.input = ''/, '保存后应清空输入框')
|
|
assert.match(afterSave, /state\.result = null/, '保存后应清掉本地检测结果')
|
|
assert.match(afterSave, /state\.lastCheckSource = ''/, '保存后应重置检测来源标记')
|
|
})
|
|
|
|
/**
|
|
* 门禁拦截提示必须带模块名:同一个密钥填进多个模块时,
|
|
* 只列脱敏值会出现「sk-P****9BgQ、sk-P****9BgQ」这种分不清是哪个模块的提示。
|
|
*/
|
|
test('密钥门禁提示按模块名列出未通过项', () => {
|
|
const body = functionBody(read('src/pages/setup/DesktopSecretSetupPage.vue'), 'async function submit(')
|
|
assert.match(body, /module\.moduleLabel/, '未通过提示应包含模块名')
|
|
assert.match(body, /snapshot\.masked/, '未通过提示应包含脱敏值')
|
|
})
|