task-94(店铺中心): 实现店铺凭证明文短时展示
新增 shop-credential-reveal.ts:复用 secret-mask 会话,店铺密码读取后默认 5 秒短时展示,读取失败/离开即清空,明文不写持久化/日志。 TDD: task-94.test.ts 8 用例先 RED 后 GREEN。
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
/** 店铺密码短时揭示会话(任务 94):读取明文后只在内存按短窗口(默认 5 秒)展示,
|
||||
* 读取失败或离开页面即清空;纯逻辑,明文不写持久化/日志。 */
|
||||
import {
|
||||
DEFAULT_REVEAL_MS,
|
||||
createSecretRevealState,
|
||||
expireReveal,
|
||||
revealSecret,
|
||||
type SecretRevealState,
|
||||
} from './secret-mask.ts'
|
||||
|
||||
export const DEFAULT_SHOP_PASSWORD_REVEAL_MS = DEFAULT_REVEAL_MS
|
||||
|
||||
/** 空揭示会话(默认/读取失败/离开页面后)。 */
|
||||
export function clearShopPasswordReveal(): SecretRevealState {
|
||||
return createSecretRevealState()
|
||||
}
|
||||
|
||||
/** 开始揭示某店铺密码明文(记录 id + 明文 + 揭示时刻),返回新会话。 */
|
||||
export function beginShopPasswordReveal(
|
||||
session: SecretRevealState,
|
||||
shopId: number,
|
||||
plain: string,
|
||||
now: number,
|
||||
expiresAt?: number,
|
||||
): SecretRevealState {
|
||||
return revealSecret(session, shopId, plain, now, expiresAt)
|
||||
}
|
||||
|
||||
/** 到达过期点即清空明文。 */
|
||||
export function expireShopPasswordReveal(session: SecretRevealState, now: number): SecretRevealState {
|
||||
return expireReveal(session, now)
|
||||
}
|
||||
|
||||
/** 当前时刻是否存在有效的短时揭示明文。 */
|
||||
export function isShopPasswordRevealActive(session: SecretRevealState, now: number): boolean {
|
||||
const current = expireReveal(session, now)
|
||||
return current.keyId !== null && current.token !== undefined && current.token !== ''
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { readSource } from './helpers.ts'
|
||||
import {
|
||||
DEFAULT_SHOP_PASSWORD_REVEAL_MS,
|
||||
beginShopPasswordReveal,
|
||||
clearShopPasswordReveal,
|
||||
expireShopPasswordReveal,
|
||||
isShopPasswordRevealActive,
|
||||
} from '../src/pages/shop/shop-credential-reveal.ts'
|
||||
|
||||
test('test_task_094_credential_reveal_normal_primary_path', () => {
|
||||
// 正常主路径:主动揭示后短时(默认 5 秒)拿到明文,超时自动清空。
|
||||
const session = beginShopPasswordReveal(clearShopPasswordReveal(), 5, 'plain-pw', 1000)
|
||||
assert.equal(isShopPasswordRevealActive(session, 1000), true)
|
||||
assert.equal(session.token, 'plain-pw')
|
||||
const expired = expireShopPasswordReveal(session, 1000 + DEFAULT_SHOP_PASSWORD_REVEAL_MS + 1)
|
||||
assert.equal(expired.token, undefined, '超时明文被清空')
|
||||
assert.equal(isShopPasswordRevealActive(expired, 7000), false)
|
||||
})
|
||||
|
||||
test('test_task_094_credential_reveal_normal_variant_input', () => {
|
||||
// 正常变体:多个店铺各自揭示互不串扰,切换记录即替换明文。
|
||||
const first = beginShopPasswordReveal(clearShopPasswordReveal(), 1, 'pw1', 0)
|
||||
const second = beginShopPasswordReveal(first, 2, 'pw2', 0)
|
||||
assert.equal(second.keyId, 2)
|
||||
assert.equal(second.token, 'pw2')
|
||||
})
|
||||
|
||||
test('test_task_094_credential_reveal_repeated_is_idempotent', () => {
|
||||
// 正常重复:同一时刻重复揭示结果稳定。
|
||||
const a = beginShopPasswordReveal(clearShopPasswordReveal(), 3, 'pw', 2000)
|
||||
assert.deepEqual(beginShopPasswordReveal(a, 3, 'pw', 2000), a)
|
||||
})
|
||||
|
||||
test('test_task_094_credential_reveal_boundary_empty_input', () => {
|
||||
// 边界空值:清空后无活动揭示、无明文残留。
|
||||
const session = clearShopPasswordReveal()
|
||||
assert.equal(session.keyId, null)
|
||||
assert.equal(isShopPasswordRevealActive(session, 0), false)
|
||||
})
|
||||
|
||||
test('test_task_094_credential_reveal_boundary_single_item', () => {
|
||||
// 边界单元素:单次揭示在窗口内有效,离开(调用清空)立即释放。
|
||||
const session = beginShopPasswordReveal(clearShopPasswordReveal(), 9, 'pw9', 0, 0 + DEFAULT_SHOP_PASSWORD_REVEAL_MS)
|
||||
assert.equal(isShopPasswordRevealActive(session, DEFAULT_SHOP_PASSWORD_REVEAL_MS - 1), true)
|
||||
const gone = clearShopPasswordReveal()
|
||||
assert.equal(isShopPasswordRevealActive(gone, 0), false)
|
||||
})
|
||||
|
||||
test('test_task_094_credential_reveal_boundary_limit_or_missing_field', () => {
|
||||
// 边界上限:跨记录的揭示不会在离开/失败后残留在其它状态里。
|
||||
const session = beginShopPasswordReveal(clearShopPasswordReveal(), 4, 'x'.repeat(600), 0)
|
||||
assert.equal(session.token!.length, 600)
|
||||
assert.equal(clearShopPasswordReveal().token, undefined)
|
||||
})
|
||||
|
||||
test('test_task_094_credential_reveal_invalid_input_rejected', () => {
|
||||
// 异常输入:读取失败路径调用清空后不残留明文。
|
||||
const session = beginShopPasswordReveal(clearShopPasswordReveal(), 7, 'pw7', 0)
|
||||
const afterFailure = clearShopPasswordReveal()
|
||||
assert.equal(isShopPasswordRevealActive(afterFailure, 0), false)
|
||||
assert.equal(afterFailure.token, undefined, '失败后无明文残留')
|
||||
assert.equal(isShopPasswordRevealActive(session, 0), true, '原会话不受影响(每次新会话)')
|
||||
})
|
||||
|
||||
test('test_task_094_credential_reveal_dependency_failure_returns_actionable_message', () => {
|
||||
// 依赖失败/可操作:揭示会话纯逻辑复用短时语义;不写持久化/日志。
|
||||
const mod = readSource('src/pages/shop/shop-credential-reveal.ts')
|
||||
assert.equal(/axios|http\.|vue|console\./.test(mod), false, '揭示会话保持纯逻辑')
|
||||
assert.match(mod, /DEFAULT_SHOP_PASSWORD_REVEAL_MS/)
|
||||
})
|
||||
Reference in New Issue
Block a user