Files
huangzd1997 7fa9b5ccf3 task-93(店铺中心): 实现店铺凭证字段分离
新增 shop-credential-model.ts:凭证明文仅经凭证端点解码且落 SensitiveString,
与列表行(仅掩码)字段分离;shop-manage-api.ts 增加 fetchShopCredential
(GET /api/admin/shop-manages/{id}/credential?shop_name=…)。

TDD: task-93.test.ts 8 用例先 RED 后 GREEN。
2026-09-05 16:48:26 +08:00

73 lines
3.2 KiB
TypeScript

import test from 'node:test'
import assert from 'node:assert/strict'
import { readSource } from './helpers.ts'
import { toShopCredential } from '../src/pages/shop/shop-credential-model.ts'
import { toShopSummary } from '../src/pages/shop/shop-manage-model.ts'
test('test_task_093_credential_separation_normal_primary_path', () => {
// 正常主路径:凭证明文只能经凭证解码产生,且落在 SensitiveString 字段。
const credential = toShopCredential({
id: 5,
groupId: 3,
groupName: '华东组',
shopName: 'BlueWave',
mallName: 'M',
znUsername: 'robot',
account: 'a@b.c',
password: 'plain-pw',
})
assert.ok(credential)
assert.equal(credential.id, 5)
assert.equal(credential.shopName, 'BlueWave')
assert.equal(credential.account, 'a@b.c')
assert.equal(credential.password, 'plain-pw')
})
test('test_task_093_credential_separation_normal_variant_input', () => {
// 正常变体:凭证端点解码对象结构与列表行独立,不混入列表摘要。
const row = toShopSummary({ id: 5, shopName: 'BlueWave', password: 'plain-pw', passwordMasked: '******' })
assert.equal(row!.account, '')
assert.equal('password' in row!, false, '列表行不带密码明文字段')
})
test('test_task_093_credential_separation_repeated_is_idempotent', () => {
// 正常重复:凭证解码稳定、不改输入。
const payload = { id: 1, shopName: 'A', account: 'x', password: 'p' }
assert.deepEqual(toShopCredential(payload), toShopCredential(payload))
})
test('test_task_093_credential_separation_boundary_empty_input', () => {
// 边界空值:无密码凭证无法解码(避免误把空当明文)。
assert.equal(toShopCredential({ id: 1, shopName: 'A', account: 'x', password: '' }), null)
})
test('test_task_093_credential_separation_boundary_single_item', () => {
// 边界单元素:单店铺凭证解码字段完整。
const credential = toShopCredential({ id: 9, shopName: 'S', account: 'a', password: 'p', mallName: 'M' })
assert.ok(credential)
assert.equal(credential.groupId, null)
assert.equal(credential.mallName, 'M')
})
test('test_task_093_credential_separation_boundary_limit_or_missing_field', () => {
// 边界上限/缺字段:凭证明文不为空时必须校验原样保留,缺 id 视为无效。
assert.equal(toShopCredential('garbage'), null)
assert.equal(toShopCredential({ shopName: 'no id', password: 'p' }), null)
})
test('test_task_093_credential_separation_invalid_input_rejected', () => {
// 异常输入:无 id/无明文凭证解码为 null,明确拒绝进入通用对象。
assert.equal(toShopCredential({ id: 1, shopName: 'A' }), null)
assert.equal(toShopCredential(null), null)
})
test('test_task_093_credential_separation_dependency_failure_returns_actionable_message', () => {
// 依赖失败/取凭证走 adapter:凭证模型纯逻辑;adapter GET /{id}/credential + SensitiveString。
const model = readSource('src/pages/shop/shop-credential-model.ts')
assert.equal(/axios|http\.|vue/.test(model), false, '凭证模型保持纯逻辑')
assert.match(model, /SensitiveString/)
const api = readSource('src/pages/shop/shop-manage-api.ts')
assert.match(api, /fetchShopCredential/)
assert.match(api, /\/credential/)
})