task-85(店铺中心): 实现编辑店铺密钥表单

新增 shop-key-edit-model.ts(由列表行预填备注/紫鸟账号/令牌,令牌仅内存内
沿用) 并在 shop-key-api.ts 增加 updateShopKey(PUT /api/admin/shop-keys/{id})。

TDD: task-85.test.ts 8 用例先 RED 后 GREEN。
This commit is contained in:
2026-09-05 16:39:04 +08:00
parent 72a8f75dc5
commit be6a17d4bd
3 changed files with 128 additions and 0 deletions
@@ -36,3 +36,17 @@ export async function submitCreateShopKey(form: ShopKeyFormValues): Promise<Shop
} }
return item return item
} }
/** 提交更新店铺密钥:编辑沿用原令牌/账号,更新体字段与新增一致;PUT /api/admin/shop-keys/{id}。 */
export async function updateShopKey(id: number, form: ShopKeyFormValues): Promise<ShopKeyItem> {
const { valid, errors } = validateShopKeyForm(form)
if (!valid) {
throw new Error(Object.values(errors)[0] || '店铺密钥表单校验未通过')
}
const { data } = await http.put<unknown>(`${SHOP_KEYS_ENDPOINT}/${id}`, toShopKeyCreateRequest(form))
const item = toShopKeyItem(unwrap<unknown>(data))
if (!item) {
throw new Error('更新店铺密钥响应异常:未返回有效记录')
}
return item
}
@@ -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 : '',
}
}
+94
View File
@@ -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> = {}): 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/)
})