Smail/test/frontend/public-password-reset.test.mjs
smail 461d8b2237
Some checks are pending
Security Checks / security (push) Waiting to run
修复认证繁忙时前端恢复重试并补齐远程测试
2026-09-17 12:33:25 +08:00

93 lines
4.4 KiB
JavaScript

import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import test from 'node:test'
import { compileFunction } from 'node:vm'
import { computed, reactive, ref } from './vue.mjs'
const script = readFileSync(new URL('../../frontend/src/views/PublicPasswordReset/PublicPasswordReset.js', import.meta.url), 'utf8')
const template = readFileSync(new URL('../../frontend/src/views/PublicPasswordReset/PublicPasswordReset.vue', import.meta.url), 'utf8')
test('匿名恢复页仅在后端明确确认链接可用时显示密码表单', () => {
assert.match(script, /!result\.ok \|\| result\.data\?\.available !== true/)
assert.match(template, /v-else-if="available"/)
assert.match(template, /恢复链接无效或已失效,请重新申请。/)
})
const createPage = compileFunction(
script.replace(/^import .*$/gm, '').replace('export function usePage', 'function usePage') + '\nreturn usePage()',
['computed', 'reactive', 'ref', 'onMounted', 'onBeforeUnmount', 'isValidPasswordLength', 'postPublicRecovery', 'window']
)
async function fixture(t, requirements = { ok: true, status: 200, data: { available: true, requires_recovery_code: true } }) {
let mounted
let response = async () => ({ ok: true, status: 200, data: { reset: true } })
const requests = []
const page = createPage(computed, reactive, ref, callback => { mounted = callback }, callback => t.after(callback), () => true,
async (endpoint, payload) => {
requests.push({ endpoint, payload })
return endpoint === 'requirements' ? requirements : response()
}, {
location: { hash: '#/reset-password/retry-token' },
addEventListener() {},
removeEventListener() {}
})
mounted()
await new Promise(resolve => setImmediate(resolve))
Object.assign(page.form, { newPassword: 'NewPass123', confirmPassword: 'NewPass123', recoveryCode: 'offline-code' })
return { page, requests, respond: value => { response = value } }
}
test('匿名恢复繁忙保留链接和输入,成功重试后清空秘密', async t => {
const { page, requests, respond } = await fixture(t)
respond(async () => ({ ok: false, status: 503, data: { reset: false } }))
for (let index = 0; index < 12; index++) {
await page.completeReset()
assert.equal(page.page.value, 'reset')
assert.equal(page.form.newPassword, 'NewPass123')
assert.equal(page.form.recoveryCode, 'offline-code')
assert.equal(page.loading.value, false)
assert.match(page.errorMessage.value, /稍后重试/)
assert.doesNotMatch(page.errorMessage.value, /已失效/)
}
respond(async () => ({ ok: true, status: 200, data: { reset: true } }))
await page.completeReset()
assert.equal(requests.at(-1).payload.token, 'retry-token')
assert.equal(page.page.value, 'success')
assert.equal(page.form.newPassword, '')
assert.equal(page.form.recoveryCode, '')
})
test('匿名恢复服务临时不可用不误报链接失效,也不显示密码表单', async t => {
const { page } = await fixture(t, { ok: false, status: 503, data: {} })
assert.equal(page.available.value, false)
assert.match(page.errorMessage.value, /稍后刷新重试/)
assert.doesNotMatch(page.errorMessage.value, /已失效/)
})
test('匿名恢复网络错误不声称已重置,并保留重试输入', async t => {
const { page, respond } = await fixture(t)
respond(async () => { throw new Error('network') })
await page.completeReset()
assert.equal(page.page.value, 'reset')
assert.equal(page.form.newPassword, 'NewPass123')
assert.match(page.errorMessage.value, /无法确认/)
})
test('匿名恢复请求保留繁忙状态码,不携带会话或启用缓存', async () => {
const transport = readFileSync(new URL('../../frontend/src/utils/publicRecoveryHttp.js', import.meta.url), 'utf8')
const createTransport = compileFunction(
transport.replace('export async function', 'async function') + '\nreturn postPublicRecovery', ['fetch']
)
const post = createTransport(async (url, options) => {
assert.equal(url, '/rest/api/recovery/complete')
assert.equal(options.credentials, 'omit')
assert.equal(options.cache, 'no-store')
assert.equal(options.referrerPolicy, 'no-referrer')
return { ok: false, status: 503, json: async () => ({ reset: false }) }
})
const result = await post('complete', { token: 'test-token' })
assert.equal(result.ok, false)
assert.equal(result.status, 503)
assert.equal(result.data.reset, false)
})