import * as Store from '../../../internal/Store.js' import * as Ttl from '../../../internal/Ttl.js' import * as Mpp from '../Mpp.js' import * as Idempotency from './Idempotency.js' const identity = { apiKeyId: 'key_mpp', idempotencyKey: 'mpp_broadcast_01j3j1k2l3m4n5p6q7r8s9t0u', inputHash: `0x${'11'.repeat(32)}`, } const credential = { challenge: { id: 'ch_01j3j1k2l3m4n5p6q7r8s9t0u', intent: 'charge', method: 'tempo', realm: 'merchant.example', request: { amount: '1', currency: 'USD' }, }, payload: { hash: `0x${'22'.repeat(32)}`, type: 'hash' }, } satisfies Mpp.RelayInput describe('inputHash', () => { test('binds semantic credential content instead of JSON property order', () => { const reordered = { payload: { hash: `0x${'22'.repeat(32)}`, type: 'hash' }, challenge: { method: 'tempo', realm: 'merchant.example', request: { currency: 'USD', amount: '1' }, intent: 'charge', id: 'ch_01j3j1k2l3m4n5p6q7r8s9t0u', }, } satisfies Mpp.RelayInput expect(Idempotency.inputHash(reordered)).toBe(Idempotency.inputHash(credential)) expect( Idempotency.inputHash({ ...credential, challenge: { ...credential.challenge, id: 'ch_01j3j1k2l3m4n5p6q7r8s9t0v' }, }), ).not.toBe(Idempotency.inputHash(credential)) }) }) describe('claim', () => { test('coordinates concurrent broadcasts and replays terminal results', async () => { const state = Store.memory() const claims = await Promise.all([ Idempotency.claim({ identity, state }), Idempotency.claim({ identity, state }), ]) const claimed = claims.find((result) => result.type === 'claimed') const pending = claims.find((result) => result.type === 'pending') expect(claimed?.type).toBe('claimed') expect(pending).toEqual({ type: 'pending' }) if (!claimed || claimed.type !== 'claimed') throw new Error('Expected a claimed challenge.') const response = { receipt: { method: 'tempo', reference: `0x${'22'.repeat(32)}`, timestamp: '2026-07-20T18:30:00.000Z', }, success: true, } satisfies Mpp.BroadcastResponse await Idempotency.complete({ identity, response, state, token: claimed.token }) await expect(Idempotency.claim({ identity, state })).resolves.toEqual({ response, type: 'replay', }) }) test('reclaims a pending request after its confirmation lease expires', async () => { vi.useFakeTimers({ now: new Date('2026-07-22T00:00:00.000Z') }) try { const state = Store.memory() await Idempotency.claim({ identity, state }) vi.advanceTimersByTime(Ttl.seconds(5) + 1) await expect(Idempotency.claim({ identity, state })).resolves.toEqual({ type: 'pending' }) vi.advanceTimersByTime(Ttl.minutes(1) - Ttl.seconds(5)) await expect(Idempotency.claim({ identity, state })).resolves.toMatchObject({ type: 'claimed', }) } finally { vi.useRealTimers() } }) test('retains a successful receipt beyond the MPP request', async () => { vi.useFakeTimers({ now: new Date('2026-07-22T00:00:00.000Z') }) try { const state = Store.memory() const claim = await Idempotency.claim({ identity, state }) if (claim.type !== 'claimed') throw new Error('Expected an idempotency key claim.') await Idempotency.complete({ identity, response: { receipt: { method: 'tempo', reference: `0x${'22'.repeat(32)}`, timestamp: '2026-07-22T00:00:00.000Z', }, success: true, }, state, token: claim.token, }) vi.advanceTimersByTime(Ttl.seconds(5) + 1) await expect(Idempotency.claim({ identity, state })).resolves.toMatchObject({ type: 'replay', }) } finally { vi.useRealTimers() } }) test('rejects a reused key for a different credential', async () => { const state = Store.memory() const claim = await Idempotency.claim({ identity, state }) if (claim.type !== 'claimed') throw new Error('Expected an idempotency key claim.') await Idempotency.complete({ identity, response: { receipt: { method: 'tempo', reference: `0x${'22'.repeat(32)}`, timestamp: '2026-07-22T00:00:00.000Z', }, success: true, }, state, token: claim.token, }) await expect( Idempotency.claim({ identity: { ...identity, inputHash: `0x${'33'.repeat(32)}` }, state, }), ).resolves.toEqual({ type: 'mismatch' }) }) test('does not replay a legacy receipt without an input hash', async () => { const state = Store.memory() const claim = await Idempotency.claim({ identity, state }) if (claim.type !== 'claimed') throw new Error('Expected an idempotency key claim.') const key = (await state.list()).keys[0]?.name if (!key) throw new Error('Expected an idempotency record.') await state.put( key, JSON.stringify({ response: { receipt: { method: 'tempo', reference: `0x${'22'.repeat(32)}`, timestamp: '2026-07-22T00:00:00.000Z', }, success: true, }, type: 'complete', }), ) await expect(Idempotency.claim({ identity, state })).resolves.toEqual({ type: 'mismatch' }) }) test('rejects a different credential while the original request is pending', async () => { const state = Store.memory() const claim = await Idempotency.claim({ identity, state }) if (claim.type !== 'claimed') throw new Error('Expected an idempotency key claim.') await expect( Idempotency.claim({ identity: { ...identity, inputHash: `0x${'33'.repeat(32)}` }, state, }), ).resolves.toEqual({ type: 'mismatch' }) await expect(Idempotency.claim({ identity, state })).resolves.toEqual({ type: 'pending' }) }) }) describe('release', () => { test('permits a retry after a retryable failure', async () => { const state = Store.memory() const result = await Idempotency.claim({ identity, state }) if (result.type !== 'claimed') throw new Error('Expected a claimed challenge.') await Idempotency.release({ identity, state, token: result.token }) await expect(Idempotency.claim({ identity, state })).resolves.toMatchObject({ type: 'claimed' }) }) test("does not release another credential's pending claim", async () => { const state = Store.memory() const claim = await Idempotency.claim({ identity, state }) if (claim.type !== 'claimed') throw new Error('Expected an idempotency key claim.') await Idempotency.release({ identity: { ...identity, inputHash: `0x${'33'.repeat(32)}` }, state, token: claim.token, }) await expect(Idempotency.claim({ identity, state })).resolves.toEqual({ type: 'pending' }) }) })