Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/test/unit/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
10 changes: 5 additions & 5 deletions packages/cli/test/unit/commands/auth/index-tty.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ describe('runAuthLogin (tty mode)', () => {
}),
};
const gen = runAuthLogin(
ctx as never,
ctx,
{
authResource: authStub(),
userResource: userStub(),
Expand Down Expand Up @@ -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(),
Expand All @@ -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,
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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(),
Expand Down
10 changes: 8 additions & 2 deletions packages/cli/test/unit/commands/auth/login-extra.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<Login auth={auth} clientName="Test" connection={{ environment: 'production' }} onComplete={() => undefined} />,
Expand All @@ -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(
<Login auth={auth} clientName="Test" connection={{ environment: 'production' }} onComplete={() => undefined} />,
Expand Down
5 changes: 4 additions & 1 deletion packages/cli/test/unit/commands/auth/status-extra.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(<AuthStatus auth={auth} probe={true} onComplete={() => undefined} />);
await vi.waitFor(() => {
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/test/unit/commands/balances/index-tty.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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');
});
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/test/unit/commands/inspect/inspect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ function ctx(): InspectCommandContext {
error: (o: { code: string; message: string }) => {
throw new Error(`${o.code}: ${o.message}`);
},
} as InspectCommandContext;
};
}

describe('buildCombinedFrame', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/test/unit/commands/mpp/cancel-view.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ describe('CancelView', () => {
it('shows the cancelling spinner then the cancelled confirmation', async () => {
const onComplete = vi.fn();
const { lastFrame, unmount } = render(
<CancelView approvalId="ap-9" cancel={async () => undefined} onComplete={onComplete} />,
<CancelView approvalId="ap-9" cancel={() => Promise.resolve(undefined)} onComplete={onComplete} />,
);
await new Promise((r) => setTimeout(r, 50));
expect(lastFrame() ?? '').toContain('ap-9');
Expand Down
102 changes: 57 additions & 45 deletions packages/cli/test/unit/commands/mpp/index-runners.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function makeClient(overrides: Partial<MppClient> = {}): 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',
Expand Down Expand Up @@ -83,7 +83,7 @@ async function drain<T>(gen: AsyncGenerator<T>): Promise<T[]> {

async function drainWithReturn<T>(gen: AsyncGenerator<T, unknown>): 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);
Expand All @@ -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);
Expand All @@ -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 });
Expand All @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand All @@ -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');
Expand Down
Loading
Loading