From be6a17d4bda0063b9719c657690592f861a5fe53 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 16:39:04 +0800 Subject: [PATCH] =?UTF-8?q?task-85(=E5=BA=97=E9=93=BA=E4=B8=AD=E5=BF=83):?= =?UTF-8?q?=20=E5=AE=9E=E7=8E=B0=E7=BC=96=E8=BE=91=E5=BA=97=E9=93=BA?= =?UTF-8?q?=E5=AF=86=E9=92=A5=E8=A1=A8=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 shop-key-edit-model.ts(由列表行预填备注/紫鸟账号/令牌,令牌仅内存内 沿用) 并在 shop-key-api.ts 增加 updateShopKey(PUT /api/admin/shop-keys/{id})。 TDD: task-85.test.ts 8 用例先 RED 后 GREEN。 --- .../src/pages/shop/shop-key-api.ts | 14 +++ .../src/pages/shop/shop-key-edit-model.ts | 20 ++++ admin-frontend-vue/tests/task-85.test.ts | 94 +++++++++++++++++++ 3 files changed, 128 insertions(+) create mode 100644 admin-frontend-vue/src/pages/shop/shop-key-edit-model.ts create mode 100644 admin-frontend-vue/tests/task-85.test.ts diff --git a/admin-frontend-vue/src/pages/shop/shop-key-api.ts b/admin-frontend-vue/src/pages/shop/shop-key-api.ts index 38c68d8d..34887f54 100644 --- a/admin-frontend-vue/src/pages/shop/shop-key-api.ts +++ b/admin-frontend-vue/src/pages/shop/shop-key-api.ts @@ -36,3 +36,17 @@ export async function submitCreateShopKey(form: ShopKeyFormValues): Promise { + const { valid, errors } = validateShopKeyForm(form) + if (!valid) { + throw new Error(Object.values(errors)[0] || '店铺密钥表单校验未通过') + } + const { data } = await http.put(`${SHOP_KEYS_ENDPOINT}/${id}`, toShopKeyCreateRequest(form)) + const item = toShopKeyItem(unwrap(data)) + if (!item) { + throw new Error('更新店铺密钥响应异常:未返回有效记录') + } + return item +} diff --git a/admin-frontend-vue/src/pages/shop/shop-key-edit-model.ts b/admin-frontend-vue/src/pages/shop/shop-key-edit-model.ts new file mode 100644 index 00000000..d2c12ed3 --- /dev/null +++ b/admin-frontend-vue/src/pages/shop/shop-key-edit-model.ts @@ -0,0 +1,20 @@ +/** 店铺密钥编辑表单模型(任务 85):由列表行预填编辑表单,镜像后端更新语义,纯逻辑。 */ +import type { ShopKeyItem } from './shop-dto.ts' +import type { ShopKeyFormValues } from './shop-key-form-model.ts' + +function finiteId(value: unknown): number | null { + return typeof value === 'number' && Number.isFinite(value) && value >= 1 ? Math.floor(value) : null +} + +/** 由已有密钥行预填编辑表单:备注/紫鸟账号/令牌沿用原值。 + * 令牌为敏感字段,仅在内存中供本次编辑使用,不写入页面状态外的地方; + * 编辑保存时后端要求重新提交令牌,故沿用原行令牌(表单层面以掩码展示,见任务 86)。 */ +export function buildShopKeyEditForm(item: ShopKeyItem | null | undefined): ShopKeyFormValues | null { + if (!item || typeof item !== 'object') return null + if (finiteId(item.id) === null) return null + return { + remarkName: typeof item.remarkName === 'string' ? item.remarkName : '', + ziniaoAccountName: typeof item.ziniaoAccountName === 'string' ? item.ziniaoAccountName : '', + ziniaoToken: typeof item.ziniaoToken === 'string' ? item.ziniaoToken : '', + } +} diff --git a/admin-frontend-vue/tests/task-85.test.ts b/admin-frontend-vue/tests/task-85.test.ts new file mode 100644 index 00000000..d46eb681 --- /dev/null +++ b/admin-frontend-vue/tests/task-85.test.ts @@ -0,0 +1,94 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { readSource } from './helpers.ts' +import { buildShopKeyEditForm } from '../src/pages/shop/shop-key-edit-model.ts' +import { toShopKeyCreateRequest, validateShopKeyForm } from '../src/pages/shop/shop-key-form-model.ts' +import type { ShopKeyItem } from '../src/pages/shop/shop-dto.ts' + +function key(id: number, over: Partial = {}): ShopKeyItem { + return { + id, + remarkName: '', + ziniaoAccountName: 'acc', + ziniaoToken: '', + ipWhitelistStatus: 'UNKNOWN', + ...over, + } +} + +test('test_task_085_shop_key_edit_form_normal_primary_path', () => { + // 正常主路径:从已有密钥行预填编辑表单并提交为更新请求体。 + const form = buildShopKeyEditForm(key(9, { remarkName: '主店', ziniaoAccountName: 'blue_agent', ziniaoToken: 'tok-old' })) + assert.ok(form) + assert.equal(form!.remarkName, '主店') + assert.equal(form!.ziniaoAccountName, 'blue_agent') + assert.equal(form!.ziniaoToken, 'tok-old') + const { valid } = validateShopKeyForm(form!) + assert.equal(valid, true) + assert.deepEqual(toShopKeyCreateRequest(form!), { + remarkName: '主店', + ziniaoAccountName: 'blue_agent', + ziniaoToken: 'tok-old', + }) +}) + +test('test_task_085_shop_key_edit_form_normal_variant_input', () => { + // 正常变体:改备注与账号但沿用原令牌;Bearer 前缀令牌也可编辑。 + const form = buildShopKeyEditForm(key(3, { remarkName: '', ziniaoAccountName: 'red', ziniaoToken: 'Bearer tok-b' })) + assert.ok(form) + const request = toShopKeyCreateRequest(form!) + assert.equal(request.ziniaoAccountName, 'red') + assert.equal(request.ziniaoToken, 'tok-b') + assert.equal('remarkName' in request, false, '空白备注编辑后不下发') +}) + +test('test_task_085_shop_key_edit_form_repeated_is_idempotent', () => { + // 正常重复:预填不改原记录、结果稳定。 + const item = key(2, { remarkName: 'A', ziniaoToken: 't' }) + const first = buildShopKeyEditForm(item) + const second = buildShopKeyEditForm(item) + assert.deepEqual(first, second) + assert.equal(item.remarkName, 'A') +}) + +test('test_task_085_shop_key_edit_form_boundary_empty_input', () => { + // 边界空值:最小记录预填完整表单结构(可编辑字段可为空串但不缺键)。 + const form = buildShopKeyEditForm(key(1)) + assert.ok(form) + assert.equal(form!.remarkName, '') + assert.equal(form!.ziniaoToken, '') +}) + +test('test_task_085_shop_key_edit_form_boundary_single_item', () => { + // 边界单元素:仅含 id 的记录仍可进入编辑流(空字段由校验兜底)。 + const form = buildShopKeyEditForm({ id: 5 } as ShopKeyItem) + assert.ok(form) + assert.equal(form!.ziniaoAccountName, '') +}) + +test('test_task_085_shop_key_edit_form_boundary_limit_or_missing_field', () => { + // 边界上限/缺字段:编辑沿用超长令牌不因展示截断而丢失,校验仍放行到 512。 + const longToken = 't'.repeat(512) + const form = buildShopKeyEditForm(key(8, { ziniaoToken: longToken, ziniaoAccountName: 'a' })) + assert.ok(form) + assert.equal(form!.ziniaoToken.length, 512) + const { valid } = validateShopKeyForm(form!) + assert.equal(valid, true) +}) + +test('test_task_085_shop_key_edit_form_invalid_input_rejected', () => { + // 异常输入:空记录/缺 id 无法构建编辑表单。 + assert.equal(buildShopKeyEditForm(null as unknown as ShopKeyItem), null) + assert.equal(buildShopKeyEditForm({ id: Number.NaN } as unknown as ShopKeyItem), null) +}) + +test('test_task_085_shop_key_edit_form_dependency_failure_returns_actionable_message', () => { + // 依赖失败/提交走 adapter:编辑模型纯逻辑,adapter PUT /api/admin/shop-keys/{id}。 + const model = readSource('src/pages/shop/shop-key-edit-model.ts') + assert.equal(/axios|http\.|vue/.test(model), false, '编辑模型保持纯逻辑') + assert.match(model, /SensitiveString|敏感/) + const api = readSource('src/pages/shop/shop-key-api.ts') + assert.match(api, /updateShopKey/) + assert.match(api, /http\.put/) + assert.match(api, /\/api\/admin\/shop-keys/) +})