diff --git a/admin-frontend-vue/src/api/users-model.ts b/admin-frontend-vue/src/api/users-model.ts index 9142575e..4f326340 100644 --- a/admin-frontend-vue/src/api/users-model.ts +++ b/admin-frontend-vue/src/api/users-model.ts @@ -1,4 +1,4 @@ -/** 用户列表查询适配(任务 42):纯解析模块,无 axios 依赖,与 Java AdminUserListVo 对齐。 */ +/** 用户 API 响应解析模型:列表分页(任务 42)与新建用户(任务 46)负载归一。 */ import { unwrap } from './envelope.ts' import { emptyUserPageResult, type UserPageResult } from './users-dto.ts' import type { AdminUser } from '../types/admin' @@ -52,3 +52,11 @@ export function parseUserPage(payload: unknown): UserPageResult { if (typeof record.current_user_username === 'string') out.currentUserUsername = record.current_user_username return out } + +/** 解包新建用户响应并取回新用户 id(POST /api/admin/user 的 data 为 userId)。 */ +export function parseCreatedUserId(payload: unknown): number { + const data = unwrap(payload) + const id = typeof data === 'number' && Number.isFinite(data) ? Math.floor(data) : null + if (id === null || id < 1) throw new Error('创建用户响应异常:未返回有效的用户 ID') + return id +} diff --git a/admin-frontend-vue/src/pages/account/user-create-adapter.ts b/admin-frontend-vue/src/pages/account/user-create-adapter.ts new file mode 100644 index 00000000..46609c9f --- /dev/null +++ b/admin-frontend-vue/src/pages/account/user-create-adapter.ts @@ -0,0 +1,24 @@ +/** 新建用户请求适配(任务 46):表单模型 → POST /api/admin/user → 新用户 id。 */ +import { http } from '@/api/http' +import { parseCreatedUserId } from '@/api/users-model' +import { + toCreateUserRequest, + validateCreateUserForm, + type CreateUserForm, +} from './user-create-model' + +/** Java AdminUserController.createUser 端点。 */ +export const CREATE_USER_ENDPOINT = '/api/admin/user' + +/** + * 提交新建用户:先校验表单(不通过直接抛首条错误,不发请求),再序列化请求体 POST, + * 最后解析返回的新用户 id;http/后端失败交由调用方提示,不吞异常。 + */ +export async function submitCreateUser(form: CreateUserForm): Promise { + const { valid, errors } = validateCreateUserForm(form) + if (!valid) { + throw new Error(Object.values(errors)[0] || '创建用户表单校验未通过') + } + const { data } = await http.post(CREATE_USER_ENDPOINT, toCreateUserRequest(form)) + return parseCreatedUserId(data) +} diff --git a/admin-frontend-vue/tests/task-46.test.ts b/admin-frontend-vue/tests/task-46.test.ts new file mode 100644 index 00000000..03c8e053 --- /dev/null +++ b/admin-frontend-vue/tests/task-46.test.ts @@ -0,0 +1,65 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { readSource } from './helpers.ts' +import { parseCreatedUserId } from '../src/api/users-model.ts' + +test('test_task_046_create_user_request_normal_primary_path', () => { + // 正常主路径:Java ApiResponse 信封 data 即为新建用户 id。 + const id = parseCreatedUserId({ success: true, message: '用户创建成功', data: 7 }) + assert.equal(id, 7) +}) + +test('test_task_046_create_user_request_normal_variant_input', () => { + // 正常变体:已解包/直传数字同样可归一;submit 序列化为 camelCase body 后 POST 创建端点。 + assert.equal(parseCreatedUserId({ data: 42 }), 42) + assert.equal(parseCreatedUserId(12), 12) + const mod = readSource('src/pages/account/user-create-adapter.ts') + assert.match(mod, /toCreateUserRequest/) + assert.match(mod, /api\/admin\/user/) +}) + +test('test_task_046_create_user_request_normal_repeated_operation_is_idempotent', () => { + // 正常重复:解析不改输入、结果稳定,不会重复触发副作用。 + const payload = { success: true, message: '用户创建成功', data: 11 } + assert.equal(parseCreatedUserId(payload), 11) + assert.equal(parseCreatedUserId(payload), 11) + assert.deepEqual(payload, { success: true, message: '用户创建成功', data: 11 }) +}) + +test('test_task_046_create_user_request_boundary_empty_input', () => { + // 边界空值:无 data 或空负载按可读错误拒绝,不崩溃。 + assert.throws(() => parseCreatedUserId({}), /创建用户响应异常/) + assert.throws(() => parseCreatedUserId(undefined as never), /创建用户响应异常/) +}) + +test('test_task_046_create_user_request_boundary_single_item', () => { + // 边界单元素:最小合法用户 id=1 正常返回。 + assert.equal(parseCreatedUserId({ success: true, data: 1 }), 1) +}) + +test('test_task_046_create_user_request_boundary_limit_or_missing_field', () => { + // 边界上限/缺字段:id 必须为正整数;0/null/缺 data 均被拒绝。 + assert.throws(() => parseCreatedUserId({ success: true, data: 0 }), /创建用户响应异常/) + assert.throws(() => parseCreatedUserId({ success: true, data: null }), /创建用户响应异常/) + assert.throws(() => parseCreatedUserId({ success: true, message: '用户创建成功' }), /创建用户响应异常/) +}) + +test('test_task_046_create_user_request_invalid_input_rejected', () => { + // 异常输入:success=false 抛后端 message;非数字 data(如字符串 id)被拒绝。 + assert.throws(() => parseCreatedUserId({ success: false, message: '用户名已存在' }), /用户名已存在/) + assert.throws(() => parseCreatedUserId({ success: true, data: '7' }), /创建用户响应异常/) +}) + +test('test_task_046_create_user_request_dependency_failure_returns_actionable_message', () => { + // 依赖失败:提交先经表单校验(不发请求)、序列化后 POST 创建端点;失败交给调用方,不吞异常。 + const mod = readSource('src/pages/account/user-create-adapter.ts') + assert.match(mod, /import \{ http \} from '@\/api\/http'/) + assert.match(mod, /validateCreateUserForm/) + assert.match(mod, /toCreateUserRequest/) + assert.match(mod, /http\.post/) + assert.match(mod, /parseCreatedUserId/) + assert.equal(/\.catch\(/.test(mod), false, 'adapter 不吞异常,交由调用方提示') + // 表单模型保持纯逻辑、不带 axios/http(任务 45 约束不回退)。 + const model = readSource('src/pages/account/user-create-model.ts') + assert.equal(/axios|from '\.\.\/\.\.\/api\/http'/.test(model), false) +})