task-46(账号/权限页面): 实现新建用户请求适配
新增 user-create-adapter.ts 编排提交:先 validateCreateUserForm 校验(不通过抛首条错误不发请求)、 再 toCreateUserRequest 序列化 camelCase 请求体 POST /api/admin/user、最后解析返回新用户 id。 parseCreatedUserId 纯解析下沉 users-model.ts:解包 ApiResponse、success=false 抛后端 message、 data 非正整数抛可读错误。8 用例全过,331 单测 + build 绿。
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
/** 用户列表查询适配(任务 42):纯解析模块,无 axios 依赖,与 Java AdminUserListVo 对齐。 */
|
/** 用户 API 响应解析模型:列表分页(任务 42)与新建用户(任务 46)负载归一。 */
|
||||||
import { unwrap } from './envelope.ts'
|
import { unwrap } from './envelope.ts'
|
||||||
import { emptyUserPageResult, type UserPageResult } from './users-dto.ts'
|
import { emptyUserPageResult, type UserPageResult } from './users-dto.ts'
|
||||||
import type { AdminUser } from '../types/admin'
|
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
|
if (typeof record.current_user_username === 'string') out.currentUserUsername = record.current_user_username
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 解包新建用户响应并取回新用户 id(POST /api/admin/user 的 data 为 userId)。 */
|
||||||
|
export function parseCreatedUserId(payload: unknown): number {
|
||||||
|
const data = unwrap<unknown>(payload)
|
||||||
|
const id = typeof data === 'number' && Number.isFinite(data) ? Math.floor(data) : null
|
||||||
|
if (id === null || id < 1) throw new Error('创建用户响应异常:未返回有效的用户 ID')
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|||||||
@@ -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<number> {
|
||||||
|
const { valid, errors } = validateCreateUserForm(form)
|
||||||
|
if (!valid) {
|
||||||
|
throw new Error(Object.values(errors)[0] || '创建用户表单校验未通过')
|
||||||
|
}
|
||||||
|
const { data } = await http.post<unknown>(CREATE_USER_ENDPOINT, toCreateUserRequest(form))
|
||||||
|
return parseCreatedUserId(data)
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user