From 440e588591f8af8ebd25f3c04ed80ea4d4cbab81 Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Thu, 12 Jan 2023 14:00:45 -0700 Subject: [PATCH 1/9] chore(open-payments): standardize incoming payment requests --- .../src/client/incoming-payment.test.ts | 259 ++++++++++-------- .../src/client/incoming-payment.ts | 58 ++-- 2 files changed, 176 insertions(+), 141 deletions(-) diff --git a/packages/open-payments/src/client/incoming-payment.test.ts b/packages/open-payments/src/client/incoming-payment.test.ts index 708390209d..fc46af977a 100644 --- a/packages/open-payments/src/client/incoming-payment.test.ts +++ b/packages/open-payments/src/client/incoming-payment.test.ts @@ -42,83 +42,21 @@ describe('incoming-payment', (): void => { const axiosInstance = defaultAxiosInstance const logger = silentLogger - const baseUrl = 'http://localhost:1000' + const paymentPointer = `http://localhost:1000/.well-known/pay` const openApiValidators = mockOpenApiResponseValidators() - describe('createIncomingPaymentRoutes', (): void => { - test('creates getIncomingPaymentOpenApiValidator properly', async (): Promise => { - jest.spyOn(openApi, 'createResponseValidator') - - createIncomingPaymentRoutes({ - axiosInstance, - openApi, - logger - }) - expect(openApi.createResponseValidator).toHaveBeenCalledWith({ - path: '/incoming-payments/{id}', - method: HttpMethod.GET - }) - }) - - test('creates createIncomingPaymentOpenApiValidator properly', async (): Promise => { - jest.spyOn(openApi, 'createResponseValidator') - - createIncomingPaymentRoutes({ - axiosInstance, - openApi, - logger - }) - - expect(openApi.createResponseValidator).toHaveBeenCalledWith({ - path: '/incoming-payments', - method: HttpMethod.POST - }) - }) - - test('creates completeIncomingPaymentOpenApiValidator properly', async (): Promise => { - jest.spyOn(openApi, 'createResponseValidator') - - createIncomingPaymentRoutes({ - axiosInstance, - openApi, - logger - }) - - expect(openApi.createResponseValidator).toHaveBeenCalledWith({ - path: '/incoming-payments/{id}/complete', - method: HttpMethod.POST - }) - }) - - test('creates listIncomingPaymentsOpenApiValidator', async (): Promise => { - jest.spyOn(openApi, 'createResponseValidator') - - createIncomingPaymentRoutes({ - axiosInstance, - openApi, - logger - }) - - expect(openApi.createResponseValidator).toHaveBeenCalledWith({ - path: '/incoming-payments', - method: HttpMethod.GET - }) - }) - }) - describe('getIncomingPayment', (): void => { test('returns incoming payment if passes validation', async (): Promise => { const incomingPayment = mockIncomingPayment() - nock(baseUrl).get('/incoming-payments').reply(200, incomingPayment) + nock(paymentPointer) + .get('/incoming-payments/1') + .reply(200, incomingPayment) const result = await getIncomingPayment( + { axiosInstance, logger }, { - axiosInstance, - logger - }, - { - url: `${baseUrl}/incoming-payments`, + url: `${paymentPointer}/incoming-payments/1`, accessToken: 'accessToken' }, openApiValidators.successfulValidator @@ -140,7 +78,9 @@ describe('incoming-payment', (): void => { } }) - nock(baseUrl).get('/incoming-payments').reply(200, incomingPayment) + nock(paymentPointer) + .get('/incoming-payments/1') + .reply(200, incomingPayment) await expect(() => getIncomingPayment( @@ -149,7 +89,7 @@ describe('incoming-payment', (): void => { logger }, { - url: `${baseUrl}/incoming-payments`, + url: `${paymentPointer}/incoming-payments/1`, accessToken: 'accessToken' }, openApiValidators.successfulValidator @@ -160,7 +100,9 @@ describe('incoming-payment', (): void => { test('throws if incoming payment does not pass open api validation', async (): Promise => { const incomingPayment = mockIncomingPayment() - nock(baseUrl).get('/incoming-payments').reply(200, incomingPayment) + nock(paymentPointer) + .get('/incoming-payments/1') + .reply(200, incomingPayment) await expect(() => getIncomingPayment( @@ -169,7 +111,7 @@ describe('incoming-payment', (): void => { logger }, { - url: `${baseUrl}/incoming-payments`, + url: `${paymentPointer}/incoming-payments/1`, accessToken: 'accessToken' }, openApiValidators.failedValidator @@ -198,26 +140,20 @@ describe('incoming-payment', (): void => { externalRef }) - const scope = nock(baseUrl) + const scope = nock(paymentPointer) .post('/incoming-payments') .reply(200, incomingPayment) const result = await createIncomingPayment( + { axiosInstance, logger }, + { paymentPointer, accessToken: 'accessToken' }, + openApiValidators.successfulValidator, { - axiosInstance, - logger - }, - { - url: `${baseUrl}/incoming-payments`, - body: { - incomingAmount, - expiresAt, - description, - externalRef - }, - accessToken: 'accessToken' - }, - openApiValidators.successfulValidator + incomingAmount, + expiresAt, + description, + externalRef + } ) scope.done() @@ -238,19 +174,16 @@ describe('incoming-payment', (): void => { completed: false }) - const scope = nock(baseUrl) + const scope = nock(paymentPointer) .post('/incoming-payments') .reply(200, incomingPayment) await expect(() => createIncomingPayment( { axiosInstance, logger }, - { - url: `${baseUrl}/incoming-payments`, - body: {}, - accessToken: 'accessToken' - }, - openApiValidators.successfulValidator + { paymentPointer, accessToken: 'accessToken' }, + openApiValidators.successfulValidator, + {} ) ).rejects.toThrowError() scope.done() @@ -259,22 +192,16 @@ describe('incoming-payment', (): void => { test('throws if the created incoming payment does not pass open api validation', async (): Promise => { const incomingPayment = mockIncomingPayment() - const scope = nock(baseUrl) + const scope = nock(paymentPointer) .post('/incoming-payments') .reply(200, incomingPayment) await expect(() => createIncomingPayment( - { - axiosInstance, - logger - }, - { - url: `${baseUrl}/incoming-payments`, - body: {}, - accessToken: 'accessToken' - }, - openApiValidators.failedValidator + { axiosInstance, logger }, + { paymentPointer, accessToken: 'accessToken' }, + openApiValidators.failedValidator, + {} ) ).rejects.toThrowError() scope.done() @@ -287,14 +214,14 @@ describe('incoming-payment', (): void => { completed: true }) - const scope = nock(baseUrl) + const scope = nock(paymentPointer) .post(`/incoming-payments/${incomingPayment.id}/complete`) .reply(200, incomingPayment) const result = await completeIncomingPayment( { axiosInstance, logger }, { - url: `${baseUrl}/incoming-payments/${incomingPayment.id}/complete`, + url: `${paymentPointer}/incoming-payments/${incomingPayment.id}`, accessToken: 'accessToken' }, openApiValidators.successfulValidator @@ -310,7 +237,7 @@ describe('incoming-payment', (): void => { completed: false }) - const scope = nock(baseUrl) + const scope = nock(paymentPointer) .post(`/incoming-payments/${incomingPayment.id}/complete`) .reply(200, incomingPayment) @@ -318,7 +245,7 @@ describe('incoming-payment', (): void => { completeIncomingPayment( { axiosInstance, logger }, { - url: `${baseUrl}/incoming-payments/${incomingPayment.id}/complete`, + url: `${paymentPointer}/incoming-payments/${incomingPayment.id}`, accessToken: 'accessToken' }, openApiValidators.successfulValidator @@ -333,7 +260,7 @@ describe('incoming-payment', (): void => { completed: true }) - const scope = nock(baseUrl) + const scope = nock(paymentPointer) .post(`/incoming-payments/${incomingPayment.id}/complete`) .reply(200, incomingPayment) @@ -341,7 +268,7 @@ describe('incoming-payment', (): void => { completeIncomingPayment( { axiosInstance, logger }, { - url: `${baseUrl}/incoming-payments/${incomingPayment.id}/complete`, + url: `${paymentPointer}/incoming-payments/${incomingPayment.id}`, accessToken: 'accessToken' }, openApiValidators.failedValidator @@ -353,8 +280,6 @@ describe('incoming-payment', (): void => { }) describe('listIncomingPayment', (): void => { - const paymentPointer = `${baseUrl}/.well-known/pay` - describe('forward pagination', (): void => { test.each` first | cursor @@ -718,6 +643,39 @@ describe('incoming-payment', (): void => { }) describe('routes', (): void => { + describe('get', (): void => { + test('calls get method with correct validator', async (): Promise => { + const mockResponseValidator = ({ path, method }) => + path === '/incoming-payments/{id}' && method === HttpMethod.GET + + const url = `${paymentPointer}/incoming-payments/1` + + jest + .spyOn(openApi, 'createResponseValidator') + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .mockImplementation(mockResponseValidator as any) + + const getSpy = jest + .spyOn(requestors, 'get') + .mockResolvedValueOnce(mockIncomingPayment()) + + await createIncomingPaymentRoutes({ + openApi, + axiosInstance, + logger + }).get({ url, accessToken: 'accessToken' }) + + expect(getSpy).toHaveBeenCalledWith( + { + axiosInstance, + logger + }, + { url, accessToken: 'accessToken' }, + true + ) + }) + }) + describe('list', (): void => { test('calls get method with correct validator', async (): Promise => { const mockResponseValidator = ({ path, method }) => @@ -727,7 +685,6 @@ describe('incoming-payment', (): void => { mockIncomingPaymentPaginationResult({ result: [mockIncomingPayment()] }) - const paymentPointer = `${baseUrl}/.well-known/pay` const url = `${paymentPointer}${getRSPath('/incoming-payments')}` jest @@ -755,5 +712,79 @@ describe('incoming-payment', (): void => { ) }) }) + + describe('create', (): void => { + test('calls post method with correct validator', async (): Promise => { + const mockResponseValidator = ({ path, method }) => + path === '/incoming-payments' && method === HttpMethod.POST + + const url = `${paymentPointer}/incoming-payments` + const incomingPaymentCreateArgs = { + description: 'Invoice', + incomingAmount: { assetCode: 'USD', assetScale: 2, value: '10' } + } + + jest + .spyOn(openApi, 'createResponseValidator') + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .mockImplementation(mockResponseValidator as any) + + const postSpy = jest + .spyOn(requestors, 'post') + .mockResolvedValueOnce(mockIncomingPayment(incomingPaymentCreateArgs)) + + await createIncomingPaymentRoutes({ + openApi, + axiosInstance, + logger + }).create( + { paymentPointer, accessToken: 'accessToken' }, + incomingPaymentCreateArgs + ) + + expect(postSpy).toHaveBeenCalledWith( + { + axiosInstance, + logger + }, + { url, accessToken: 'accessToken', body: incomingPaymentCreateArgs }, + true + ) + }) + }) + + describe('complete', (): void => { + test('calls post method with correct validator', async (): Promise => { + const mockResponseValidator = ({ path, method }) => + path === '/incoming-payments/{id}/complete' && + method === HttpMethod.POST + + const incomingPaymentUrl = `${paymentPointer}/incoming-payments/1` + + jest + .spyOn(openApi, 'createResponseValidator') + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .mockImplementation(mockResponseValidator as any) + + const postSpy = jest + .spyOn(requestors, 'post') + .mockResolvedValueOnce(mockIncomingPayment({ completed: true })) + + await createIncomingPaymentRoutes({ + openApi, + axiosInstance, + logger + }).complete({ url: incomingPaymentUrl, accessToken: 'accessToken' }) + + expect(postSpy).toHaveBeenCalledWith( + { + axiosInstance, + logger + }, + { url: `${incomingPaymentUrl}/complete`, accessToken: 'accessToken' }, + true + ) + }) + }) }) }) diff --git a/packages/open-payments/src/client/incoming-payment.ts b/packages/open-payments/src/client/incoming-payment.ts index afe092ed52..5d784fe206 100644 --- a/packages/open-payments/src/client/incoming-payment.ts +++ b/packages/open-payments/src/client/incoming-payment.ts @@ -9,28 +9,25 @@ import { } from '../types' import { get, post } from './requests' -interface GetArgs { +interface RequestWithUrlArgs { url: string accessToken: string } -interface PostArgs { - url: string - body?: T - accessToken: string -} - -interface ListGetArgs { +interface RequestWithPaymentPointerArgs { paymentPointer: string accessToken: string } export interface IncomingPaymentRoutes { - get(args: GetArgs): Promise - create(args: PostArgs): Promise - complete(args: PostArgs): Promise + get(args: RequestWithUrlArgs): Promise + create( + args: RequestWithPaymentPointerArgs, + createArgs: CreateIncomingPaymentArgs + ): Promise + complete(args: RequestWithUrlArgs): Promise list( - args: ListGetArgs, + args: RequestWithPaymentPointerArgs, pagination?: PaginationArgs ): Promise } @@ -65,25 +62,29 @@ export const createIncomingPaymentRoutes = ( }) return { - get: (args: GetArgs) => + get: (args: RequestWithUrlArgs) => getIncomingPayment( { axiosInstance, logger }, args, getIncomingPaymentOpenApiValidator ), - create: (args: PostArgs) => + create: ( + requestArgs: RequestWithPaymentPointerArgs, + createArgs: CreateIncomingPaymentArgs + ) => createIncomingPayment( { axiosInstance, logger }, - args, - createIncomingPaymentOpenApiValidator + requestArgs, + createIncomingPaymentOpenApiValidator, + createArgs ), - complete: (args: PostArgs) => + complete: (args: RequestWithUrlArgs) => completeIncomingPayment( { axiosInstance, logger }, args, completeIncomingPaymentOpenApiValidator ), - list: (args: ListGetArgs, pagination?: PaginationArgs) => + list: (args: RequestWithPaymentPointerArgs, pagination?: PaginationArgs) => listIncomingPayment( { axiosInstance, logger }, args, @@ -95,7 +96,7 @@ export const createIncomingPaymentRoutes = ( export const getIncomingPayment = async ( deps: BaseDeps, - args: GetArgs, + args: RequestWithUrlArgs, validateOpenApiResponse: ResponseValidator ) => { const { axiosInstance, logger } = deps @@ -119,15 +120,17 @@ export const getIncomingPayment = async ( export const createIncomingPayment = async ( deps: BaseDeps, - args: PostArgs, - validateOpenApiResponse: ResponseValidator + requestArgs: RequestWithPaymentPointerArgs, + validateOpenApiResponse: ResponseValidator, + createArgs: CreateIncomingPaymentArgs ) => { const { axiosInstance, logger } = deps - const { url } = args + const { paymentPointer, accessToken } = requestArgs + const url = `${paymentPointer}${getRSPath('/incoming-payments')}` const incomingPayment = await post( { axiosInstance, logger }, - args, + { url, accessToken, body: createArgs }, validateOpenApiResponse ) @@ -143,15 +146,16 @@ export const createIncomingPayment = async ( export const completeIncomingPayment = async ( deps: BaseDeps, - args: PostArgs, + args: RequestWithUrlArgs, validateOpenApiResponse: ResponseValidator ) => { const { axiosInstance, logger } = deps - const { url } = args + const { url: incomingPaymentUrl, accessToken } = args + const url = `${incomingPaymentUrl}/complete` const incomingPayment = await post( { axiosInstance, logger }, - args, + { url, accessToken }, validateOpenApiResponse ) @@ -167,7 +171,7 @@ export const completeIncomingPayment = async ( export const listIncomingPayment = async ( deps: BaseDeps, - args: ListGetArgs, + args: RequestWithPaymentPointerArgs, validateOpenApiResponse: ResponseValidator, pagination?: PaginationArgs ) => { From d22c304481c5cb215bf538984fe758ad2c8ac2cc Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Thu, 12 Jan 2023 14:00:54 -0700 Subject: [PATCH 2/9] chore(open-payments): standardize outgoing payment requests --- .../src/client/outgoing-payment.test.ts | 263 +++++++++++------- .../src/client/outgoing-payment.ts | 58 ++-- 2 files changed, 196 insertions(+), 125 deletions(-) diff --git a/packages/open-payments/src/client/outgoing-payment.test.ts b/packages/open-payments/src/client/outgoing-payment.test.ts index 98e956dce4..a18e944de9 100644 --- a/packages/open-payments/src/client/outgoing-payment.test.ts +++ b/packages/open-payments/src/client/outgoing-payment.test.ts @@ -16,6 +16,15 @@ import { import nock from 'nock' import path from 'path' import { v4 as uuid } from 'uuid' +import * as requestors from './requests' + +jest.mock('./requests', () => { + return { + // https://jestjs.io/docs/jest-object#jestmockmodulename-factory-options + __esModule: true, + ...jest.requireActual('./requests') + } +}) describe('outgoing-payment', (): void => { let openApi: OpenAPI @@ -28,71 +37,27 @@ describe('outgoing-payment', (): void => { const axiosInstance = defaultAxiosInstance const logger = silentLogger - const baseUrl = 'http://localhost:1000' + const paymentPointer = `http://localhost:1000/.well-known/pay` const openApiValidators = mockOpenApiResponseValidators() - describe('createOutgoingPaymentRoutes', (): void => { - test('creates getOutgoingPaymentOpenApiValidator properly', async (): Promise => { - jest.spyOn(openApi, 'createResponseValidator') - - createOutgoingPaymentRoutes({ - axiosInstance, - openApi, - logger - }) - expect(openApi.createResponseValidator).toHaveBeenCalledWith({ - path: '/outgoing-payments/{id}', - method: HttpMethod.GET - }) - }) - - test('creates listOutgoingPaymentOpenApiValidator properly', async (): Promise => { - jest.spyOn(openApi, 'createResponseValidator') - - createOutgoingPaymentRoutes({ - axiosInstance, - openApi, - logger - }) - expect(openApi.createResponseValidator).toHaveBeenCalledWith({ - path: '/outgoing-payments', - method: HttpMethod.GET - }) - }) - - test('creates createOutgoingPaymentOpenApiValidator properly', async (): Promise => { - jest.spyOn(openApi, 'createResponseValidator') - - createOutgoingPaymentRoutes({ - axiosInstance, - openApi, - logger - }) - expect(openApi.createResponseValidator).toHaveBeenCalledWith({ - path: '/outgoing-payments', - method: HttpMethod.POST - }) - }) - }) - describe('getOutgoingPayment', (): void => { test('returns outgoing payment if passes validation', async (): Promise => { const outgoingPayment = mockOutgoingPayment() - nock(baseUrl).get('/outgoing-payments').reply(200, outgoingPayment) + const scope = nock(paymentPointer) + .get('/outgoing-payments/1') + .reply(200, outgoingPayment) const result = await getOutgoingPayment( + { axiosInstance, logger }, { - axiosInstance, - logger - }, - { - url: `${baseUrl}/outgoing-payments`, + url: `${paymentPointer}/outgoing-payments/1`, accessToken: 'accessToken' }, openApiValidators.successfulValidator ) expect(result).toStrictEqual(outgoingPayment) + scope.done() }) test('throws if outgoing payment does not pass validation', async (): Promise => { @@ -109,47 +74,45 @@ describe('outgoing-payment', (): void => { } }) - nock(baseUrl).get('/outgoing-payments').reply(200, outgoingPayment) + const scope = nock(paymentPointer) + .get('/outgoing-payments/1') + .reply(200, outgoingPayment) await expect(() => getOutgoingPayment( + { axiosInstance, logger }, { - axiosInstance, - logger - }, - { - url: `${baseUrl}/outgoing-payments`, + url: `${paymentPointer}/outgoing-payments/1`, accessToken: 'accessToken' }, openApiValidators.successfulValidator ) ).rejects.toThrowError() + scope.done() }) test('throws if outgoing payment does not pass open api validation', async (): Promise => { const outgoingPayment = mockOutgoingPayment() - nock(baseUrl).get('/outgoing-payments').reply(200, outgoingPayment) + const scope = nock(paymentPointer) + .get('/outgoing-payments/1') + .reply(200, outgoingPayment) await expect(() => getOutgoingPayment( + { axiosInstance, logger }, { - axiosInstance, - logger - }, - { - url: `${baseUrl}/outgoing-payments`, + url: `${paymentPointer}/outgoing-payments/1`, accessToken: 'accessToken' }, openApiValidators.failedValidator ) ).rejects.toThrowError() + scope.done() }) }) describe('listOutgoingPayment', (): void => { - const paymentPointer = 'http://localhost:1000/.well-known/pay' - describe('forward pagination', (): void => { test.each` first | cursor @@ -173,10 +136,7 @@ describe('outgoing-payment', (): void => { .reply(200, outgoingPaymentPaginationResult) const result = await listOutgoingPayments( - { - axiosInstance, - logger - }, + { axiosInstance, logger }, { paymentPointer, accessToken: 'accessToken' @@ -297,7 +257,7 @@ describe('outgoing-payment', (): void => { }) describe('createOutgoingPayment', (): void => { - const quoteId = `${baseUrl}/quotes/${uuid()}` + const quoteId = `${paymentPointer}/quotes/${uuid()}` test.each` description | externalRef @@ -312,25 +272,22 @@ describe('outgoing-payment', (): void => { externalRef }) - const scope = nock(baseUrl) + const scope = nock(paymentPointer) .post('/outgoing-payments') .reply(200, outgoingPayment) const result = await createOutgoingPayment( + { axiosInstance, logger }, { - axiosInstance, - logger + paymentPointer, + accessToken: 'accessToken' }, + openApiValidators.successfulValidator, { - url: `${baseUrl}/outgoing-payments`, - accessToken: 'accessToken', - body: { - quoteId, - description, - externalRef - } - }, - openApiValidators.successfulValidator + quoteId, + description, + externalRef + } ) expect(result).toEqual(outgoingPayment) scope.done() @@ -351,24 +308,21 @@ describe('outgoing-payment', (): void => { } }) - const scope = nock(baseUrl) + const scope = nock(paymentPointer) .post('/outgoing-payments') .reply(200, outgoingPayment) await expect(() => createOutgoingPayment( + { axiosInstance, logger }, { - axiosInstance, - logger + paymentPointer, + accessToken: 'accessToken' }, + openApiValidators.successfulValidator, { - url: `${baseUrl}/outgoing-payments`, - accessToken: 'accessToken', - body: { - quoteId: uuid() - } - }, - openApiValidators.successfulValidator + quoteId: uuid() + } ) ).rejects.toThrowError() scope.done() @@ -377,7 +331,7 @@ describe('outgoing-payment', (): void => { test('throws if outgoing payment does not pass open api validation', async (): Promise => { const outgoingPayment = mockOutgoingPayment() - const scope = nock(baseUrl) + const scope = nock(paymentPointer) .post('/outgoing-payments') .reply(200, outgoingPayment) @@ -388,13 +342,13 @@ describe('outgoing-payment', (): void => { logger }, { - url: `${baseUrl}/outgoing-payments`, - accessToken: 'accessToken', - body: { - quoteId: uuid() - } + paymentPointer, + accessToken: 'accessToken' }, - openApiValidators.failedValidator + openApiValidators.failedValidator, + { + quoteId: uuid() + } ) ).rejects.toThrowError() scope.done() @@ -487,4 +441,115 @@ describe('outgoing-payment', (): void => { ) }) }) + + describe('routes', (): void => { + describe('get', (): void => { + test('calls get method with correct validator', async (): Promise => { + const mockResponseValidator = ({ path, method }) => + path === '/outgoing-payments/{id}' && method === HttpMethod.GET + + const url = `${paymentPointer}/outgoing-payments/1` + + jest + .spyOn(openApi, 'createResponseValidator') + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .mockImplementation(mockResponseValidator as any) + + const getSpy = jest + .spyOn(requestors, 'get') + .mockResolvedValueOnce(mockOutgoingPayment()) + + await createOutgoingPaymentRoutes({ + openApi, + axiosInstance, + logger + }).get({ url, accessToken: 'accessToken' }) + + expect(getSpy).toHaveBeenCalledWith( + { + axiosInstance, + logger + }, + { url, accessToken: 'accessToken' }, + true + ) + }) + }) + + describe('list', (): void => { + test('calls get method with correct validator', async (): Promise => { + const mockResponseValidator = ({ path, method }) => + path === '/outgoing-payments' && method === HttpMethod.GET + + const outgoingPaymentPaginationResult = + mockOutgoingPaymentPaginationResult({ + result: [mockOutgoingPayment()] + }) + const url = `${paymentPointer}/outgoing-payments` + + jest + .spyOn(openApi, 'createResponseValidator') + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .mockImplementation(mockResponseValidator as any) + + const getSpy = jest + .spyOn(requestors, 'get') + .mockResolvedValueOnce(outgoingPaymentPaginationResult) + + await createOutgoingPaymentRoutes({ + openApi, + axiosInstance, + logger + }).list({ paymentPointer, accessToken: 'accessToken' }) + + expect(getSpy).toHaveBeenCalledWith( + { + axiosInstance, + logger + }, + { url, accessToken: 'accessToken' }, + true + ) + }) + }) + + describe('create', (): void => { + test('calls post method with correct validator', async (): Promise => { + const mockResponseValidator = ({ path, method }) => + path === '/outgoing-payments' && method === HttpMethod.POST + + const url = `${paymentPointer}/outgoing-payments` + const outgoingPaymentCreateArgs = { + quoteId: uuid() + } + + jest + .spyOn(openApi, 'createResponseValidator') + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .mockImplementation(mockResponseValidator as any) + + const postSpy = jest + .spyOn(requestors, 'post') + .mockResolvedValueOnce(mockOutgoingPayment(outgoingPaymentCreateArgs)) + + await createOutgoingPaymentRoutes({ + openApi, + axiosInstance, + logger + }).create( + { paymentPointer, accessToken: 'accessToken' }, + outgoingPaymentCreateArgs + ) + + expect(postSpy).toHaveBeenCalledWith( + { + axiosInstance, + logger + }, + { url, accessToken: 'accessToken', body: outgoingPaymentCreateArgs }, + true + ) + }) + }) + }) }) diff --git a/packages/open-payments/src/client/outgoing-payment.ts b/packages/open-payments/src/client/outgoing-payment.ts index 4c0d3cbc36..83a69dfb8d 100644 --- a/packages/open-payments/src/client/outgoing-payment.ts +++ b/packages/open-payments/src/client/outgoing-payment.ts @@ -9,29 +9,26 @@ import { } from '../types' import { get, post } from './requests' -interface GetArgs { +interface RequestWithUrlArgs { url: string accessToken: string } -interface ListGetArgs { +interface RequestWithPaymentPointerArgs { paymentPointer: string accessToken: string } -interface PostArgs { - url: string - body: T - accessToken: string -} - export interface OutgoingPaymentRoutes { - get(args: GetArgs): Promise + get(args: RequestWithUrlArgs): Promise list( - args: ListGetArgs, + args: RequestWithPaymentPointerArgs, pagination?: PaginationArgs ): Promise - create(args: PostArgs): Promise + create( + requestArgs: RequestWithPaymentPointerArgs, + createArgs: CreateOutgoingPaymentArgs + ): Promise } export const createOutgoingPaymentRoutes = ( @@ -58,35 +55,42 @@ export const createOutgoingPaymentRoutes = ( }) return { - get: (args: GetArgs) => + get: (requestArgs: RequestWithUrlArgs) => getOutgoingPayment( { axiosInstance, logger }, - args, + requestArgs, getOutgoingPaymentOpenApiValidator ), - list: (getArgs: ListGetArgs, pagination?: PaginationArgs) => + list: ( + requestArgs: RequestWithPaymentPointerArgs, + pagination?: PaginationArgs + ) => listOutgoingPayments( { axiosInstance, logger }, - getArgs, + requestArgs, listOutgoingPaymentOpenApiValidator, pagination ), - create: (args: PostArgs) => + create: ( + requestArgs: RequestWithPaymentPointerArgs, + createArgs: CreateOutgoingPaymentArgs + ) => createOutgoingPayment( { axiosInstance, logger }, - args, - createOutgoingPaymentOpenApiValidator + requestArgs, + createOutgoingPaymentOpenApiValidator, + createArgs ) } } export const getOutgoingPayment = async ( deps: BaseDeps, - args: GetArgs, + requestArgs: RequestWithUrlArgs, validateOpenApiResponse: ResponseValidator ) => { const { axiosInstance, logger } = deps - const { url, accessToken } = args + const { url, accessToken } = requestArgs const outgoingPayment = await get( { axiosInstance, logger }, @@ -106,15 +110,17 @@ export const getOutgoingPayment = async ( export const createOutgoingPayment = async ( deps: BaseDeps, - args: PostArgs, - validateOpenApiResponse: ResponseValidator + requestArgs: RequestWithPaymentPointerArgs, + validateOpenApiResponse: ResponseValidator, + createArgs: CreateOutgoingPaymentArgs ) => { const { axiosInstance, logger } = deps - const { url, body, accessToken } = args + const { paymentPointer, accessToken } = requestArgs + const url = `${paymentPointer}${getRSPath('/outgoing-payments')}` const outgoingPayment = await post( { axiosInstance, logger }, - { url, body, accessToken }, + { url, body: createArgs, accessToken }, validateOpenApiResponse ) @@ -130,12 +136,12 @@ export const createOutgoingPayment = async ( export const listOutgoingPayments = async ( deps: BaseDeps, - getArgs: ListGetArgs, + requestArgs: RequestWithPaymentPointerArgs, validateOpenApiResponse: ResponseValidator, pagination?: PaginationArgs ) => { const { axiosInstance, logger } = deps - const { accessToken, paymentPointer } = getArgs + const { accessToken, paymentPointer } = requestArgs const url = `${paymentPointer}${getRSPath('/outgoing-payments')}` const outgoingPayments = await get( From c196901e05c6745d3577ad386f783445c7d8ec53 Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Thu, 12 Jan 2023 14:01:22 -0700 Subject: [PATCH 3/9] chore(open-payments): update tests for ilp-stream-connection & payment pointers --- .../src/client/ilp-stream-connection.test.ts | 49 +++++++++-- .../src/client/payment-pointer.test.ts | 87 +++++++++++++++---- 2 files changed, 113 insertions(+), 23 deletions(-) diff --git a/packages/open-payments/src/client/ilp-stream-connection.test.ts b/packages/open-payments/src/client/ilp-stream-connection.test.ts index 3a17949302..fe2c37e3d2 100644 --- a/packages/open-payments/src/client/ilp-stream-connection.test.ts +++ b/packages/open-payments/src/client/ilp-stream-connection.test.ts @@ -1,7 +1,20 @@ import { createILPStreamConnectionRoutes } from './ilp-stream-connection' import { OpenAPI, HttpMethod, createOpenAPI } from 'openapi' import path from 'path' -import { defaultAxiosInstance, silentLogger } from '../test/helpers' +import { + defaultAxiosInstance, + mockILPStreamConnection, + silentLogger +} from '../test/helpers' +import * as requestors from './requests' + +jest.mock('./requests', () => { + return { + // https://jestjs.io/docs/jest-object#jestmockmodulename-factory-options + __esModule: true, + ...jest.requireActual('./requests') + } +}) describe('ilp-stream-connection', (): void => { let openApi: OpenAPI @@ -15,14 +28,34 @@ describe('ilp-stream-connection', (): void => { const axiosInstance = defaultAxiosInstance const logger = silentLogger - describe('createILPStreamConnectionRoutes', (): void => { - test('calls createResponseValidator properly', async (): Promise => { - jest.spyOn(openApi, 'createResponseValidator') + describe('routes', (): void => { + const ilpStreamConnection = mockILPStreamConnection() + + describe('get', (): void => { + test('calls get method with correct validator', async (): Promise => { + const mockResponseValidator = ({ path, method }) => + path === '/connections/{id}' && method === HttpMethod.GET + + jest + .spyOn(openApi, 'createResponseValidator') + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .mockImplementation(mockResponseValidator as any) + + const getSpy = jest + .spyOn(requestors, 'get') + .mockResolvedValueOnce(ilpStreamConnection) + + await createILPStreamConnectionRoutes({ + openApi, + axiosInstance, + logger + }).get({ url: ilpStreamConnection.id }) - createILPStreamConnectionRoutes({ axiosInstance, openApi, logger }) - expect(openApi.createResponseValidator).toHaveBeenCalledWith({ - path: '/connections/{id}', - method: HttpMethod.GET + expect(getSpy).toHaveBeenCalledWith( + { axiosInstance, logger }, + { url: ilpStreamConnection.id }, + true + ) }) }) }) diff --git a/packages/open-payments/src/client/payment-pointer.test.ts b/packages/open-payments/src/client/payment-pointer.test.ts index 19d0713206..009deff9b2 100644 --- a/packages/open-payments/src/client/payment-pointer.test.ts +++ b/packages/open-payments/src/client/payment-pointer.test.ts @@ -1,7 +1,21 @@ import { createPaymentPointerRoutes } from './payment-pointer' import { OpenAPI, HttpMethod, createOpenAPI } from 'openapi' import path from 'path' -import { defaultAxiosInstance, silentLogger } from '../test/helpers' +import { + defaultAxiosInstance, + mockJwk, + mockPaymentPointer, + silentLogger +} from '../test/helpers' +import * as requestors from './requests' + +jest.mock('./requests', () => { + return { + // https://jestjs.io/docs/jest-object#jestmockmodulename-factory-options + __esModule: true, + ...jest.requireActual('./requests') + } +}) describe('payment-pointer', (): void => { let openApi: OpenAPI @@ -15,22 +29,65 @@ describe('payment-pointer', (): void => { const axiosInstance = defaultAxiosInstance const logger = silentLogger - describe('createPaymentPointerRoutes', (): void => { - test('calls createResponseValidator properly', async (): Promise => { - jest.spyOn(openApi, 'createResponseValidator') + describe('routes', (): void => { + const paymentPointer = mockPaymentPointer() - createPaymentPointerRoutes({ - axiosInstance, - openApi, - logger - }) - expect(openApi.createResponseValidator).toHaveBeenCalledWith({ - path: '/', - method: HttpMethod.GET + describe('get', (): void => { + test('calls get method with correct validator', async (): Promise => { + const mockResponseValidator = ({ path, method }) => + path === '/' && method === HttpMethod.GET + + jest + .spyOn(openApi, 'createResponseValidator') + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .mockImplementation(mockResponseValidator as any) + + const getSpy = jest + .spyOn(requestors, 'get') + .mockResolvedValueOnce(paymentPointer) + + await createPaymentPointerRoutes({ + openApi, + axiosInstance, + logger + }).get({ url: paymentPointer.id }) + + expect(getSpy).toHaveBeenCalledWith( + { axiosInstance, logger }, + { url: paymentPointer.id }, + true + ) }) - expect(openApi.createResponseValidator).toHaveBeenCalledWith({ - path: '/jwks.json', - method: HttpMethod.GET + }) + + describe('getKeys', (): void => { + test('calls get method with correct validator', async (): Promise => { + const mockResponseValidator = ({ path, method }) => + path === '/jwks.json' && method === HttpMethod.GET + + jest + .spyOn(openApi, 'createResponseValidator') + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .mockImplementation(mockResponseValidator as any) + + const getSpy = jest + .spyOn(requestors, 'get') + .mockResolvedValueOnce([mockJwk()]) + + await createPaymentPointerRoutes({ + openApi, + axiosInstance, + logger + }).getKeys({ url: paymentPointer.id }) + + expect(getSpy).toHaveBeenCalledWith( + { + axiosInstance, + logger + }, + { url: `${paymentPointer.id}/jwks.json` }, + true + ) }) }) }) From 85aa9d5ded4d18d7155cf026e46b10b2279b8d6d Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Thu, 12 Jan 2023 14:01:40 -0700 Subject: [PATCH 4/9] chore(open-payments): add test mock functions --- packages/open-payments/src/test/helpers.ts | 24 +++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/open-payments/src/test/helpers.ts b/packages/open-payments/src/test/helpers.ts index e9c565c287..cc3b9e68c1 100644 --- a/packages/open-payments/src/test/helpers.ts +++ b/packages/open-payments/src/test/helpers.ts @@ -11,7 +11,9 @@ import { OutgoingPayment, OutgoingPaymentPaginationResult, IncomingPaymentPaginationResult, - AccessToken + AccessToken, + PaymentPointer, + JWK } from '../types' import base64url from 'base64url' import { v4 as uuid } from 'uuid' @@ -39,6 +41,26 @@ export const mockOpenApiResponseValidators = () => ({ }) as ResponseValidator }) +export const mockJwk = (overrides?: Partial): JWK => ({ + x: uuid(), + kid: uuid(), + alg: 'EdDSA', + kty: 'OKP', + crv: 'Ed25519', + ...overrides +}) + +export const mockPaymentPointer = ( + overrides?: Partial +): PaymentPointer => ({ + id: 'https://example.com/.well-known/pay', + publicName: 'Payment Pointer', + authServer: 'https://auth.wallet.example/authorize', + assetScale: 2, + assetCode: 'USD', + ...overrides +}) + export const mockILPStreamConnection = ( overrides?: Partial ): ILPStreamConnection => ({ From a59b1e5c92e9040d8e3d80fc314eced2cf90b6b5 Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Thu, 12 Jan 2023 16:44:46 -0700 Subject: [PATCH 5/9] chore(open-payments): add remaining tests for request functions --- .../open-payments/src/client/requests.test.ts | 141 +++++++++++++++++- packages/open-payments/src/test/helpers.ts | 17 +++ 2 files changed, 157 insertions(+), 1 deletion(-) diff --git a/packages/open-payments/src/client/requests.test.ts b/packages/open-payments/src/client/requests.test.ts index e9d80ba7c5..7bb9e573ed 100644 --- a/packages/open-payments/src/client/requests.test.ts +++ b/packages/open-payments/src/client/requests.test.ts @@ -2,7 +2,11 @@ import { createAxiosInstance, deleteRequest, get, post } from './requests' import { generateKeyPairSync } from 'crypto' import nock from 'nock' -import { mockOpenApiResponseValidators, silentLogger } from '../test/helpers' +import { + mockOpenApiResponseValidators, + silentLogger, + withEnvVariableOverride +} from '../test/helpers' describe('requests', (): void => { const logger = silentLogger @@ -194,6 +198,51 @@ describe('requests', (): void => { ) ).rejects.toThrow(/Failed to validate OpenApi response/) }) + + test( + 'keeps https protocol for request if non-development environment', + withEnvVariableOverride( + { NODE_ENV: 'production' }, + async (): Promise => { + const httpsUrl = 'https://localhost:1000/' + const scope = nock(httpsUrl).get('/').reply(200) + + await get( + { axiosInstance, logger }, + { url: httpsUrl }, + responseValidators.successfulValidator + ) + + expect(axiosInstance.get).toHaveBeenCalledWith(httpsUrl, { + headers: {} + }) + scope.done() + } + ) + ) + + test( + 'uses http protocol for request if development environment', + withEnvVariableOverride( + { NODE_ENV: 'development' }, + async (): Promise => { + const httpsUrl = 'https://localhost:1000/' + const httpUrl = httpsUrl.replace('https', 'http') + const scope = nock(httpUrl).get('/').reply(200) + + await get( + { axiosInstance, logger }, + { url: httpsUrl }, + responseValidators.successfulValidator + ) + + expect(axiosInstance.get).toHaveBeenCalledWith(httpUrl, { + headers: {} + }) + scope.done() + } + ) + ) }) describe('post', (): void => { @@ -364,6 +413,51 @@ describe('requests', (): void => { ) ).rejects.toThrow(/Failed to validate OpenApi response/) }) + + test( + 'keeps https protocol for request if non-development environment', + withEnvVariableOverride( + { NODE_ENV: 'production' }, + async (): Promise => { + const httpsUrl = 'https://localhost:1000/' + const scope = nock(httpsUrl).post('/').reply(200) + + await post( + { axiosInstance, logger }, + { url: httpsUrl }, + responseValidators.successfulValidator + ) + + expect(axiosInstance.post).toHaveBeenCalledWith(httpsUrl, undefined, { + headers: {} + }) + scope.done() + } + ) + ) + + test( + 'uses http protocol for request if development environment', + withEnvVariableOverride( + { NODE_ENV: 'development' }, + async (): Promise => { + const httpsUrl = 'https://localhost:1000/' + const httpUrl = httpsUrl.replace('https', 'http') + const scope = nock(httpUrl).post('/').reply(200) + + await post( + { axiosInstance, logger }, + { url: httpsUrl }, + responseValidators.successfulValidator + ) + + expect(axiosInstance.post).toHaveBeenCalledWith(httpUrl, undefined, { + headers: {} + }) + scope.done() + } + ) + ) }) describe('delete', (): void => { @@ -501,5 +595,50 @@ describe('requests', (): void => { ) ).rejects.toThrow(/Failed to validate OpenApi response/) }) + + test( + 'keeps https protocol for request if non-development environment', + withEnvVariableOverride( + { NODE_ENV: 'production' }, + async (): Promise => { + const httpsUrl = 'https://localhost:1000/' + const scope = nock(httpsUrl).delete('/').reply(200) + + await deleteRequest( + { axiosInstance, logger }, + { url: httpsUrl }, + responseValidators.successfulValidator + ) + + expect(axiosInstance.delete).toHaveBeenCalledWith(httpsUrl, { + headers: {} + }) + scope.done() + } + ) + ) + + test( + 'uses http protocol for request if development environment', + withEnvVariableOverride( + { NODE_ENV: 'development' }, + async (): Promise => { + const httpsUrl = 'https://localhost:1000/' + const httpUrl = httpsUrl.replace('https', 'http') + const scope = nock(httpUrl).delete('/').reply(200) + + await deleteRequest( + { axiosInstance, logger }, + { url: httpsUrl }, + responseValidators.successfulValidator + ) + + expect(axiosInstance.delete).toHaveBeenCalledWith(httpUrl, { + headers: {} + }) + scope.done() + } + ) + ) }) }) diff --git a/packages/open-payments/src/test/helpers.ts b/packages/open-payments/src/test/helpers.ts index cc3b9e68c1..a24d4ed342 100644 --- a/packages/open-payments/src/test/helpers.ts +++ b/packages/open-payments/src/test/helpers.ts @@ -31,6 +31,23 @@ export const defaultAxiosInstance = createAxiosInstance({ privateKey: generateKeyPairSync('ed25519').privateKey }) +export const withEnvVariableOverride = ( + override: Record, + testCallback: () => Promise +): (() => Promise) => { + return async () => { + const savedEnvVars = Object.assign({}, process.env) + + for (const key in override) { + process.env[key] = override[key] + } + + await testCallback() + + process.env = savedEnvVars + } +} + export const mockOpenApiResponseValidators = () => ({ successfulValidator: ((data: unknown): data is unknown => // eslint-disable-next-line @typescript-eslint/no-explicit-any From 8a235dd572863c52c91cd501dce71051a2f784a7 Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Thu, 12 Jan 2023 17:10:52 -0700 Subject: [PATCH 6/9] chore(open-payments): address comments --- packages/open-payments/src/client/grant.ts | 23 ++++--- .../src/client/ilp-stream-connection.ts | 10 +-- .../src/client/incoming-payment.test.ts | 64 +++++++++---------- .../src/client/incoming-payment.ts | 41 ++++++------ packages/open-payments/src/client/index.ts | 14 ++++ .../src/client/outgoing-payment.test.ts | 12 ++-- .../src/client/outgoing-payment.ts | 38 +++++------ .../src/client/payment-pointer.ts | 14 ++-- packages/open-payments/src/client/quote.ts | 10 +-- packages/open-payments/src/client/token.ts | 19 ++---- packages/open-payments/src/test/helpers.ts | 9 +-- 11 files changed, 118 insertions(+), 136 deletions(-) diff --git a/packages/open-payments/src/client/grant.ts b/packages/open-payments/src/client/grant.ts index ebc8c1b2d7..3a4f0c4b83 100644 --- a/packages/open-payments/src/client/grant.ts +++ b/packages/open-payments/src/client/grant.ts @@ -1,5 +1,9 @@ import { HttpMethod } from 'openapi' -import { RouteDeps } from '.' +import { + ResourceRequestArgs, + RouteDeps, + UnauthenticatedResourceRequestArgs +} from '.' import { getASPath, InteractiveGrant, @@ -13,21 +17,16 @@ export interface GrantRouteDeps extends RouteDeps { client: string } -interface PostArgs { - url: string - accessToken: string -} - export interface GrantRoutes { request( - postArgs: Omit, + postArgs: UnauthenticatedResourceRequestArgs, args: Omit ): Promise continue( - postArgs: PostArgs, + postArgs: ResourceRequestArgs, args: GrantContinuationRequest ): Promise - cancel(postArgs: PostArgs): Promise + cancel(postArgs: ResourceRequestArgs): Promise } export const createGrantRoutes = (deps: GrantRouteDeps): GrantRoutes => { @@ -49,7 +48,7 @@ export const createGrantRoutes = (deps: GrantRouteDeps): GrantRoutes => { return { request: ( - { url }: Omit, + { url }: UnauthenticatedResourceRequestArgs, args: Omit ) => post( @@ -64,7 +63,7 @@ export const createGrantRoutes = (deps: GrantRouteDeps): GrantRoutes => { requestGrantValidator ), continue: ( - { url, accessToken }: PostArgs, + { url, accessToken }: ResourceRequestArgs, args: GrantContinuationRequest ) => post( @@ -76,7 +75,7 @@ export const createGrantRoutes = (deps: GrantRouteDeps): GrantRoutes => { }, continueGrantValidator ), - cancel: ({ url, accessToken }: PostArgs) => + cancel: ({ url, accessToken }: ResourceRequestArgs) => deleteRequest( deps, { diff --git a/packages/open-payments/src/client/ilp-stream-connection.ts b/packages/open-payments/src/client/ilp-stream-connection.ts index 6227022128..d4da86156f 100644 --- a/packages/open-payments/src/client/ilp-stream-connection.ts +++ b/packages/open-payments/src/client/ilp-stream-connection.ts @@ -1,14 +1,10 @@ import { HttpMethod } from 'openapi' -import { RouteDeps } from '.' +import { RouteDeps, UnauthenticatedResourceRequestArgs } from '.' import { getRSPath, ILPStreamConnection } from '../types' import { get } from './requests' -interface GetArgs { - url: string -} - export interface ILPStreamConnectionRoutes { - get(args: GetArgs): Promise + get(args: UnauthenticatedResourceRequestArgs): Promise } export const createILPStreamConnectionRoutes = ( @@ -23,7 +19,7 @@ export const createILPStreamConnectionRoutes = ( }) return { - get: (args: GetArgs) => + get: (args: UnauthenticatedResourceRequestArgs) => get({ axiosInstance, logger }, args, getILPStreamConnectionValidator) } } diff --git a/packages/open-payments/src/client/incoming-payment.test.ts b/packages/open-payments/src/client/incoming-payment.test.ts index fc46af977a..90acd2fb42 100644 --- a/packages/open-payments/src/client/incoming-payment.test.ts +++ b/packages/open-payments/src/client/incoming-payment.test.ts @@ -42,7 +42,8 @@ describe('incoming-payment', (): void => { const axiosInstance = defaultAxiosInstance const logger = silentLogger - const paymentPointer = `http://localhost:1000/.well-known/pay` + const paymentPointer = 'http://localhost:1000/.well-known/pay' + const accessToken = 'accessToken' const openApiValidators = mockOpenApiResponseValidators() describe('getIncomingPayment', (): void => { @@ -57,7 +58,7 @@ describe('incoming-payment', (): void => { { axiosInstance, logger }, { url: `${paymentPointer}/incoming-payments/1`, - accessToken: 'accessToken' + accessToken }, openApiValidators.successfulValidator ) @@ -82,7 +83,7 @@ describe('incoming-payment', (): void => { .get('/incoming-payments/1') .reply(200, incomingPayment) - await expect(() => + await expect( getIncomingPayment( { axiosInstance, @@ -90,7 +91,7 @@ describe('incoming-payment', (): void => { }, { url: `${paymentPointer}/incoming-payments/1`, - accessToken: 'accessToken' + accessToken }, openApiValidators.successfulValidator ) @@ -104,7 +105,7 @@ describe('incoming-payment', (): void => { .get('/incoming-payments/1') .reply(200, incomingPayment) - await expect(() => + await expect( getIncomingPayment( { axiosInstance, @@ -112,7 +113,7 @@ describe('incoming-payment', (): void => { }, { url: `${paymentPointer}/incoming-payments/1`, - accessToken: 'accessToken' + accessToken }, openApiValidators.failedValidator ) @@ -146,7 +147,7 @@ describe('incoming-payment', (): void => { const result = await createIncomingPayment( { axiosInstance, logger }, - { paymentPointer, accessToken: 'accessToken' }, + { paymentPointer, accessToken }, openApiValidators.successfulValidator, { incomingAmount, @@ -178,10 +179,10 @@ describe('incoming-payment', (): void => { .post('/incoming-payments') .reply(200, incomingPayment) - await expect(() => + await expect( createIncomingPayment( { axiosInstance, logger }, - { paymentPointer, accessToken: 'accessToken' }, + { paymentPointer, accessToken }, openApiValidators.successfulValidator, {} ) @@ -196,10 +197,10 @@ describe('incoming-payment', (): void => { .post('/incoming-payments') .reply(200, incomingPayment) - await expect(() => + await expect( createIncomingPayment( { axiosInstance, logger }, - { paymentPointer, accessToken: 'accessToken' }, + { paymentPointer, accessToken }, openApiValidators.failedValidator, {} ) @@ -222,7 +223,7 @@ describe('incoming-payment', (): void => { { axiosInstance, logger }, { url: `${paymentPointer}/incoming-payments/${incomingPayment.id}`, - accessToken: 'accessToken' + accessToken }, openApiValidators.successfulValidator ) @@ -241,12 +242,12 @@ describe('incoming-payment', (): void => { .post(`/incoming-payments/${incomingPayment.id}/complete`) .reply(200, incomingPayment) - await expect(() => + await expect( completeIncomingPayment( { axiosInstance, logger }, { url: `${paymentPointer}/incoming-payments/${incomingPayment.id}`, - accessToken: 'accessToken' + accessToken }, openApiValidators.successfulValidator ) @@ -264,12 +265,12 @@ describe('incoming-payment', (): void => { .post(`/incoming-payments/${incomingPayment.id}/complete`) .reply(200, incomingPayment) - await expect(() => + await expect( completeIncomingPayment( { axiosInstance, logger }, { url: `${paymentPointer}/incoming-payments/${incomingPayment.id}`, - accessToken: 'accessToken' + accessToken }, openApiValidators.failedValidator ) @@ -309,7 +310,7 @@ describe('incoming-payment', (): void => { }, { paymentPointer, - accessToken: 'accessToken' + accessToken }, openApiValidators.successfulValidator, { @@ -352,7 +353,7 @@ describe('incoming-payment', (): void => { }, { paymentPointer, - accessToken: 'accessToken' + accessToken }, openApiValidators.successfulValidator, { @@ -390,7 +391,7 @@ describe('incoming-payment', (): void => { .get('/incoming-payments') .reply(200, incomingPaymentPaginationResult) - await expect(() => + await expect( listIncomingPayment( { axiosInstance, @@ -398,7 +399,7 @@ describe('incoming-payment', (): void => { }, { paymentPointer, - accessToken: 'accessToken' + accessToken }, openApiValidators.successfulValidator ) @@ -415,10 +416,10 @@ describe('incoming-payment', (): void => { .get('/incoming-payments') .reply(200, incomingPaymentPaginationResult) - await expect(() => + await expect( listIncomingPayment( { axiosInstance, logger }, - { paymentPointer, accessToken: 'accessToken' }, + { paymentPointer, accessToken }, openApiValidators.failedValidator ) ).rejects.toThrowError() @@ -663,14 +664,14 @@ describe('incoming-payment', (): void => { openApi, axiosInstance, logger - }).get({ url, accessToken: 'accessToken' }) + }).get({ url, accessToken }) expect(getSpy).toHaveBeenCalledWith( { axiosInstance, logger }, - { url, accessToken: 'accessToken' }, + { url, accessToken }, true ) }) @@ -700,14 +701,14 @@ describe('incoming-payment', (): void => { openApi, axiosInstance, logger - }).list({ paymentPointer, accessToken: 'accessToken' }) + }).list({ paymentPointer, accessToken }) expect(getSpy).toHaveBeenCalledWith( { axiosInstance, logger }, - { url, accessToken: 'accessToken' }, + { url, accessToken }, true ) }) @@ -737,17 +738,14 @@ describe('incoming-payment', (): void => { openApi, axiosInstance, logger - }).create( - { paymentPointer, accessToken: 'accessToken' }, - incomingPaymentCreateArgs - ) + }).create({ paymentPointer, accessToken }, incomingPaymentCreateArgs) expect(postSpy).toHaveBeenCalledWith( { axiosInstance, logger }, - { url, accessToken: 'accessToken', body: incomingPaymentCreateArgs }, + { url, accessToken, body: incomingPaymentCreateArgs }, true ) }) @@ -774,14 +772,14 @@ describe('incoming-payment', (): void => { openApi, axiosInstance, logger - }).complete({ url: incomingPaymentUrl, accessToken: 'accessToken' }) + }).complete({ url: incomingPaymentUrl, accessToken }) expect(postSpy).toHaveBeenCalledWith( { axiosInstance, logger }, - { url: `${incomingPaymentUrl}/complete`, accessToken: 'accessToken' }, + { url: `${incomingPaymentUrl}/complete`, accessToken }, true ) }) diff --git a/packages/open-payments/src/client/incoming-payment.ts b/packages/open-payments/src/client/incoming-payment.ts index 5d784fe206..25990e269c 100644 --- a/packages/open-payments/src/client/incoming-payment.ts +++ b/packages/open-payments/src/client/incoming-payment.ts @@ -1,5 +1,10 @@ import { HttpMethod, ResponseValidator } from 'openapi' -import { BaseDeps, RouteDeps } from '.' +import { + BaseDeps, + CollectionRequestArgs, + ResourceRequestArgs, + RouteDeps +} from '.' import { IncomingPayment, getRSPath, @@ -9,25 +14,15 @@ import { } from '../types' import { get, post } from './requests' -interface RequestWithUrlArgs { - url: string - accessToken: string -} - -interface RequestWithPaymentPointerArgs { - paymentPointer: string - accessToken: string -} - export interface IncomingPaymentRoutes { - get(args: RequestWithUrlArgs): Promise + get(args: ResourceRequestArgs): Promise create( - args: RequestWithPaymentPointerArgs, + args: CollectionRequestArgs, createArgs: CreateIncomingPaymentArgs ): Promise - complete(args: RequestWithUrlArgs): Promise + complete(args: ResourceRequestArgs): Promise list( - args: RequestWithPaymentPointerArgs, + args: CollectionRequestArgs, pagination?: PaginationArgs ): Promise } @@ -62,14 +57,14 @@ export const createIncomingPaymentRoutes = ( }) return { - get: (args: RequestWithUrlArgs) => + get: (args: ResourceRequestArgs) => getIncomingPayment( { axiosInstance, logger }, args, getIncomingPaymentOpenApiValidator ), create: ( - requestArgs: RequestWithPaymentPointerArgs, + requestArgs: CollectionRequestArgs, createArgs: CreateIncomingPaymentArgs ) => createIncomingPayment( @@ -78,13 +73,13 @@ export const createIncomingPaymentRoutes = ( createIncomingPaymentOpenApiValidator, createArgs ), - complete: (args: RequestWithUrlArgs) => + complete: (args: ResourceRequestArgs) => completeIncomingPayment( { axiosInstance, logger }, args, completeIncomingPaymentOpenApiValidator ), - list: (args: RequestWithPaymentPointerArgs, pagination?: PaginationArgs) => + list: (args: CollectionRequestArgs, pagination?: PaginationArgs) => listIncomingPayment( { axiosInstance, logger }, args, @@ -96,7 +91,7 @@ export const createIncomingPaymentRoutes = ( export const getIncomingPayment = async ( deps: BaseDeps, - args: RequestWithUrlArgs, + args: ResourceRequestArgs, validateOpenApiResponse: ResponseValidator ) => { const { axiosInstance, logger } = deps @@ -120,7 +115,7 @@ export const getIncomingPayment = async ( export const createIncomingPayment = async ( deps: BaseDeps, - requestArgs: RequestWithPaymentPointerArgs, + requestArgs: CollectionRequestArgs, validateOpenApiResponse: ResponseValidator, createArgs: CreateIncomingPaymentArgs ) => { @@ -146,7 +141,7 @@ export const createIncomingPayment = async ( export const completeIncomingPayment = async ( deps: BaseDeps, - args: RequestWithUrlArgs, + args: ResourceRequestArgs, validateOpenApiResponse: ResponseValidator ) => { const { axiosInstance, logger } = deps @@ -171,7 +166,7 @@ export const completeIncomingPayment = async ( export const listIncomingPayment = async ( deps: BaseDeps, - args: RequestWithPaymentPointerArgs, + args: CollectionRequestArgs, validateOpenApiResponse: ResponseValidator, pagination?: PaginationArgs ) => { diff --git a/packages/open-payments/src/client/index.ts b/packages/open-payments/src/client/index.ts index 862bab52d9..df3bf610ea 100644 --- a/packages/open-payments/src/client/index.ts +++ b/packages/open-payments/src/client/index.ts @@ -41,6 +41,20 @@ export interface RouteDeps extends BaseDeps { logger: Logger } +export interface UnauthenticatedResourceRequestArgs { + url: string +} + +export interface ResourceRequestArgs { + url: string + accessToken: string +} + +export interface CollectionRequestArgs { + paymentPointer: string + accessToken: string +} + const createDeps = async ( args: Partial ): Promise => { diff --git a/packages/open-payments/src/client/outgoing-payment.test.ts b/packages/open-payments/src/client/outgoing-payment.test.ts index a18e944de9..6eac1efd2f 100644 --- a/packages/open-payments/src/client/outgoing-payment.test.ts +++ b/packages/open-payments/src/client/outgoing-payment.test.ts @@ -78,7 +78,7 @@ describe('outgoing-payment', (): void => { .get('/outgoing-payments/1') .reply(200, outgoingPayment) - await expect(() => + await expect( getOutgoingPayment( { axiosInstance, logger }, { @@ -98,7 +98,7 @@ describe('outgoing-payment', (): void => { .get('/outgoing-payments/1') .reply(200, outgoingPayment) - await expect(() => + await expect( getOutgoingPayment( { axiosInstance, logger }, { @@ -215,7 +215,7 @@ describe('outgoing-payment', (): void => { .get('/outgoing-payments') .reply(200, outgoingPaymentPaginationResult) - await expect(() => + await expect( listOutgoingPayments( { axiosInstance, @@ -239,7 +239,7 @@ describe('outgoing-payment', (): void => { .get('/outgoing-payments') .reply(200, outgoingPaymentPaginationResult) - await expect(() => + await expect( listOutgoingPayments( { axiosInstance, @@ -312,7 +312,7 @@ describe('outgoing-payment', (): void => { .post('/outgoing-payments') .reply(200, outgoingPayment) - await expect(() => + await expect( createOutgoingPayment( { axiosInstance, logger }, { @@ -335,7 +335,7 @@ describe('outgoing-payment', (): void => { .post('/outgoing-payments') .reply(200, outgoingPayment) - await expect(() => + await expect( createOutgoingPayment( { axiosInstance, diff --git a/packages/open-payments/src/client/outgoing-payment.ts b/packages/open-payments/src/client/outgoing-payment.ts index 83a69dfb8d..023cfdab07 100644 --- a/packages/open-payments/src/client/outgoing-payment.ts +++ b/packages/open-payments/src/client/outgoing-payment.ts @@ -1,5 +1,10 @@ import { HttpMethod, ResponseValidator } from 'openapi' -import { BaseDeps, RouteDeps } from '.' +import { + BaseDeps, + CollectionRequestArgs, + ResourceRequestArgs, + RouteDeps +} from '.' import { CreateOutgoingPaymentArgs, getRSPath, @@ -9,24 +14,14 @@ import { } from '../types' import { get, post } from './requests' -interface RequestWithUrlArgs { - url: string - accessToken: string -} - -interface RequestWithPaymentPointerArgs { - paymentPointer: string - accessToken: string -} - export interface OutgoingPaymentRoutes { - get(args: RequestWithUrlArgs): Promise + get(args: ResourceRequestArgs): Promise list( - args: RequestWithPaymentPointerArgs, + args: CollectionRequestArgs, pagination?: PaginationArgs ): Promise create( - requestArgs: RequestWithPaymentPointerArgs, + requestArgs: CollectionRequestArgs, createArgs: CreateOutgoingPaymentArgs ): Promise } @@ -55,16 +50,13 @@ export const createOutgoingPaymentRoutes = ( }) return { - get: (requestArgs: RequestWithUrlArgs) => + get: (requestArgs: ResourceRequestArgs) => getOutgoingPayment( { axiosInstance, logger }, requestArgs, getOutgoingPaymentOpenApiValidator ), - list: ( - requestArgs: RequestWithPaymentPointerArgs, - pagination?: PaginationArgs - ) => + list: (requestArgs: CollectionRequestArgs, pagination?: PaginationArgs) => listOutgoingPayments( { axiosInstance, logger }, requestArgs, @@ -72,7 +64,7 @@ export const createOutgoingPaymentRoutes = ( pagination ), create: ( - requestArgs: RequestWithPaymentPointerArgs, + requestArgs: CollectionRequestArgs, createArgs: CreateOutgoingPaymentArgs ) => createOutgoingPayment( @@ -86,7 +78,7 @@ export const createOutgoingPaymentRoutes = ( export const getOutgoingPayment = async ( deps: BaseDeps, - requestArgs: RequestWithUrlArgs, + requestArgs: ResourceRequestArgs, validateOpenApiResponse: ResponseValidator ) => { const { axiosInstance, logger } = deps @@ -110,7 +102,7 @@ export const getOutgoingPayment = async ( export const createOutgoingPayment = async ( deps: BaseDeps, - requestArgs: RequestWithPaymentPointerArgs, + requestArgs: CollectionRequestArgs, validateOpenApiResponse: ResponseValidator, createArgs: CreateOutgoingPaymentArgs ) => { @@ -136,7 +128,7 @@ export const createOutgoingPayment = async ( export const listOutgoingPayments = async ( deps: BaseDeps, - requestArgs: RequestWithPaymentPointerArgs, + requestArgs: CollectionRequestArgs, validateOpenApiResponse: ResponseValidator, pagination?: PaginationArgs ) => { diff --git a/packages/open-payments/src/client/payment-pointer.ts b/packages/open-payments/src/client/payment-pointer.ts index c905d08d2e..d4644e8b1f 100644 --- a/packages/open-payments/src/client/payment-pointer.ts +++ b/packages/open-payments/src/client/payment-pointer.ts @@ -1,15 +1,11 @@ import { HttpMethod } from 'openapi' -import { RouteDeps } from '.' +import { RouteDeps, UnauthenticatedResourceRequestArgs } from '.' import { JWKS, PaymentPointer, getRSPath } from '../types' import { get } from './requests' -interface GetArgs { - url: string -} - export interface PaymentPointerRoutes { - get(args: GetArgs): Promise - getKeys(args: GetArgs): Promise + get(args: UnauthenticatedResourceRequestArgs): Promise + getKeys(args: UnauthenticatedResourceRequestArgs): Promise } export const createPaymentPointerRoutes = ( @@ -29,9 +25,9 @@ export const createPaymentPointerRoutes = ( }) return { - get: (args: GetArgs) => + get: (args: UnauthenticatedResourceRequestArgs) => get({ axiosInstance, logger }, args, getPaymentPaymentValidator), - getKeys: (args: GetArgs) => + getKeys: (args: UnauthenticatedResourceRequestArgs) => get( { axiosInstance, logger }, { diff --git a/packages/open-payments/src/client/quote.ts b/packages/open-payments/src/client/quote.ts index faead27027..3ccd2e34bf 100644 --- a/packages/open-payments/src/client/quote.ts +++ b/packages/open-payments/src/client/quote.ts @@ -1,14 +1,10 @@ import { HttpMethod } from 'openapi' -import { RouteDeps } from '.' +import { ResourceRequestArgs, RouteDeps } from '.' import { getRSPath, Quote } from '../types' import { get } from './requests' -interface GetArgs { - url: string -} - export interface QuoteRoutes { - get(args: GetArgs): Promise + get(args: ResourceRequestArgs): Promise } export const createQuoteRoutes = (deps: RouteDeps): QuoteRoutes => { @@ -20,7 +16,7 @@ export const createQuoteRoutes = (deps: RouteDeps): QuoteRoutes => { }) return { - get: (args: GetArgs) => + get: (args: ResourceRequestArgs) => get({ axiosInstance, logger }, args, getQuoteValidator) } } diff --git a/packages/open-payments/src/client/token.ts b/packages/open-payments/src/client/token.ts index 4e4164246d..a1bc84b79a 100644 --- a/packages/open-payments/src/client/token.ts +++ b/packages/open-payments/src/client/token.ts @@ -1,21 +1,16 @@ import { HttpMethod, ResponseValidator } from 'openapi' -import { RouteDeps } from '.' +import { ResourceRequestArgs, RouteDeps } from '.' import { getASPath, AccessToken } from '../types' import { deleteRequest, post } from './requests' -interface TokenRequestArgs { - url: string - accessToken: string -} - export interface TokenRoutes { - rotate(args: TokenRequestArgs): Promise - revoke(args: TokenRequestArgs): Promise + rotate(args: ResourceRequestArgs): Promise + revoke(args: ResourceRequestArgs): Promise } export const rotateToken = async ( deps: RouteDeps, - args: TokenRequestArgs, + args: ResourceRequestArgs, validateOpenApiResponse: ResponseValidator ) => { const { axiosInstance, logger } = deps @@ -36,7 +31,7 @@ export const rotateToken = async ( export const revokeToken = async ( deps: RouteDeps, - args: TokenRequestArgs, + args: ResourceRequestArgs, validateOpenApiResponse: ResponseValidator ) => { const { axiosInstance, logger } = deps @@ -68,9 +63,9 @@ export const createTokenRoutes = (deps: RouteDeps): TokenRoutes => { }) return { - rotate: (args: TokenRequestArgs) => + rotate: (args: ResourceRequestArgs) => rotateToken(deps, args, rotateTokenValidator), - revoke: (args: TokenRequestArgs) => + revoke: (args: ResourceRequestArgs) => revokeToken(deps, args, revokeTokenValidator) } } diff --git a/packages/open-payments/src/test/helpers.ts b/packages/open-payments/src/test/helpers.ts index a24d4ed342..f65559b6b2 100644 --- a/packages/open-payments/src/test/helpers.ts +++ b/packages/open-payments/src/test/helpers.ts @@ -42,9 +42,11 @@ export const withEnvVariableOverride = ( process.env[key] = override[key] } - await testCallback() - - process.env = savedEnvVars + try { + await testCallback() + } finally { + process.env = savedEnvVars + } } } @@ -71,7 +73,6 @@ export const mockPaymentPointer = ( overrides?: Partial ): PaymentPointer => ({ id: 'https://example.com/.well-known/pay', - publicName: 'Payment Pointer', authServer: 'https://auth.wallet.example/authorize', assetScale: 2, assetCode: 'USD', From 581a5e4d5b0f337f6904d81cd4d4a2947202105b Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Thu, 12 Jan 2023 17:40:36 -0700 Subject: [PATCH 7/9] chore(open-payments): use types suggestion --- packages/open-payments/src/client/index.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/open-payments/src/client/index.ts b/packages/open-payments/src/client/index.ts index df3bf610ea..7f71c0ad3b 100644 --- a/packages/open-payments/src/client/index.ts +++ b/packages/open-payments/src/client/index.ts @@ -45,14 +45,15 @@ export interface UnauthenticatedResourceRequestArgs { url: string } -export interface ResourceRequestArgs { - url: string +interface AuthenticatedRequestArgs { accessToken: string } +export interface ResourceRequestArgs + extends UnauthenticatedResourceRequestArgs, + AuthenticatedRequestArgs {} -export interface CollectionRequestArgs { +export interface CollectionRequestArgs extends AuthenticatedRequestArgs { paymentPointer: string - accessToken: string } const createDeps = async ( From 6490fff0f006b7af267629a4e95264d36cb593f0 Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Fri, 13 Jan 2023 13:57:03 -0700 Subject: [PATCH 8/9] chore(open-payments): remove extraneous tests --- .../open-payments/src/client/quote.test.ts | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/packages/open-payments/src/client/quote.test.ts b/packages/open-payments/src/client/quote.test.ts index b652460e52..cf933b5e62 100644 --- a/packages/open-payments/src/client/quote.test.ts +++ b/packages/open-payments/src/client/quote.test.ts @@ -35,29 +35,6 @@ describe('quote', (): void => { const paymentPointer = 'http://localhost:1000/.well-known/pay' const accessToken = 'accessToken' - describe('createQuoteRoutes', (): void => { - test('creates getQuoteOpenApiValidator properly', async (): Promise => { - jest.spyOn(openApi, 'createResponseValidator') - - createQuoteRoutes({ axiosInstance, openApi, logger }) - expect(openApi.createResponseValidator).toHaveBeenCalledWith({ - path: '/quotes/{id}', - method: HttpMethod.GET - }) - }) - - test('creates createQuoteOpenApiValidator properly', async (): Promise => { - jest.spyOn(openApi, 'createResponseValidator') - - createQuoteRoutes({ openApi, axiosInstance, logger }) - - expect(openApi.createResponseValidator).toHaveBeenCalledWith({ - path: '/quotes', - method: HttpMethod.POST - }) - }) - }) - describe('getQuote', (): void => { test('returns the quote if it passes open api validation', async (): Promise => { const scope = nock(baseUrl).get(`/quotes/${quote.id}`).reply(200, quote) From d42bc5b59300bd6aefba1a08419b9c975dc68505 Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Fri, 13 Jan 2023 14:03:57 -0700 Subject: [PATCH 9/9] chore(open-payments): address object copy suggestion --- packages/open-payments/src/test/helpers.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/open-payments/src/test/helpers.ts b/packages/open-payments/src/test/helpers.ts index cfbdcdd12e..29e7d01aed 100644 --- a/packages/open-payments/src/test/helpers.ts +++ b/packages/open-payments/src/test/helpers.ts @@ -39,9 +39,7 @@ export const withEnvVariableOverride = ( return async () => { const savedEnvVars = Object.assign({}, process.env) - for (const key in override) { - process.env[key] = override[key] - } + Object.assign(process.env, override) try { await testCallback()