Why: The 2026-10-13 login contract makes username/password the default login, replaces the browser ask_ tab with a one-time recovery key entry, requires the forced set-password gate to drop the current password field, and routes admin password recovery through admin-issued login keys instead of direct password resets. Evidence: vitest 218 passed, npm run build OK, check-knip 43<=43, design-guard green (SELF, local run in task worktree). Impact: LoginPage gains a redeem-only key tab (no key persistence); ChangePasswordGate shows server-confirmed owner plus new/confirm only and returns to the login page after success (reloginRequired); App gates on sessionScope=set_password_only; UsersPanel replaces reset password with issue/revoke login key UI showing plaintext once. Refs: Dev task feature/svc-login-recovery-flow; backend companion commit 467718a.
50 lines
2.9 KiB
TypeScript
50 lines
2.9 KiB
TypeScript
// 登录/改密门错误文案:登录与改密的 401 语义不同,映射分开锁定
|
|
// (后端错误串是现行 HTTP 契约的一部分,这里逐字对拍防漂移)。
|
|
import { describe, expect, it } from 'vitest'
|
|
import { humanChangePasswordError, humanLoginError, humanRedeemError } from '../src/components/LoginPage'
|
|
|
|
describe('humanLoginError(登录)', () => {
|
|
it('401 统一为用户名或密码不正确', () => {
|
|
expect(humanLoginError('401: invalid owner or password')).toBe('用户名或密码不正确')
|
|
})
|
|
it('429/412 各有文案,其余剥状态码前缀', () => {
|
|
expect(humanLoginError('429: too many failed attempts; retry later')).toBe('失败次数过多,请稍后再试')
|
|
expect(humanLoginError('412: auth disabled (embedded single-tenant mode)')).toBe('本服务未开启登录(嵌入式单租户档)')
|
|
expect(humanLoginError('400: bad')).toBe('bad')
|
|
})
|
|
})
|
|
|
|
describe('humanChangePasswordError(改密门)', () => {
|
|
it('当前密码错误不再伪装成登录失败', () => {
|
|
expect(humanChangePasswordError('401: current password incorrect')).toBe('当前密码不正确')
|
|
})
|
|
it('无会话(API key 凭证)给出可行动指引', () => {
|
|
expect(humanChangePasswordError('401: session login required (API keys do not own passwords)')).toBe('请先用账号密码登录再改密(API 密钥不能修改密码)')
|
|
})
|
|
it('会话过期与失效 key 各有文案', () => {
|
|
expect(humanChangePasswordError('401: session expired')).toBe('会话已过期,请重新登录')
|
|
expect(humanChangePasswordError('401: unauthorized: invalid or revoked API key')).toBe('本地残留的 API 密钥已失效,请退出登录后重试')
|
|
})
|
|
it('未知 401 回落通用文案,其余错误剥前缀透传', () => {
|
|
expect(humanChangePasswordError('401: whatever')).toBe('认证失败,请重新登录')
|
|
expect(humanChangePasswordError('400: password must be 8-256 characters')).toBe('password must be 8-256 characters')
|
|
})
|
|
})
|
|
|
|
describe('humanRedeemError(一次性登录 Key 兑换)', () => {
|
|
it('409 已使用 / 401 无效过期 / 429 退避各有文案', () => {
|
|
expect(humanRedeemError('409: login key already used')).toBe('该 Key 已被使用过,请联系管理员重新签发')
|
|
expect(humanRedeemError('401: invalid or expired login key')).toBe('Key 无效或已过期,请联系管理员重新签发')
|
|
expect(humanRedeemError('429: too many failed attempts; retry later')).toBe('尝试次数过多,请稍后再试')
|
|
})
|
|
it('未知错误剥状态码前缀透传', () => {
|
|
expect(humanRedeemError('400: bad')).toBe('bad')
|
|
})
|
|
})
|
|
|
|
describe('humanChangePasswordError(登录恢复新增文案)', () => {
|
|
it('并发凭据变化给出重新登录指引', () => {
|
|
expect(humanChangePasswordError('401: credentials changed concurrently; retry login')).toBe('登录状态已变化(密码在其他地方被更新),请重新登录')
|
|
})
|
|
})
|