From dad9cda4acf617621efaa2ff4a9c0053c09b6632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E8=87=AA=E8=BE=BE?= <980324341@qq.com> Date: Sat, 5 Sep 2026 14:55:11 +0800 Subject: [PATCH] =?UTF-8?q?task-45(=E8=B4=A6=E5=8F=B7/=E6=9D=83=E9=99=90?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2):=20=E5=AE=9E=E7=8E=B0=E6=96=B0=E5=BB=BA?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E8=A1=A8=E5=8D=95=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 user-create-model.ts 纯模型:role 仅 normal|admin(未知/超管归一 normal, 镜像后端白名单)、用户名≥2/密码≥6 镜像校验、toCreateUserRequest 去空格/拷 columnIds,字段对齐 Java AdminUserCreateRequest(camelCase)。8 用例全过, 323 单测 + build 绿。 --- .../src/pages/account/user-create-model.ts | 65 ++++++++++++++++ admin-frontend-vue/tests/task-45.test.ts | 77 +++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 admin-frontend-vue/src/pages/account/user-create-model.ts create mode 100644 admin-frontend-vue/tests/task-45.test.ts diff --git a/admin-frontend-vue/src/pages/account/user-create-model.ts b/admin-frontend-vue/src/pages/account/user-create-model.ts new file mode 100644 index 00000000..d1e7b662 --- /dev/null +++ b/admin-frontend-vue/src/pages/account/user-create-model.ts @@ -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() : [], + } +} diff --git a/admin-frontend-vue/tests/task-45.test.ts b/admin-frontend-vue/tests/task-45.test.ts new file mode 100644 index 00000000..2fc8c443 --- /dev/null +++ b/admin-frontend-vue/tests/task-45.test.ts @@ -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/) +})