import assert from 'node:assert/strict' import { describe, it } from 'node:test' import { IDEMPOTENCY_INFLIGHT_MS, IDEMPOTENCY_RETENTION_MS, idempotencyStorageKey, normalizeIdempotencyKey, readFreshFingerprint, withSendIdempotency, type IdempotencyStorage, } from './idempotency.ts' function memoryStorage(initial: Record = {}): IdempotencyStorage & { data: Record } { const data = { ...initial } return { data, async get(key) { return data[key] }, async set(key, value) { data[key] = value }, async delete(key) { delete data[key] }, } } describe('normalizeIdempotencyKey', () => { it('trims and accepts non-empty keys', () => { assert.equal(normalizeIdempotencyKey(' batch:1 '), 'batch:1') }) it('treats blank as absent', () => { assert.equal(normalizeIdempotencyKey(''), null) assert.equal(normalizeIdempotencyKey(' '), null) assert.equal(normalizeIdempotencyKey(undefined), null) }) it('rejects oversized keys', () => { assert.throws(() => normalizeIdempotencyKey('x'.repeat(257)), /at most 256/) }) }) describe('idempotencyStorageKey', () => { it('scopes by action, account, and key', () => { assert.equal( idempotencyStorageKey('sendMessage', 'business', 'welcome:42'), 'idempotency:sendMessage:business:welcome:42', ) }) }) describe('readFreshFingerprint', () => { it('returns success within retention', () => { const now = Date.parse('2026-09-11T20:00:00.000Z') const fresh = readFreshFingerprint( { status: 'success', result: { id: 'msg-1' }, completedAt: new Date(now - 60_000).toISOString(), }, now, ) assert.deepEqual(fresh, { kind: 'success', result: { id: 'msg-1' } }) }) it('expires success after retention', () => { const now = Date.parse('2026-09-11T20:00:00.000Z') const stale = readFreshFingerprint( { status: 'success', result: { id: 'msg-1' }, completedAt: new Date(now - IDEMPOTENCY_RETENTION_MS - 1).toISOString(), }, now, ) assert.equal(stale, null) }) it('detects fresh inflight', () => { const now = Date.parse('2026-09-11T20:00:00.000Z') const inflight = readFreshFingerprint( { status: 'inflight', startedAt: new Date(now - IDEMPOTENCY_INFLIGHT_MS + 1_000).toISOString(), }, now, ) assert.deepEqual(inflight, { kind: 'inflight' }) }) }) describe('withSendIdempotency', () => { it('passes through when key omitted', async () => { const storage = memoryStorage() const result = await withSendIdempotency({ action: 'sendMessage', account: 'business', storage, run: async () => ({ id: 'fresh' }), }) assert.deepEqual(result, { id: 'fresh' }) assert.deepEqual(storage.data, {}) }) it('stores and replays successful sends', async () => { const storage = memoryStorage() let calls = 0 const first = await withSendIdempotency({ action: 'sendMessage', account: 'business', idempotencyKey: 'welcome:42', storage, run: async () => { calls += 1 return { id: 'msg-1' } }, }) const second = await withSendIdempotency({ action: 'sendMessage', account: 'business', idempotencyKey: 'welcome:42', storage, run: async () => { calls += 1 return { id: 'msg-2' } }, }) assert.deepEqual(first, { id: 'msg-1' }) assert.deepEqual(second, { id: 'msg-1' }) assert.equal(calls, 1) }) it('treats different keys as different sends', async () => { const storage = memoryStorage() const a = await withSendIdempotency({ action: 'sendMessage', account: 'business', idempotencyKey: 'a', storage, run: async () => ({ id: 'a' }), }) const b = await withSendIdempotency({ action: 'sendMessage', account: 'business', idempotencyKey: 'b', storage, run: async () => ({ id: 'b' }), }) assert.deepEqual(a, { id: 'a' }) assert.deepEqual(b, { id: 'b' }) }) it('clears inflight on failure so retries can send', async () => { const storage = memoryStorage() await assert.rejects( withSendIdempotency({ action: 'sendMessage', account: 'business', idempotencyKey: 'fail-once', storage, run: async () => { throw new Error('boom') }, }), /boom/, ) assert.equal(Object.keys(storage.data).length, 0) const recovered = await withSendIdempotency({ action: 'sendMessage', account: 'business', idempotencyKey: 'fail-once', storage, run: async () => ({ id: 'ok' }), }) assert.deepEqual(recovered, { id: 'ok' }) }) })