diff --git a/packages/cli/package.json b/packages/cli/package.json index 4fe8ef5..2039d9f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -20,7 +20,7 @@ "dev": "tsup --watch", "test": "vitest run --coverage", "test:watch": "vitest", - "lint": "eslint src --max-warnings 0", + "lint": "eslint src test --max-warnings 0", "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json", "clean": "rm -rf dist .turbo" }, diff --git a/packages/cli/test/unit/cli.test.ts b/packages/cli/test/unit/cli.test.ts index f95e179..5bbcbf9 100644 --- a/packages/cli/test/unit/cli.test.ts +++ b/packages/cli/test/unit/cli.test.ts @@ -348,7 +348,7 @@ describe.skipIf(!existsSync(DIST_CLI))( } const { exitCode, stdout } = await run( ['--auth', '/tmp/inflow-test-no-auth.json', 'user', 'get', '--format', 'json'], - { env: cleanEnv as NodeJS.ProcessEnv }, + { env: cleanEnv }, ); expect(exitCode).toBe(1); const payload = JSON.parse(stdout) as { diff --git a/packages/cli/test/unit/commands/auth/index-tty.test.tsx b/packages/cli/test/unit/commands/auth/index-tty.test.tsx index 8018a40..0b5c1e7 100644 --- a/packages/cli/test/unit/commands/auth/index-tty.test.tsx +++ b/packages/cli/test/unit/commands/auth/index-tty.test.tsx @@ -206,7 +206,7 @@ describe('runAuthLogin (tty mode)', () => { }), }; const gen = runAuthLogin( - ctx as never, + ctx, { authResource: authStub(), userResource: userStub(), @@ -248,7 +248,7 @@ describe('runAuthStatus (tty + agent details)', () => { }; const storage = new MemoryStorage(sampleTokens); const gen = runAuthStatus( - ctx as never, + ctx, { authResource: authStub(), userResource: userStub(), @@ -275,7 +275,7 @@ describe('runAuthStatus (tty + agent details)', () => { const storage = new MemoryStorage(sampleTokens); const failingUser = userStub(() => Promise.reject(new Error('network'))); const gen = runAuthStatus( - ctx as never, + ctx, { authResource: authStub({}, storage, failingUser), userResource: failingUser, @@ -359,7 +359,7 @@ describe('runAuthLogin error envelope', () => { await expect( (async () => { for await (const frame of runAuthLogin( - ctx as never, + ctx, { authResource: auth, userResource: userStub(), @@ -394,7 +394,7 @@ describe('runAuthLogin error envelope', () => { await expect( (async () => { for await (const _frame of runAuthLogin( - ctx as never, + ctx, { authResource: auth, userResource: userStub(), diff --git a/packages/cli/test/unit/commands/auth/login-extra.test.tsx b/packages/cli/test/unit/commands/auth/login-extra.test.tsx index 775f9ab..756d256 100644 --- a/packages/cli/test/unit/commands/auth/login-extra.test.tsx +++ b/packages/cli/test/unit/commands/auth/login-extra.test.tsx @@ -56,8 +56,11 @@ describe('Login — extra branches', () => { }); it('surfaces an authentication failure when initiateDeviceAuth rejects with a bare string', async () => { + // Reject with a runtime non-Error to exercise the String(error) fallback path, + // while typing it as Error to satisfy prefer-promise-reject-errors. + const bareStringFail: Error = 'bare-string-fail' as unknown as Error; const auth = authStub({ - initiateDeviceAuth: vi.fn(() => Promise.reject('bare-string-fail')), + initiateDeviceAuth: vi.fn(() => Promise.reject(bareStringFail)), }); const { lastFrame, unmount } = render( undefined} />, @@ -70,8 +73,11 @@ describe('Login — extra branches', () => { }); it('falls back to the failed frame with a string message when polling rejects with a non-Error value', async () => { + // Reject with a runtime non-Error to exercise the String(error) fallback path, + // while typing it as Error to satisfy prefer-promise-reject-errors. + const bootFromServer: Error = 'boom-from-server' as unknown as Error; const auth = authStub({ - pollDeviceAuth: vi.fn(() => Promise.reject('boom-from-server')), + pollDeviceAuth: vi.fn(() => Promise.reject(bootFromServer)), }); const { lastFrame, unmount } = render( undefined} />, diff --git a/packages/cli/test/unit/commands/auth/status-extra.test.tsx b/packages/cli/test/unit/commands/auth/status-extra.test.tsx index 3fbabe8..21e934f 100644 --- a/packages/cli/test/unit/commands/auth/status-extra.test.tsx +++ b/packages/cli/test/unit/commands/auth/status-extra.test.tsx @@ -217,9 +217,12 @@ describe('AuthStatus', () => { it('renders the probe-failed frame with a string fallback when probe rejects with a non-Error', async () => { const storage = new MemoryStorage(tokens); + // Reject with a runtime non-Error to exercise the String(error) fallback path, + // while typing it as Error to satisfy prefer-promise-reject-errors. + const nonErrorBoom: Error = 'boom' as unknown as Error; const auth = makeAuth({ storage, - userImpl: () => Promise.reject('boom'), + userImpl: () => Promise.reject(nonErrorBoom), }); const { lastFrame, unmount } = render( undefined} />); await vi.waitFor(() => { diff --git a/packages/cli/test/unit/commands/balances/index-tty.test.ts b/packages/cli/test/unit/commands/balances/index-tty.test.ts index 4a87d30..cc37401 100644 --- a/packages/cli/test/unit/commands/balances/index-tty.test.ts +++ b/packages/cli/test/unit/commands/balances/index-tty.test.ts @@ -63,7 +63,7 @@ afterEach(() => { describe('runBalancesList (tty mode)', () => { it('returns the resolved balances when the TTY renderer surfaces them', async () => { const fixture: Balance[] = [{ available: '1.0', currency: 'USDC' }]; - renderMock.mockImplementation(async () => fixture); + renderMock.mockImplementation(() => Promise.resolve(fixture)); const ctx = ttyCtx(); const storage = new MemoryStorage(tokens); @@ -92,7 +92,7 @@ describe('runBalancesList (tty mode)', () => { describe('createBalancesCli', () => { it('returns a Cli scoped to balance commands', () => { const storage = new MemoryStorage(tokens); - const cli = createBalancesCli(balanceStub() as IBalanceResource, storage, makeInflow(storage)); + const cli = createBalancesCli(balanceStub(), storage, makeInflow(storage)); const description = (cli as unknown as { description?: string }).description; expect(description).toContain('Balance commands'); }); diff --git a/packages/cli/test/unit/commands/inspect/inspect.test.ts b/packages/cli/test/unit/commands/inspect/inspect.test.ts index 4a510ea..2a1562c 100644 --- a/packages/cli/test/unit/commands/inspect/inspect.test.ts +++ b/packages/cli/test/unit/commands/inspect/inspect.test.ts @@ -62,7 +62,7 @@ function ctx(): InspectCommandContext { error: (o: { code: string; message: string }) => { throw new Error(`${o.code}: ${o.message}`); }, - } as InspectCommandContext; + }; } describe('buildCombinedFrame', () => { diff --git a/packages/cli/test/unit/commands/mpp/cancel-view.test.tsx b/packages/cli/test/unit/commands/mpp/cancel-view.test.tsx index 98f421d..5cf8feb 100644 --- a/packages/cli/test/unit/commands/mpp/cancel-view.test.tsx +++ b/packages/cli/test/unit/commands/mpp/cancel-view.test.tsx @@ -7,7 +7,7 @@ describe('CancelView', () => { it('shows the cancelling spinner then the cancelled confirmation', async () => { const onComplete = vi.fn(); const { lastFrame, unmount } = render( - undefined} onComplete={onComplete} />, + Promise.resolve(undefined)} onComplete={onComplete} />, ); await new Promise((r) => setTimeout(r, 50)); expect(lastFrame() ?? '').toContain('ap-9'); diff --git a/packages/cli/test/unit/commands/mpp/index-runners.test.ts b/packages/cli/test/unit/commands/mpp/index-runners.test.ts index bb355c0..8e69ead 100644 --- a/packages/cli/test/unit/commands/mpp/index-runners.test.ts +++ b/packages/cli/test/unit/commands/mpp/index-runners.test.ts @@ -34,7 +34,7 @@ function makeClient(overrides: Partial = {}): MppClient { function authed( client: MppClient, - cancelApproval = vi.fn(async () => undefined), + cancelApproval = vi.fn(() => Promise.resolve(undefined)), ): { inflow: Inflow; storage: AuthStorage } { const storage = new MemoryStorage({ access_token: 'a', @@ -83,7 +83,7 @@ async function drain(gen: AsyncGenerator): Promise { async function drainWithReturn(gen: AsyncGenerator): Promise<{ values: T[]; returnValue: unknown }> { const values: T[] = []; - while (true) { + for (;;) { const next = await gen.next(); if (next.done) return { values, returnValue: next.value }; values.push(next.value); @@ -99,16 +99,14 @@ describe('mpp agent runners', () => { { method: 'inflow', intents: [{ intent: 'charge', rails: [{ rail: 'balance', currencies: ['USDC'] }] }] }, ], }; - const { inflow, storage } = authed( - makeClient({ getSupported: vi.fn(async () => supported) as MppClient['getSupported'] }), - ); + const { inflow, storage } = authed(makeClient({ getSupported: vi.fn(() => Promise.resolve(supported)) })); const ctx = { agent: true, formatExplicit: true, error: vi.fn() }; const out = await runSupportedCommand(ctx as never, inflow, storage); expect(out).toEqual(supported); }); it('runCancelCommand delegates to cancelApproval', async () => { - const cancelApproval = vi.fn(async () => undefined); + const cancelApproval = vi.fn(() => Promise.resolve(undefined)); const { inflow, storage } = authed(makeClient(), cancelApproval); const ctx = { agent: true, formatExplicit: true, args: { approvalId: 'ap-1' }, error: vi.fn() }; const out = await runCancelCommand(ctx as never, inflow, storage); @@ -118,11 +116,13 @@ describe('mpp agent runners', () => { it('runStatusCommand (interval 0) yields a single ready snapshot', async () => { const client = makeClient({ - getTransaction: vi.fn(async () => ({ - transactionId: 'tx-1', - state: 'ready', - credential: 'CRED', - })) as MppClient['getTransaction'], + getTransaction: vi.fn(() => + Promise.resolve({ + transactionId: 'tx-1', + state: 'ready', + credential: 'CRED', + }), + ) as MppClient['getTransaction'], }); const { inflow, storage } = authed(client); const ctx = agentCtx({ transactionId: 'tx-1' }, { interval: 0, maxAttempts: 0, timeout: 900 }); @@ -147,11 +147,13 @@ describe('mpp agent runners', () => { fetchSpy.mockResolvedValueOnce(challenge402()); fetchSpy.mockResolvedValueOnce(new Response('PAID', { status: 200 })); const client = makeClient({ - createTransaction: vi.fn(async () => ({ - state: 'ready', - credential: 'CRED', - transactionId: 'tx-1', - })) as MppClient['createTransaction'], + createTransaction: vi.fn(() => + Promise.resolve({ + state: 'ready', + credential: 'CRED', + transactionId: 'tx-1', + }), + ) as MppClient['createTransaction'], }); const { inflow, storage } = authed(client); const ctx = agentCtx( @@ -220,11 +222,13 @@ describe('mpp agent runners', () => { fetchSpy.mockResolvedValueOnce(challenge402()); fetchSpy.mockResolvedValueOnce(new Response('nope', { status: 402 })); const client = makeClient({ - createTransaction: vi.fn(async () => ({ - state: 'ready', - credential: 'CRED', - transactionId: 'tx-1', - })) as MppClient['createTransaction'], + createTransaction: vi.fn(() => + Promise.resolve({ + state: 'ready', + credential: 'CRED', + transactionId: 'tx-1', + }), + ) as MppClient['createTransaction'], }); const { inflow, storage } = authed(client); const ctx = agentCtx( @@ -253,17 +257,21 @@ describe('mpp agent runners', () => { it('runPayCommand returns the c.error sentinel when an awaited transaction expires', async () => { vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce(challenge402()); const client = makeClient({ - createTransaction: vi.fn(async () => ({ - state: 'pending', - transactionId: 'tx-expired', - approvalId: 'ap-expired', - approvalUrl: 'https://sandbox.inflowpay.ai/approvals/ap-expired/view/', - retryAfterSeconds: 1, - })) as MppClient['createTransaction'], - getTransaction: vi.fn(async () => ({ - transactionId: 'tx-expired', - state: 'expired', - })) as MppClient['getTransaction'], + createTransaction: vi.fn(() => + Promise.resolve({ + state: 'pending', + transactionId: 'tx-expired', + approvalId: 'ap-expired', + approvalUrl: 'https://sandbox.inflowpay.ai/approvals/ap-expired/view/', + retryAfterSeconds: 1, + }), + ) as MppClient['createTransaction'], + getTransaction: vi.fn(() => + Promise.resolve({ + transactionId: 'tx-expired', + state: 'expired', + }), + ) as MppClient['getTransaction'], }); const { inflow, storage } = authed(client); const ctx = agentCtxReturningError( @@ -277,30 +285,34 @@ describe('mpp agent runners', () => { }); it('runStatusCommand (interval > 0) errors PAYMENT_FAILED on a failed terminal', async () => { - const getTransaction = vi.fn(async () => ({ - transactionId: 'tx-1', - state: 'failed' as const, - problem: { - type: 'https://paymentauth.org/problems/verification-failed', - title: 'fail', - status: 402, - detail: 'declined', - }, - })); - const { inflow, storage } = authed(makeClient({ getTransaction: getTransaction as MppClient['getTransaction'] })); + const getTransaction = vi.fn(() => + Promise.resolve({ + transactionId: 'tx-1', + state: 'failed' as const, + problem: { + type: 'https://paymentauth.org/problems/verification-failed', + title: 'fail', + status: 402, + detail: 'declined', + }, + }), + ); + const { inflow, storage } = authed(makeClient({ getTransaction: getTransaction })); const ctx = agentCtx({ transactionId: 'tx-1' }, { interval: 0.01, maxAttempts: 0, timeout: 900 }); await expect(drain(runStatusCommand(ctx as never, inflow, storage))).rejects.toThrow('c.error: PAYMENT_FAILED'); }); it('runStatusCommand (interval > 0) errors PAYMENT_EXPIRED on an expired terminal', async () => { - const getTransaction = vi.fn(async () => ({ transactionId: 'tx-1', state: 'expired' })); + const getTransaction = vi.fn(() => Promise.resolve({ transactionId: 'tx-1', state: 'expired' })); const { inflow, storage } = authed(makeClient({ getTransaction: getTransaction as MppClient['getTransaction'] })); const ctx = agentCtx({ transactionId: 'tx-1' }, { interval: 0.01, maxAttempts: 0, timeout: 900 }); await expect(drain(runStatusCommand(ctx as never, inflow, storage))).rejects.toThrow('c.error: PAYMENT_EXPIRED'); }); it('runStatusCommand (interval > 0) errors POLLING_TIMEOUT when max attempts are exhausted', async () => { - const getTransaction = vi.fn(async () => ({ transactionId: 'tx-1', state: 'pending', retryAfterSeconds: 0 })); + const getTransaction = vi.fn(() => + Promise.resolve({ transactionId: 'tx-1', state: 'pending', retryAfterSeconds: 0 }), + ); const { inflow, storage } = authed(makeClient({ getTransaction: getTransaction as MppClient['getTransaction'] })); const ctx = agentCtx({ transactionId: 'tx-1' }, { interval: 0.01, maxAttempts: 2, timeout: 900 }); await expect(drain(runStatusCommand(ctx as never, inflow, storage))).rejects.toThrow('c.error: POLLING_TIMEOUT'); diff --git a/packages/cli/test/unit/commands/mpp/index-tty.test.tsx b/packages/cli/test/unit/commands/mpp/index-tty.test.tsx index 1f3de23..0c24bec 100644 --- a/packages/cli/test/unit/commands/mpp/index-tty.test.tsx +++ b/packages/cli/test/unit/commands/mpp/index-tty.test.tsx @@ -43,7 +43,7 @@ function authed(client: MppClient): { inflow: Inflow; storage: AuthStorage } { const inflow = new Inflow({ authStorage: storage, environment: 'sandbox', cliClientId: 'test' }); (inflow.mpp as unknown as { cachedClient: Promise }).cachedClient = Promise.resolve(client); (inflow.mpp as unknown as { cachedMethod: { cancelApproval: () => Promise } }).cachedMethod = { - cancelApproval: vi.fn(async () => undefined), + cancelApproval: vi.fn(() => Promise.resolve(undefined)), }; return { inflow, storage }; } @@ -74,11 +74,13 @@ describe('mpp TTY runners (renderInkUntilExit paths)', () => { fetchSpy.mockResolvedValueOnce(challenge402()); fetchSpy.mockResolvedValueOnce(new Response('PAID', { status: 200 })); const client = makeClient({ - createTransaction: vi.fn(async () => ({ - state: 'ready', - credential: 'CRED', - transactionId: 'tx-1', - })) as MppClient['createTransaction'], + createTransaction: vi.fn(() => + Promise.resolve({ + state: 'ready', + credential: 'CRED', + transactionId: 'tx-1', + }), + ) as MppClient['createTransaction'], }); const { inflow, storage } = authed(client); const ctx = ttyCtx( @@ -94,11 +96,13 @@ describe('mpp TTY runners (renderInkUntilExit paths)', () => { fetchSpy.mockResolvedValueOnce(challenge402()); fetchSpy.mockResolvedValueOnce(new Response('nope', { status: 402 })); const client = makeClient({ - createTransaction: vi.fn(async () => ({ - state: 'ready', - credential: 'CRED', - transactionId: 'tx-1', - })) as MppClient['createTransaction'], + createTransaction: vi.fn(() => + Promise.resolve({ + state: 'ready', + credential: 'CRED', + transactionId: 'tx-1', + }), + ) as MppClient['createTransaction'], }); const { inflow, storage } = authed(client); const ctx = ttyCtx( @@ -124,11 +128,13 @@ describe('mpp TTY runners (renderInkUntilExit paths)', () => { it('runStatusCommand renders the status view to completion', async () => { const client = makeClient({ - getTransaction: vi.fn(async () => ({ - transactionId: 'tx-1', - state: 'ready', - credential: 'CRED', - })) as MppClient['getTransaction'], + getTransaction: vi.fn(() => + Promise.resolve({ + transactionId: 'tx-1', + state: 'ready', + credential: 'CRED', + }), + ) as MppClient['getTransaction'], }); const { inflow, storage } = authed(client); const ctx = ttyCtx({ transactionId: 'tx-1' }, { interval: 0, maxAttempts: 0, timeout: 900 }); @@ -139,28 +145,30 @@ describe('mpp TTY runners (renderInkUntilExit paths)', () => { it('runCancelCommand renders the cancel view and returns the best-effort frame', async () => { const { inflow, storage } = authed(makeClient()); const ctx = ttyCtx({ approvalId: 'ap-1' }, {}); - const out = await runCancelCommand(ctx as never, inflow, storage); + const out = await runCancelCommand(ctx, inflow, storage); expect(out).toMatchObject({ approval_id: 'ap-1', cancelled: true }); }); it('runSupportedCommand renders the supported view and returns undefined', async () => { const client = makeClient({ - getSupported: vi.fn(async () => ({ - kinds: [ - { method: 'inflow', intents: [{ intent: 'charge', rails: [{ rail: 'balance', currencies: ['USDC'] }] }] }, - ], - })) as MppClient['getSupported'], + getSupported: vi.fn(() => + Promise.resolve({ + kinds: [ + { method: 'inflow', intents: [{ intent: 'charge', rails: [{ rail: 'balance', currencies: ['USDC'] }] }] }, + ], + }), + ), }); const { inflow, storage } = authed(client); const ctx = ttyCtx({}, {}); - const out = await runSupportedCommand(ctx as never, inflow, storage); + const out = await runSupportedCommand(ctx, inflow, storage); expect(out).toBeUndefined(); }); it('runInspectCommand renders the inspect view and returns undefined on a 2xx probe', async () => { vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce(new Response('FREE', { status: 200 })); const ctx = ttyCtx({ url: SELLER }, { method: 'GET', header: [] }); - const out = await runInspectCommand(ctx as never); + const out = await runInspectCommand(ctx); expect(out).toBeUndefined(); expect(ctx.error).not.toHaveBeenCalled(); }); diff --git a/packages/cli/test/unit/commands/mpp/index.test.ts b/packages/cli/test/unit/commands/mpp/index.test.ts index 25dcd7d..84b61a4 100644 --- a/packages/cli/test/unit/commands/mpp/index.test.ts +++ b/packages/cli/test/unit/commands/mpp/index.test.ts @@ -3,7 +3,7 @@ import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { encode, type MppChallenge, type MppTransactionResponse, renderChallengeHeader } from '@inflowpayai/mpp'; import type { MppPayCreated, MppPayResultRejected, MppPayResultSuccess } from '@inflowpayai/inflow-core'; -import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { describe, expect, it } from 'vitest'; import { __testing } from '../../../../src/commands/mpp/index.js'; const { diff --git a/packages/cli/test/unit/commands/mpp/supported-view.test.tsx b/packages/cli/test/unit/commands/mpp/supported-view.test.tsx index 9c29c94..d111174 100644 --- a/packages/cli/test/unit/commands/mpp/supported-view.test.tsx +++ b/packages/cli/test/unit/commands/mpp/supported-view.test.tsx @@ -25,7 +25,9 @@ function supported(): MppSupportedResponse { describe('SupportedView', () => { it('renders a method/intent/rail/currencies table', async () => { - const { lastFrame, unmount } = render( supported()} onComplete={vi.fn()} />); + const { lastFrame, unmount } = render( + Promise.resolve(supported())} onComplete={vi.fn()} />, + ); await new Promise((r) => setTimeout(r, 50)); const frame = lastFrame() ?? ''; expect(frame).toContain('Method'); @@ -41,7 +43,9 @@ describe('SupportedView', () => { const response: MppSupportedResponse = { kinds: [{ method: 'inflow', intents: [{ intent: 'charge', rails: [{ rail: 'balance', currencies: [] }] }] }], }; - const { lastFrame, unmount } = render( response} onComplete={vi.fn()} />); + const { lastFrame, unmount } = render( + Promise.resolve(response)} onComplete={vi.fn()} />, + ); await new Promise((r) => setTimeout(r, 50)); const frame = lastFrame() ?? ''; expect(frame).toContain('balance'); @@ -50,7 +54,9 @@ describe('SupportedView', () => { }); it('renders an empty-state message when there are no kinds', async () => { - const { lastFrame, unmount } = render( ({ kinds: [] })} onComplete={vi.fn()} />); + const { lastFrame, unmount } = render( + Promise.resolve({ kinds: [] })} onComplete={vi.fn()} />, + ); await new Promise((r) => setTimeout(r, 50)); expect(lastFrame() ?? '').toContain('No supported MPP methods'); unmount(); @@ -58,12 +64,7 @@ describe('SupportedView', () => { it('shows the error message when load rejects', async () => { const { lastFrame, unmount } = render( - { - throw new Error('network down'); - }} - onComplete={vi.fn()} - />, + Promise.reject(new Error('network down'))} onComplete={vi.fn()} />, ); await new Promise((r) => setTimeout(r, 50)); expect(lastFrame() ?? '').toContain('network down'); diff --git a/packages/cli/test/unit/commands/user/index-tty.test.ts b/packages/cli/test/unit/commands/user/index-tty.test.ts index 65948d6..92e9e20 100644 --- a/packages/cli/test/unit/commands/user/index-tty.test.ts +++ b/packages/cli/test/unit/commands/user/index-tty.test.ts @@ -85,12 +85,14 @@ afterEach(() => { describe('runUserGet (tty mode)', () => { it('returns the user when renderInkUntilExit produces a success outcome', async () => { - renderMock.mockImplementation(async (_element, resolveResult) => { + renderMock.mockImplementation((_element, resolveResult) => { const resolver = resolveResult as (() => unknown) | undefined; - return resolver ? (resolver() ?? { kind: 'success', user: sampleUser }) : { kind: 'success', user: sampleUser }; + return Promise.resolve( + resolver ? (resolver() ?? { kind: 'success', user: sampleUser }) : { kind: 'success', user: sampleUser }, + ); }); - renderMock.mockImplementation(async (_element) => { - return { kind: 'success', user: sampleUser } as const; + renderMock.mockImplementation((_element) => { + return Promise.resolve({ kind: 'success', user: sampleUser } as const); }); const ctx = ttyCtx(); @@ -108,7 +110,7 @@ describe('runUserGet (tty mode)', () => { }); it('throws an IncurError when the TTY frame produced no result (null outcome)', async () => { - renderMock.mockImplementation(async () => null); + renderMock.mockImplementation(() => Promise.resolve(null)); const ctx = ttyCtx(); const storage = new MemoryStorage(tokens); @@ -122,10 +124,12 @@ describe('runUserGet (tty mode)', () => { }); it('throws an IncurError surfacing the error message when the TTY frame produced an error outcome', async () => { - renderMock.mockImplementation(async () => ({ - kind: 'error', - message: 'boom', - })); + renderMock.mockImplementation(() => + Promise.resolve({ + kind: 'error', + message: 'boom', + }), + ); const ctx = ttyCtx(); const storage = new MemoryStorage(tokens); await expect( diff --git a/packages/cli/test/unit/commands/x402/cancel.test.tsx b/packages/cli/test/unit/commands/x402/cancel.test.tsx index 898bb6c..7953273 100644 --- a/packages/cli/test/unit/commands/x402/cancel.test.tsx +++ b/packages/cli/test/unit/commands/x402/cancel.test.tsx @@ -19,7 +19,7 @@ describe('CancelView', () => { it('still surfaces the confirmation when the underlying cancel rejects (fire-and-forget)', async () => { const onComplete = vi.fn(); const cancel = vi.fn(() => Promise.reject(new Error('server 500'))); - const { lastFrame, unmount } = render(); + const { unmount } = render(); await new Promise((resolve) => setTimeout(resolve, 50)); expect(cancel).toHaveBeenCalledTimes(1); unmount(); diff --git a/packages/cli/test/unit/commands/x402/index-runners.test.ts b/packages/cli/test/unit/commands/x402/index-runners.test.ts index c99954f..1758107 100644 --- a/packages/cli/test/unit/commands/x402/index-runners.test.ts +++ b/packages/cli/test/unit/commands/x402/index-runners.test.ts @@ -64,21 +64,25 @@ function makePrepared( function makeClient(overrides: Partial = {}): X402InflowClient { const base = { - selectInflowRequirement: vi.fn(async () => ({ - scheme: 'balance', - network: 'inflow:1', - amount: '500', - payTo: 'inflow:abc', - maxTimeoutSeconds: 60, - asset: 'USDC', - extra: {}, - })), - prepareInflowPayment: vi.fn(async () => makePrepared()), - getSupported: vi.fn(async () => ({ - kinds: [{ scheme: 'balance', network: 'inflow:1', x402Version: 2 }], - })), - getX402Payload: vi.fn(async () => ({ status: 'INITIATED' as const })), - cancelApproval: vi.fn(async () => undefined), + selectInflowRequirement: vi.fn(() => + Promise.resolve({ + scheme: 'balance', + network: 'inflow:1', + amount: '500', + payTo: 'inflow:abc', + maxTimeoutSeconds: 60, + asset: 'USDC', + extra: {}, + }), + ), + prepareInflowPayment: vi.fn(() => Promise.resolve(makePrepared())), + getSupported: vi.fn(() => + Promise.resolve({ + kinds: [{ scheme: 'balance', network: 'inflow:1', x402Version: 2 }], + }), + ), + getX402Payload: vi.fn(() => Promise.resolve({ status: 'INITIATED' as const })), + cancelApproval: vi.fn(() => Promise.resolve(undefined)), }; return { ...base, ...overrides } as unknown as X402InflowClient; } @@ -175,7 +179,7 @@ async function drain(gen: AsyncGenerator): Promise { async function drainWithReturn(gen: AsyncGenerator): Promise<{ values: T[]; returnValue: unknown }> { const values: T[] = []; - while (true) { + for (;;) { const next = await gen.next(); if (next.done) return { values, returnValue: next.value }; values.push(next.value); @@ -305,9 +309,7 @@ describe('runPayCommand (agent mode)', () => { }), ); const client = makeClient({ - prepareInflowPayment: vi.fn(async () => { - throw new X402PaymentIdFormatError('bad-id'); - }), + prepareInflowPayment: vi.fn(() => Promise.reject(new X402PaymentIdFormatError('bad-id'))), }); const ctx = agentContext( { url: 'https://seller/api' }, @@ -333,15 +335,17 @@ describe('runPayCommand (agent mode)', () => { headers: { 'PAYMENT-REQUIRED': header }, }), ); - const selectSpy = vi.fn(async () => ({ - scheme: 'balance' as const, - network: 'inflow:1', - amount: '500', - payTo: 'inflow:abc', - maxTimeoutSeconds: 60, - asset: 'USDC', - extra: {}, - })); + const selectSpy = vi.fn(() => + Promise.resolve({ + scheme: 'balance' as const, + network: 'inflow:1', + amount: '500', + payTo: 'inflow:abc', + maxTimeoutSeconds: 60, + asset: 'USDC', + extra: {}, + }), + ); const client = makeClient({ selectInflowRequirement: selectSpy }); const ctx = agentContext( { url: 'https://seller/api' }, @@ -393,15 +397,17 @@ describe('runPayCommand (agent mode)', () => { headers: { 'PAYMENT-REQUIRED': header }, }), ); - const selectSpy = vi.fn(async (_decoded: PaymentRequired) => ({ - scheme: 'exact' as const, - network: 'eip155:84532', - amount: '500', - payTo: '0xabc', - maxTimeoutSeconds: 60, - asset: 'USDC', - extra: {}, - })); + const selectSpy = vi.fn((_decoded: PaymentRequired) => + Promise.resolve({ + scheme: 'exact' as const, + network: 'eip155:84532', + amount: '500', + payTo: '0xabc', + maxTimeoutSeconds: 60, + asset: 'USDC', + extra: {}, + }), + ); const client = makeClient({ selectInflowRequirement: selectSpy }); const ctx = agentContext( { url: 'https://seller/api' }, @@ -437,7 +443,7 @@ describe('runPayCommand (agent mode)', () => { }), ); const client = makeClient({ - selectInflowRequirement: vi.fn(async () => null), + selectInflowRequirement: vi.fn(() => Promise.resolve(null)), }); const ctx = agentContext( { url: 'https://seller/api' }, @@ -464,9 +470,7 @@ describe('runPayCommand (agent mode)', () => { }), ); const client = makeClient({ - prepareInflowPayment: vi.fn(async () => { - throw new X402AdapterRoutingError('balance', 'inflow:1'); - }), + prepareInflowPayment: vi.fn(() => Promise.reject(new X402AdapterRoutingError('balance', 'inflow:1'))), }); const ctx = agentContext( { url: 'https://seller/api' }, @@ -493,7 +497,9 @@ describe('runPayCommand (agent mode)', () => { }), ); const client = makeClient({ - prepareInflowPayment: vi.fn(async () => makePrepared(new X402ApprovalFailedError('appr_1', 'DECLINED'))), + prepareInflowPayment: vi.fn(() => + Promise.resolve(makePrepared(new X402ApprovalFailedError('appr_1', 'DECLINED'))), + ), }); const ctx = agentContext( { url: 'https://seller/api' }, @@ -607,7 +613,7 @@ describe('runPayCommand (agent mode)', () => { const { inflow, storage } = authedResources(makeClient()); await drain(runPayCommand(ctx, inflow, storage, 'https://api.inflowpay.ai')); const [, init] = fetchSpy.mock.calls[0] ?? []; - expect((init as RequestInit | undefined)?.body).toBe('{"x":1}'); + expect(init?.body).toBe('{"x":1}'); }); it('yields replay-rejected frame and emits PAYMENT_NOT_ACCEPTED when the seller returns 402 on replay', async () => { @@ -678,7 +684,7 @@ describe('runPayCommand (agent mode)', () => { describe('runStatusCommand (agent mode)', () => { it('yields a single status frame when interval=0 (snapshot mode)', async () => { const client = makeClient({ - getX402Payload: vi.fn(async () => ({ status: 'INITIATED' })), + getX402Payload: vi.fn(() => Promise.resolve({ status: 'INITIATED' })), }); const ctx = agentContext( { transactionId: 'txn_1' }, @@ -696,7 +702,7 @@ describe('runStatusCommand (agent mode)', () => { it('emits POLLING_TIMEOUT when polling exhausts max_attempts', async () => { const client = makeClient({ - getX402Payload: vi.fn(async () => ({ status: 'INITIATED' })), + getX402Payload: vi.fn(() => Promise.resolve({ status: 'INITIATED' })), }); const ctx = agentContext( { transactionId: 'txn_x' }, @@ -713,7 +719,7 @@ describe('runStatusCommand (agent mode)', () => { it('emits APPROVAL_FAILED when the final status is a terminal failure with no payload', async () => { const client = makeClient({ - getX402Payload: vi.fn(async () => ({ status: 'DECLINED' })), + getX402Payload: vi.fn(() => Promise.resolve({ status: 'DECLINED' })), }); const ctx = agentContext( { transactionId: 'txn_x' }, @@ -730,7 +736,7 @@ describe('runStatusCommand (agent mode)', () => { it('returns the c.error sentinel when the final status is a terminal failure with no payload', async () => { const client = makeClient({ - getX402Payload: vi.fn(async () => ({ status: 'DECLINED' })), + getX402Payload: vi.fn(() => Promise.resolve({ status: 'DECLINED' })), }); const ctx = agentContextReturningError( { transactionId: 'txn_x' }, @@ -769,7 +775,7 @@ describe('runStatusCommand (agent mode)', () => { }, ]; const client = makeClient({ - getX402Payload: vi.fn(async () => responses.shift() ?? { status: 'INITIATED' }), + getX402Payload: vi.fn(() => Promise.resolve(responses.shift() ?? { status: 'INITIATED' })), }); const ctx = agentContext( { transactionId: 'txn_x' }, @@ -788,7 +794,7 @@ describe('runStatusCommand (agent mode)', () => { describe('runCancelCommand', () => { it('returns the best-effort cancel envelope in agent mode', async () => { - const cancelApproval = vi.fn(async () => undefined); + const cancelApproval = vi.fn(() => Promise.resolve(undefined)); const client = makeClient({ cancelApproval }); const ctx = agentContextNoOptions({ approvalId: 'appr_1' }); const { inflow, storage } = authedResources(client); @@ -796,7 +802,7 @@ describe('runCancelCommand', () => { expect(result).toEqual({ approval_id: 'appr_1', cancelled: true, - note: expect.any(String), + note: expect.any(String) as string, }); expect(cancelApproval).toHaveBeenCalledWith('appr_1'); }); @@ -835,7 +841,7 @@ describe('runSupportedCommand', () => { const ctx = agentContextNoOptions({}); const { inflow, storage } = authedResources(makeClient()); const result = await runSupportedCommand(ctx, inflow, storage); - expect(result?.kinds?.[0]?.scheme).toBe('balance'); + expect(result?.kinds[0]?.scheme).toBe('balance'); }); it('short-circuits via c.error when not authenticated', async () => { diff --git a/packages/cli/test/unit/commands/x402/index.test.ts b/packages/cli/test/unit/commands/x402/index.test.ts index c9c59cf..b729c75 100644 --- a/packages/cli/test/unit/commands/x402/index.test.ts +++ b/packages/cli/test/unit/commands/x402/index.test.ts @@ -31,10 +31,10 @@ function preparedEvent(overrides: Record = {}): Extract Promise.reject(new Error('not called in this test')), status: () => Promise.resolve('INITIATED' as const), cancel: () => Promise.resolve(), - } as Extract['prepared'], + }, approvalUrl: 'https://app.inflowpay.ai/approvals/appr_1/view/', }; - return { ...base, ...overrides } as Extract; + return { ...base, ...overrides }; } describe('initialPayFrame', () => { diff --git a/packages/cli/test/unit/commands/x402/pay-view.test.tsx b/packages/cli/test/unit/commands/x402/pay-view.test.tsx index 269e8e0..8abca23 100644 --- a/packages/cli/test/unit/commands/x402/pay-view.test.tsx +++ b/packages/cli/test/unit/commands/x402/pay-view.test.tsx @@ -59,11 +59,11 @@ function prepared( function makeClient(overrides: Partial = {}): X402InflowClient { const base = { - selectInflowRequirement: vi.fn(async () => requirement()), - prepareInflowPayment: vi.fn(async () => prepared()), - getSupported: vi.fn(async () => ({ kinds: [] })), - getX402Payload: vi.fn(async () => ({ status: 'INITIATED' as const })), - cancelApproval: vi.fn(async () => undefined), + selectInflowRequirement: vi.fn(() => Promise.resolve(requirement())), + prepareInflowPayment: vi.fn(() => Promise.resolve(prepared())), + getSupported: vi.fn(() => Promise.resolve({ kinds: [] })), + getX402Payload: vi.fn(() => Promise.resolve({ status: 'INITIATED' as const })), + cancelApproval: vi.fn(() => Promise.resolve(undefined)), }; return { ...base, ...overrides } as unknown as X402InflowClient; } @@ -149,7 +149,7 @@ describe('PayView', () => { }), ); const client = makeClient({ - selectInflowRequirement: vi.fn(async () => null), + selectInflowRequirement: vi.fn(() => Promise.resolve(null)), }); const { lastFrame, unmount } = render( = {}): X402InflowClient { const base = { - selectInflowRequirement: vi.fn(async () => makeRequirement()), - prepareInflowPayment: vi.fn(async () => makePrepared()), - getSupported: vi.fn(async () => ({ kinds: [{ scheme: 'balance', network: 'inflow:1', x402Version: 2 }] })), - getX402Payload: vi.fn(async () => ({ status: 'INITIATED' })), - cancelApproval: vi.fn(async () => undefined), + selectInflowRequirement: vi.fn(() => Promise.resolve(makeRequirement())), + prepareInflowPayment: vi.fn(() => Promise.resolve(makePrepared())), + getSupported: vi.fn(() => Promise.resolve({ kinds: [{ scheme: 'balance', network: 'inflow:1', x402Version: 2 }] })), + getX402Payload: vi.fn(() => Promise.resolve({ status: 'INITIATED' })), + cancelApproval: vi.fn(() => Promise.resolve(undefined)), }; return { ...base, ...overrides } as unknown as X402InflowClient; } @@ -223,8 +223,8 @@ describe('runPayPipeline', () => { }), ); const client = makeClient({ - selectInflowRequirement: vi.fn(async () => null), - } as Partial); + selectInflowRequirement: vi.fn(() => Promise.resolve(null)), + }); const { emit, events } = captureEvents(); await runPayPipeline( { @@ -288,8 +288,10 @@ describe('runPayPipeline', () => { }), ); const client = makeClient({ - prepareInflowPayment: vi.fn(async () => makePrepared(new X402ApprovalFailedError('appr_1', 'DECLINED'))), - } as Partial); + prepareInflowPayment: vi.fn(() => + Promise.resolve(makePrepared(new X402ApprovalFailedError('appr_1', 'DECLINED'))), + ), + }); const { emit, events } = captureEvents(); await runPayPipeline( { @@ -650,10 +652,10 @@ describe('runPayPipeline with --scheme / --network / --asset / --asset-name filt }), ); fetchSpy.mockResolvedValueOnce(new Response('paid-body', { status: 200 })); - const selectSpy = vi.fn(async (_decoded: PaymentRequired) => makeRequirement()); + const selectSpy = vi.fn((_decoded: PaymentRequired) => Promise.resolve(makeRequirement())); const client = makeClient({ selectInflowRequirement: selectSpy, - } as Partial); + }); const { emit, events } = captureEvents(); await runPayPipeline( { @@ -685,10 +687,10 @@ describe('runPayPipeline with --scheme / --network / --asset / --asset-name filt headers: { 'PAYMENT-REQUIRED': header }, }), ); - const selectSpy = vi.fn(async (_decoded: PaymentRequired) => makeRequirement()); + const selectSpy = vi.fn((_decoded: PaymentRequired) => Promise.resolve(makeRequirement())); const client = makeClient({ selectInflowRequirement: selectSpy, - } as Partial); + }); const { emit, events } = captureEvents(); await runPayPipeline( { @@ -748,10 +750,10 @@ describe('runPayPipeline with --scheme / --network / --asset / --asset-name filt headers: { 'PAYMENT-REQUIRED': header }, }), ); - const selectSpy = vi.fn(async (_decoded: PaymentRequired) => makeRequirement()); + const selectSpy = vi.fn((_decoded: PaymentRequired) => Promise.resolve(makeRequirement())); const client = makeClient({ selectInflowRequirement: selectSpy, - } as Partial); + }); const { emit, events } = captureEvents(); await runPayPipeline( { @@ -786,10 +788,10 @@ describe('runPayPipeline with --scheme / --network / --asset / --asset-name filt headers: { 'PAYMENT-REQUIRED': header }, }), ); - const selectSpy = vi.fn(async (_decoded: PaymentRequired) => makeRequirement()); + const selectSpy = vi.fn((_decoded: PaymentRequired) => Promise.resolve(makeRequirement())); const client = makeClient({ selectInflowRequirement: selectSpy, - } as Partial); + }); const { emit, events } = captureEvents(); await runPayPipeline( { @@ -820,8 +822,8 @@ describe('runPayPipeline with --scheme / --network / --asset / --asset-name filt }), ); const client = makeClient({ - selectInflowRequirement: vi.fn(async () => null), - } as Partial); + selectInflowRequirement: vi.fn(() => Promise.resolve(null)), + }); const { emit, events } = captureEvents(); await runPayPipeline( { diff --git a/packages/cli/test/unit/commands/x402/status-view.test.tsx b/packages/cli/test/unit/commands/x402/status-view.test.tsx index 5b1683d..77f4555 100644 --- a/packages/cli/test/unit/commands/x402/status-view.test.tsx +++ b/packages/cli/test/unit/commands/x402/status-view.test.tsx @@ -139,7 +139,10 @@ describe('X402StatusView', () => { }); it('renders the error frame with a string fallback when fetchOnce throws a non-Error', async () => { - const fetchOnce = vi.fn(() => Promise.reject('bare-string-failure')); + // Reject with a runtime non-Error to exercise the String(error) fallback path, + // while typing it as Error to satisfy prefer-promise-reject-errors. + const bareStringFailure: Error = 'bare-string-failure' as unknown as Error; + const fetchOnce = vi.fn(() => Promise.reject(bareStringFailure)); const { lastFrame, unmount } = render( { const onComplete = vi.fn(); const { lastFrame, unmount } = render( - makeResponse([ - { scheme: 'balance', network: 'inflow:1', x402Version: 2 }, - { scheme: 'exact', network: 'eip155:8453', x402Version: 2 }, - ]) + load={() => + Promise.resolve( + makeResponse([ + { scheme: 'balance', network: 'inflow:1', x402Version: 2 }, + { scheme: 'exact', network: 'eip155:8453', x402Version: 2 }, + ]), + ) } onComplete={onComplete} />, @@ -38,7 +40,7 @@ describe('SupportedView', () => { it('renders an empty-state message when kinds is []', async () => { const onComplete = vi.fn(); const { lastFrame, unmount } = render( - makeResponse([])} onComplete={onComplete} />, + Promise.resolve(makeResponse([]))} onComplete={onComplete} />, ); await new Promise((resolve) => setTimeout(resolve, 50)); expect(lastFrame() ?? '').toContain('No supported'); @@ -48,12 +50,7 @@ describe('SupportedView', () => { it('shows the error message when load rejects', async () => { const onComplete = vi.fn(); const { lastFrame, unmount } = render( - { - throw new Error('server unavailable'); - }} - onComplete={onComplete} - />, + Promise.reject(new Error('server unavailable'))} onComplete={onComplete} />, ); await new Promise((resolve) => setTimeout(resolve, 50)); expect(lastFrame() ?? '').toContain('server unavailable'); diff --git a/packages/cli/test/unit/hooks/use-flow-state.test.tsx b/packages/cli/test/unit/hooks/use-flow-state.test.tsx index 90ad1b9..32cbb67 100644 --- a/packages/cli/test/unit/hooks/use-flow-state.test.tsx +++ b/packages/cli/test/unit/hooks/use-flow-state.test.tsx @@ -14,12 +14,6 @@ function Probe({ action, onComplete }: ProbeProps): React.ReactElement { return {`status=${status};data=${String(data)};error=${error}`}; } -async function flushMicrotasks(rounds = 8): Promise { - for (let i = 0; i < rounds; i++) { - await Promise.resolve(); - } -} - describe('useFlowState', () => { it('transitions loading -> success and calls onComplete with the result', async () => { const onComplete = vi.fn(); diff --git a/packages/core/package.json b/packages/core/package.json index a0b93b8..abecdbb 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -27,7 +27,7 @@ "dev": "tsup --watch", "test": "vitest run --coverage", "test:watch": "vitest", - "lint": "eslint src --max-warnings 0", + "lint": "eslint src test --max-warnings 0", "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json && tsc --noEmit -p tsconfig.examples.json", "clean": "rm -rf dist .turbo" }, diff --git a/packages/core/test/unit/auth/session-presence.test.ts b/packages/core/test/unit/auth/session-presence.test.ts index a798dae..1afea65 100644 --- a/packages/core/test/unit/auth/session-presence.test.ts +++ b/packages/core/test/unit/auth/session-presence.test.ts @@ -26,7 +26,7 @@ describe('hasSession', () => { it('short-circuits on api key: never calls storage.isAuthenticated when hasApiKey() is true', () => { let storageCalls = 0; const storage = new MemoryStorage(); - const wrapped: typeof storage = Object.create(storage); + const wrapped = Object.create(storage) as typeof storage; wrapped.isAuthenticated = () => { storageCalls++; return false; diff --git a/packages/core/test/unit/client.test.ts b/packages/core/test/unit/client.test.ts index 7e9260a..fe4372c 100644 --- a/packages/core/test/unit/client.test.ts +++ b/packages/core/test/unit/client.test.ts @@ -1,3 +1,4 @@ +import type * as X402BuyerModule from '@inflowpayai/x402-buyer'; import { http, HttpResponse } from 'msw'; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'; import { Inflow, MemoryStorage } from '../../src/index.js'; @@ -5,7 +6,7 @@ import { BASE_URL, balancesHappy, userHappy } from './fixtures/handlers.js'; import { makeServer } from './fixtures/server.js'; vi.mock('@inflowpayai/x402-buyer', async (importOriginal) => { - const actual = await importOriginal(); + const actual = await importOriginal(); const calls: Array> = []; return { ...actual, @@ -172,7 +173,7 @@ describe('Inflow auto-wiring of device-token provider', () => { }); server.use( - http.post(`${BASE_URL}/v1/oauth2/token`, async () => + http.post(`${BASE_URL}/v1/oauth2/token`, () => HttpResponse.json({ access_token: 'fresh', refresh_token: 'r1', diff --git a/packages/core/test/unit/flows/auth-login.test.ts b/packages/core/test/unit/flows/auth-login.test.ts index fa79efe..c5ba7c1 100644 --- a/packages/core/test/unit/flows/auth-login.test.ts +++ b/packages/core/test/unit/flows/auth-login.test.ts @@ -138,7 +138,8 @@ describe('runAuthLogin', () => { it('best-effort revokes the prior refresh token after the new tokens land', async () => { const storage = new MemoryStorage(); - const auth = makeAuth(); + const revokeToken = vi.fn().mockResolvedValue(undefined); + const auth = makeAuth({ revokeToken }); const run = runAuthLogin({ authResource: auth, authStorage: storage, @@ -150,7 +151,7 @@ describe('runAuthLogin', () => { }); await drain(run.events); await vi.waitFor(() => { - expect(auth.revokeToken).toHaveBeenCalledWith('old-refresh'); + expect(revokeToken).toHaveBeenCalledWith('old-refresh'); }); }); diff --git a/packages/core/test/unit/flows/auth-logout.test.ts b/packages/core/test/unit/flows/auth-logout.test.ts index 19b628f..a4819dc 100644 --- a/packages/core/test/unit/flows/auth-logout.test.ts +++ b/packages/core/test/unit/flows/auth-logout.test.ts @@ -26,11 +26,12 @@ describe('runAuthLogout', () => { const storage = new MemoryStorage(sampleTokens); storage.setApiKey('inflow_old_key'); storage.setConnection({ environment: 'sandbox' }); - const auth = makeAuth(); + const revokeToken = vi.fn().mockResolvedValue(undefined); + const auth = makeAuth({ revokeToken }); await runAuthLogout({ authResource: auth, authStorage: storage }); - expect(auth.revokeToken).toHaveBeenCalledWith('r'); + expect(revokeToken).toHaveBeenCalledWith('r'); expect(storage.getAuth()).toBeNull(); expect(storage.getApiKey()).toBeNull(); expect(storage.getConnection()).toBeNull(); @@ -49,11 +50,12 @@ describe('runAuthLogout', () => { it('skips revokeToken when no refresh_token is present in storage', async () => { const storage = new MemoryStorage(); storage.setApiKey('inflow_api_key_only'); - const auth = makeAuth(); + const revokeToken = vi.fn().mockResolvedValue(undefined); + const auth = makeAuth({ revokeToken }); await runAuthLogout({ authResource: auth, authStorage: storage }); - expect(auth.revokeToken).not.toHaveBeenCalled(); + expect(revokeToken).not.toHaveBeenCalled(); expect(storage.getApiKey()).toBeNull(); }); }); diff --git a/packages/core/test/unit/flows/inflow-flows.test.ts b/packages/core/test/unit/flows/inflow-flows.test.ts index 357d637..41eb302 100644 --- a/packages/core/test/unit/flows/inflow-flows.test.ts +++ b/packages/core/test/unit/flows/inflow-flows.test.ts @@ -1,4 +1,5 @@ import type { MppClient } from '@inflowpayai/mpp'; +import type * as X402BuyerModule from '@inflowpayai/x402-buyer'; import type { InflowClient as X402BuyerClient } from '@inflowpayai/x402-buyer'; import { http, HttpResponse } from 'msw'; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'; @@ -8,16 +9,18 @@ import { BASE_URL, balancesHappy, depositAddressesHappy, userHappy } from '../fi import { makeServer } from '../fixtures/server.js'; vi.mock('@inflowpayai/x402-buyer', async (importOriginal) => { - const actual = await importOriginal(); + const actual = await importOriginal(); return { ...actual, - createInflowClient: vi.fn(async () => ({ - getSupported: vi.fn(async () => ({ kinds: [] })), - selectInflowRequirement: () => null, - getX402Payload: vi.fn(async () => ({ status: 'INITIATED' as const })), - cancelApproval: vi.fn(async () => undefined), - prepareInflowPayment: vi.fn(), - })), + createInflowClient: vi.fn(() => + Promise.resolve({ + getSupported: vi.fn(() => Promise.resolve({ kinds: [] })), + selectInflowRequirement: () => null, + getX402Payload: vi.fn(() => Promise.resolve({ status: 'INITIATED' as const })), + cancelApproval: vi.fn(() => Promise.resolve(undefined)), + prepareInflowPayment: vi.fn(), + }), + ), }; }); @@ -180,15 +183,20 @@ describe('augmentX402 — mutation contract', () => { function stubMppClient(overrides: Partial = {}): MppClient { return { - getSupported: vi.fn(async () => ({ kinds: [] })), - getTransaction: vi.fn(async () => ({ transactionId: 'tx-9', state: 'ready' as const, credential: 'CRED' })), + getSupported: vi.fn(() => Promise.resolve({ kinds: [] })), + getTransaction: vi.fn(() => + Promise.resolve({ transactionId: 'tx-9', state: 'ready' as const, credential: 'CRED' }), + ), createTransaction: vi.fn(), getConfig: vi.fn(), ...overrides, } as unknown as MppClient; } -function rawMpp(client: MppClient, cancelApproval: () => Promise = vi.fn(async () => undefined)): IMppResource { +function rawMpp( + client: MppClient, + cancelApproval: () => Promise = vi.fn(() => Promise.resolve(undefined)), +): IMppResource { return { client: () => Promise.resolve(client), cancelApproval }; } @@ -228,13 +236,13 @@ describe('Inflow.mpp augmented operations', () => { { method: 'inflow', intents: [{ intent: 'charge', rails: [{ rail: 'balance', currencies: ['USDC'] }] }] }, ], }; - const raw = rawMpp(stubMppClient({ getSupported: vi.fn(async () => supported) as MppClient['getSupported'] })); + const raw = rawMpp(stubMppClient({ getSupported: vi.fn(() => Promise.resolve(supported)) })); const mpp = augmentMpp(raw, 'https://api.example.test'); await expect(mpp.supported()).resolves.toEqual(supported); }); it('mpp.cancel delegates to cancelApproval and returns the best-effort frame', async () => { - const cancelApproval = vi.fn(async () => undefined); + const cancelApproval = vi.fn(() => Promise.resolve(undefined)); const mpp = augmentMpp(rawMpp(stubMppClient(), cancelApproval), 'https://api.example.test'); await expect(mpp.cancel({ approvalId: 'ap-7' })).resolves.toEqual({ approval_id: 'ap-7', @@ -282,7 +290,7 @@ describe('wrapEmittingPipeline — error propagation through the MPP pay handle' it('rethrows an Error raised before the pipeline starts', async () => { const raw: IMppResource = { client: () => Promise.reject(new Error('client boom')), - cancelApproval: vi.fn(async () => undefined), + cancelApproval: vi.fn(() => Promise.resolve(undefined)), }; const mpp = augmentMpp(raw, 'https://api.example.test'); const run = mpp.pay({ @@ -297,9 +305,12 @@ describe('wrapEmittingPipeline — error propagation through the MPP pay handle' }); it('wraps a non-Error rejection in an InflowSdkError carrying its string form', async () => { + // The pipeline is expected to wrap non-Error rejections; cast preserves the + // runtime string value while satisfying prefer-promise-reject-errors. + const nonErrorReason = 'weird-failure' as unknown as Error; const raw: IMppResource = { - client: () => Promise.reject('weird-failure'), - cancelApproval: vi.fn(async () => undefined), + client: () => Promise.reject(nonErrorReason), + cancelApproval: vi.fn(() => Promise.resolve(undefined)), }; const mpp = augmentMpp(raw, 'https://api.example.test'); const run = mpp.pay({ diff --git a/packages/core/test/unit/flows/mpp-inspect.test.ts b/packages/core/test/unit/flows/mpp-inspect.test.ts index 8a8eb93..c1fe9e3 100644 --- a/packages/core/test/unit/flows/mpp-inspect.test.ts +++ b/packages/core/test/unit/flows/mpp-inspect.test.ts @@ -118,7 +118,7 @@ describe('runMppInspectPipeline', () => { it('errors INVALID_402 when the 402 carries no WWW-Authenticate header', async () => { server.use(http.get(SELLER, () => new HttpResponse(null, { status: 402 }))); const [event] = await collect(); - expect(event).toEqual({ type: 'errored', code: 'INVALID_402', message: expect.any(String) }); + expect(event).toEqual({ type: 'errored', code: 'INVALID_402', message: expect.any(String) as string }); }); it('errors NO_INFLOW_MATCH when the 402 carries only unsupported method challenges', async () => { diff --git a/packages/core/test/unit/flows/mpp-shared.test.ts b/packages/core/test/unit/flows/mpp-shared.test.ts index 02adbca..02b4a8a 100644 --- a/packages/core/test/unit/flows/mpp-shared.test.ts +++ b/packages/core/test/unit/flows/mpp-shared.test.ts @@ -29,7 +29,7 @@ function decodableChallenge(over: { currency: over.currency ?? 'USDC', methodDetails: { rail: over.rail ?? 'balance' }, }), - } as unknown as MppChallenge; + }; } const usdcBalance = decodableChallenge({ id: 'a', currency: 'USDC', rail: 'balance' }); diff --git a/packages/core/test/unit/flows/x402-status.test.ts b/packages/core/test/unit/flows/x402-status.test.ts index 7b64171..51a7876 100644 --- a/packages/core/test/unit/flows/x402-status.test.ts +++ b/packages/core/test/unit/flows/x402-status.test.ts @@ -26,13 +26,13 @@ describe('classifyPayloadResponse', () => { it('classifies terminal failure statuses as failed', () => { for (const status of TERMINAL_FAILURE_STATUSES) { - expect(classifyPayloadResponse({ status } as unknown as X402PayloadResponse)).toBe('failed'); + expect(classifyPayloadResponse({ status })).toBe('failed'); } }); it('classifies everything else as pending', () => { - expect(classifyPayloadResponse({ status: 'INITIATED' } as unknown as X402PayloadResponse)).toBe('pending'); - expect(classifyPayloadResponse({ status: 'AWAITING_SIGNATURE' } as unknown as X402PayloadResponse)).toBe('pending'); + expect(classifyPayloadResponse({ status: 'INITIATED' })).toBe('pending'); + expect(classifyPayloadResponse({ status: 'AWAITING_SIGNATURE' })).toBe('pending'); }); });