task-45(账号/权限页面): 实现新建用户表单模型
新增 user-create-model.ts 纯模型:role 仅 normal|admin(未知/超管归一 normal, 镜像后端白名单)、用户名≥2/密码≥6 镜像校验、toCreateUserRequest 去空格/拷 columnIds,字段对齐 Java AdminUserCreateRequest(camelCase)。8 用例全过, 323 单测 + build 绿。
This commit is contained in:
@@ -0,0 +1,65 @@
|
|||||||
|
/** 新建用户表单模型(任务 45):纯逻辑,镜像 Java AdminUserCreateRequest 字段与后端校验。 */
|
||||||
|
|
||||||
|
export const CREATE_USERNAME_MIN_LEN = 2
|
||||||
|
export const CREATE_PASSWORD_MIN_LEN = 6
|
||||||
|
|
||||||
|
/** 新建用户可选角色:super_admin 不可由表单创建(后端只收 admin|normal)。 */
|
||||||
|
export type CreatableUserRole = 'normal' | 'admin'
|
||||||
|
|
||||||
|
export interface CreateUserForm {
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
role: CreatableUserRole
|
||||||
|
/** 授权菜单 id(后端 replaceDirectPermissions 用)。 */
|
||||||
|
columnIds: number[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateUserFormErrors {
|
||||||
|
username?: string
|
||||||
|
password?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 发送 POST /api/admin/user 的请求体(与 Java AdminUserCreateRequest camelCase 对齐)。 */
|
||||||
|
export interface CreateUserPayload {
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
role: string
|
||||||
|
columnIds: number[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createEmptyCreateUserForm(): CreateUserForm {
|
||||||
|
return { username: '', password: '', role: 'normal', columnIds: [] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 未知/空白/超管角色一律归一到 normal,与后端 "admin"/"normal" 白名单一致。 */
|
||||||
|
export function normalizeCreateRole(role: string | null | undefined): CreatableUserRole {
|
||||||
|
return (role || '').trim().toLowerCase() === 'admin' ? 'admin' : 'normal'
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 表单校验(镜像后端:用户名≥2、密码≥6、均必填);返回逐字段错误。 */
|
||||||
|
export function validateCreateUserForm(form: CreateUserForm): { valid: boolean; errors: CreateUserFormErrors } {
|
||||||
|
const errors: CreateUserFormErrors = {}
|
||||||
|
const username = (form.username || '').trim()
|
||||||
|
if (!username) {
|
||||||
|
errors.username = '用户名不能为空'
|
||||||
|
} else if (username.length < CREATE_USERNAME_MIN_LEN) {
|
||||||
|
errors.username = `用户名至少${CREATE_USERNAME_MIN_LEN}个字符`
|
||||||
|
}
|
||||||
|
const password = form.password || ''
|
||||||
|
if (!password) {
|
||||||
|
errors.password = '密码不能为空'
|
||||||
|
} else if (password.length < CREATE_PASSWORD_MIN_LEN) {
|
||||||
|
errors.password = `密码至少${CREATE_PASSWORD_MIN_LEN}个字符`
|
||||||
|
}
|
||||||
|
return { valid: Object.keys(errors).length === 0, errors }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 表单 → 创建请求体:用户名去空格、角色归一、菜单 id 拷贝不共享引用。 */
|
||||||
|
export function toCreateUserRequest(form: CreateUserForm): CreateUserPayload {
|
||||||
|
return {
|
||||||
|
username: (form.username || '').trim(),
|
||||||
|
password: form.password || '',
|
||||||
|
role: normalizeCreateRole(form.role),
|
||||||
|
columnIds: Array.isArray(form.columnIds) ? form.columnIds.slice() : [],
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import test from 'node:test'
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import { readSource } from './helpers.ts'
|
||||||
|
import {
|
||||||
|
createEmptyCreateUserForm,
|
||||||
|
normalizeCreateRole,
|
||||||
|
toCreateUserRequest,
|
||||||
|
validateCreateUserForm,
|
||||||
|
} from '../src/pages/account/user-create-model.ts'
|
||||||
|
|
||||||
|
test('test_task_045_create_user_form_normal_primary_path', () => {
|
||||||
|
// 正常主路径:合法用户名/密码/角色通过校验。
|
||||||
|
const form = createEmptyCreateUserForm()
|
||||||
|
form.username = '张伟恒'
|
||||||
|
form.password = 'secret1'
|
||||||
|
form.role = 'normal'
|
||||||
|
const r = validateCreateUserForm(form)
|
||||||
|
assert.equal(r.valid, true)
|
||||||
|
assert.deepEqual(r.errors, {})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_task_045_create_user_form_normal_variant_input', () => {
|
||||||
|
// 正常变体:admin 角色可由 super_admin 创建时通过;用户名/密码去空格后入参。
|
||||||
|
const form = { username: ' ops ', password: '123456', role: 'admin' as const, columnIds: [] }
|
||||||
|
assert.equal(validateCreateUserForm(form).valid, true)
|
||||||
|
const payload = toCreateUserRequest(form)
|
||||||
|
assert.equal(payload.username, 'ops')
|
||||||
|
assert.equal(payload.password, '123456')
|
||||||
|
assert.equal(payload.role, 'admin')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_task_045_create_user_form_normal_repeated_operation_is_idempotent', () => {
|
||||||
|
// 正常重复:空表单工厂稳定;序列化纯函数不改输入。
|
||||||
|
assert.deepEqual(createEmptyCreateUserForm(), createEmptyCreateUserForm())
|
||||||
|
const form = { username: 'a', password: '123456', role: 'normal' as const, columnIds: [] }
|
||||||
|
assert.deepEqual(toCreateUserRequest(form), toCreateUserRequest(form))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_task_045_create_user_form_boundary_empty_input', () => {
|
||||||
|
// 边界空值:空用户名/空密码报可读错误。
|
||||||
|
const form = createEmptyCreateUserForm()
|
||||||
|
const r = validateCreateUserForm(form)
|
||||||
|
assert.equal(r.valid, false)
|
||||||
|
assert.ok(r.errors.username)
|
||||||
|
assert.ok(r.errors.password)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_task_045_create_user_form_boundary_single_item', () => {
|
||||||
|
// 边界单元素:用户名恰好 2 字符、密码恰好 6 字符可通过(镜像后端最小长度)。
|
||||||
|
const form = { username: 'ab', password: '123456', role: 'normal' as const, columnIds: [] }
|
||||||
|
assert.equal(validateCreateUserForm(form).valid, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_task_045_create_user_form_boundary_limit_or_missing_field', () => {
|
||||||
|
// 边界上限/过短:用户名 1 字符、密码 5 字符被拒并带长度提示。
|
||||||
|
const r = validateCreateUserForm({ username: 'a', password: '12345', role: 'normal', columnIds: [] })
|
||||||
|
assert.equal(r.valid, false)
|
||||||
|
assert.match(r.errors.username || '', /2/)
|
||||||
|
assert.match(r.errors.password || '', /6/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_task_045_create_user_form_invalid_input_rejected', () => {
|
||||||
|
// 异常输入:未知角色串归一到 normal,不传非法角色。
|
||||||
|
assert.equal(normalizeCreateRole('super_admin'), 'normal')
|
||||||
|
assert.equal(normalizeCreateRole(''), 'normal')
|
||||||
|
const payload = toCreateUserRequest({ username: 'u', password: '123456', role: 'xxx' as never, columnIds: [] })
|
||||||
|
assert.equal(payload.role, 'normal')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_task_045_create_user_form_dependency_failure_returns_actionable_message', () => {
|
||||||
|
// 依赖失败:表单模型纯逻辑,字段与 Java AdminUserCreateRequest 对齐(camelCase)。
|
||||||
|
const mod = readSource('src/pages/account/user-create-model.ts')
|
||||||
|
assert.equal(/axios|from '\.\.\/\.\.\/api\/http'/.test(mod), false, '表单模型保持纯逻辑')
|
||||||
|
assert.match(mod, /username/)
|
||||||
|
assert.match(mod, /columnIds/)
|
||||||
|
assert.match(mod, /password/)
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user