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.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/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 708390209d..90acd2fb42 100644 --- a/packages/open-payments/src/client/incoming-payment.test.ts +++ b/packages/open-payments/src/client/incoming-payment.test.ts @@ -42,84 +42,23 @@ describe('incoming-payment', (): void => { const axiosInstance = defaultAxiosInstance const logger = silentLogger - const baseUrl = 'http://localhost:1000' + const paymentPointer = 'http://localhost:1000/.well-known/pay' + const accessToken = 'accessToken' 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`, - accessToken: 'accessToken' + url: `${paymentPointer}/incoming-payments/1`, + accessToken }, openApiValidators.successfulValidator ) @@ -140,17 +79,19 @@ describe('incoming-payment', (): void => { } }) - nock(baseUrl).get('/incoming-payments').reply(200, incomingPayment) + nock(paymentPointer) + .get('/incoming-payments/1') + .reply(200, incomingPayment) - await expect(() => + await expect( getIncomingPayment( { axiosInstance, logger }, { - url: `${baseUrl}/incoming-payments`, - accessToken: 'accessToken' + url: `${paymentPointer}/incoming-payments/1`, + accessToken }, openApiValidators.successfulValidator ) @@ -160,17 +101,19 @@ 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(() => + await expect( getIncomingPayment( { axiosInstance, logger }, { - url: `${baseUrl}/incoming-payments`, - accessToken: 'accessToken' + url: `${paymentPointer}/incoming-payments/1`, + accessToken }, openApiValidators.failedValidator ) @@ -198,26 +141,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 }, + 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 +175,16 @@ describe('incoming-payment', (): void => { completed: false }) - const scope = nock(baseUrl) + const scope = nock(paymentPointer) .post('/incoming-payments') .reply(200, incomingPayment) - await expect(() => + await expect( createIncomingPayment( { axiosInstance, logger }, - { - url: `${baseUrl}/incoming-payments`, - body: {}, - accessToken: 'accessToken' - }, - openApiValidators.successfulValidator + { paymentPointer, accessToken }, + openApiValidators.successfulValidator, + {} ) ).rejects.toThrowError() scope.done() @@ -259,22 +193,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(() => + await expect( createIncomingPayment( - { - axiosInstance, - logger - }, - { - url: `${baseUrl}/incoming-payments`, - body: {}, - accessToken: 'accessToken' - }, - openApiValidators.failedValidator + { axiosInstance, logger }, + { paymentPointer, accessToken }, + openApiValidators.failedValidator, + {} ) ).rejects.toThrowError() scope.done() @@ -287,15 +215,15 @@ 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`, - accessToken: 'accessToken' + url: `${paymentPointer}/incoming-payments/${incomingPayment.id}`, + accessToken }, openApiValidators.successfulValidator ) @@ -310,16 +238,16 @@ describe('incoming-payment', (): void => { completed: false }) - const scope = nock(baseUrl) + const scope = nock(paymentPointer) .post(`/incoming-payments/${incomingPayment.id}/complete`) .reply(200, incomingPayment) - await expect(() => + await expect( completeIncomingPayment( { axiosInstance, logger }, { - url: `${baseUrl}/incoming-payments/${incomingPayment.id}/complete`, - accessToken: 'accessToken' + url: `${paymentPointer}/incoming-payments/${incomingPayment.id}`, + accessToken }, openApiValidators.successfulValidator ) @@ -333,16 +261,16 @@ describe('incoming-payment', (): void => { completed: true }) - const scope = nock(baseUrl) + const scope = nock(paymentPointer) .post(`/incoming-payments/${incomingPayment.id}/complete`) .reply(200, incomingPayment) - await expect(() => + await expect( completeIncomingPayment( { axiosInstance, logger }, { - url: `${baseUrl}/incoming-payments/${incomingPayment.id}/complete`, - accessToken: 'accessToken' + url: `${paymentPointer}/incoming-payments/${incomingPayment.id}`, + accessToken }, openApiValidators.failedValidator ) @@ -353,8 +281,6 @@ describe('incoming-payment', (): void => { }) describe('listIncomingPayment', (): void => { - const paymentPointer = `${baseUrl}/.well-known/pay` - describe('forward pagination', (): void => { test.each` first | cursor @@ -384,7 +310,7 @@ describe('incoming-payment', (): void => { }, { paymentPointer, - accessToken: 'accessToken' + accessToken }, openApiValidators.successfulValidator, { @@ -427,7 +353,7 @@ describe('incoming-payment', (): void => { }, { paymentPointer, - accessToken: 'accessToken' + accessToken }, openApiValidators.successfulValidator, { @@ -465,7 +391,7 @@ describe('incoming-payment', (): void => { .get('/incoming-payments') .reply(200, incomingPaymentPaginationResult) - await expect(() => + await expect( listIncomingPayment( { axiosInstance, @@ -473,7 +399,7 @@ describe('incoming-payment', (): void => { }, { paymentPointer, - accessToken: 'accessToken' + accessToken }, openApiValidators.successfulValidator ) @@ -490,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() @@ -718,6 +644,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 }) + + expect(getSpy).toHaveBeenCalledWith( + { + axiosInstance, + logger + }, + { url, accessToken }, + true + ) + }) + }) + describe('list', (): void => { test('calls get method with correct validator', async (): Promise => { const mockResponseValidator = ({ path, method }) => @@ -727,7 +686,6 @@ describe('incoming-payment', (): void => { mockIncomingPaymentPaginationResult({ result: [mockIncomingPayment()] }) - const paymentPointer = `${baseUrl}/.well-known/pay` const url = `${paymentPointer}${getRSPath('/incoming-payments')}` jest @@ -743,14 +701,85 @@ 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 + ) + }) + }) + + 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 }, incomingPaymentCreateArgs) + + expect(postSpy).toHaveBeenCalledWith( + { + axiosInstance, + logger + }, + { url, 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 }) + + expect(postSpy).toHaveBeenCalledWith( + { + axiosInstance, + logger + }, + { 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 afe092ed52..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,28 +14,15 @@ import { } from '../types' import { get, post } from './requests' -interface GetArgs { - url: string - accessToken: string -} - -interface PostArgs { - url: string - body?: T - accessToken: string -} - -interface ListGetArgs { - paymentPointer: string - accessToken: string -} - export interface IncomingPaymentRoutes { - get(args: GetArgs): Promise - create(args: PostArgs): Promise - complete(args: PostArgs): Promise + get(args: ResourceRequestArgs): Promise + create( + args: CollectionRequestArgs, + createArgs: CreateIncomingPaymentArgs + ): Promise + complete(args: ResourceRequestArgs): Promise list( - args: ListGetArgs, + args: CollectionRequestArgs, pagination?: PaginationArgs ): Promise } @@ -65,25 +57,29 @@ export const createIncomingPaymentRoutes = ( }) return { - get: (args: GetArgs) => + get: (args: ResourceRequestArgs) => getIncomingPayment( { axiosInstance, logger }, args, getIncomingPaymentOpenApiValidator ), - create: (args: PostArgs) => + create: ( + requestArgs: CollectionRequestArgs, + createArgs: CreateIncomingPaymentArgs + ) => createIncomingPayment( { axiosInstance, logger }, - args, - createIncomingPaymentOpenApiValidator + requestArgs, + createIncomingPaymentOpenApiValidator, + createArgs ), - complete: (args: PostArgs) => + complete: (args: ResourceRequestArgs) => completeIncomingPayment( { axiosInstance, logger }, args, completeIncomingPaymentOpenApiValidator ), - list: (args: ListGetArgs, pagination?: PaginationArgs) => + list: (args: CollectionRequestArgs, pagination?: PaginationArgs) => listIncomingPayment( { axiosInstance, logger }, args, @@ -95,7 +91,7 @@ export const createIncomingPaymentRoutes = ( export const getIncomingPayment = async ( deps: BaseDeps, - args: GetArgs, + args: ResourceRequestArgs, validateOpenApiResponse: ResponseValidator ) => { const { axiosInstance, logger } = deps @@ -119,15 +115,17 @@ export const getIncomingPayment = async ( export const createIncomingPayment = async ( deps: BaseDeps, - args: PostArgs, - validateOpenApiResponse: ResponseValidator + requestArgs: CollectionRequestArgs, + 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 +141,16 @@ export const createIncomingPayment = async ( export const completeIncomingPayment = async ( deps: BaseDeps, - args: PostArgs, + args: ResourceRequestArgs, 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 +166,7 @@ export const completeIncomingPayment = async ( export const listIncomingPayment = async ( deps: BaseDeps, - args: ListGetArgs, + 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..7f71c0ad3b 100644 --- a/packages/open-payments/src/client/index.ts +++ b/packages/open-payments/src/client/index.ts @@ -41,6 +41,21 @@ export interface RouteDeps extends BaseDeps { logger: Logger } +export interface UnauthenticatedResourceRequestArgs { + url: string +} + +interface AuthenticatedRequestArgs { + accessToken: string +} +export interface ResourceRequestArgs + extends UnauthenticatedResourceRequestArgs, + AuthenticatedRequestArgs {} + +export interface CollectionRequestArgs extends AuthenticatedRequestArgs { + paymentPointer: 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 98e956dce4..6eac1efd2f 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(() => + 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(() => + 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' @@ -255,7 +215,7 @@ describe('outgoing-payment', (): void => { .get('/outgoing-payments') .reply(200, outgoingPaymentPaginationResult) - await expect(() => + await expect( listOutgoingPayments( { axiosInstance, @@ -279,7 +239,7 @@ describe('outgoing-payment', (): void => { .get('/outgoing-payments') .reply(200, outgoingPaymentPaginationResult) - await expect(() => + await expect( listOutgoingPayments( { axiosInstance, @@ -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(() => + 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,24 +331,24 @@ 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) - await expect(() => + await expect( createOutgoingPayment( { axiosInstance, 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..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,29 +14,16 @@ import { } from '../types' import { get, post } from './requests' -interface GetArgs { - url: string - accessToken: string -} - -interface ListGetArgs { - paymentPointer: string - accessToken: string -} - -interface PostArgs { - url: string - body: T - accessToken: string -} - export interface OutgoingPaymentRoutes { - get(args: GetArgs): Promise + get(args: ResourceRequestArgs): Promise list( - args: ListGetArgs, + args: CollectionRequestArgs, pagination?: PaginationArgs ): Promise - create(args: PostArgs): Promise + create( + requestArgs: CollectionRequestArgs, + createArgs: CreateOutgoingPaymentArgs + ): Promise } export const createOutgoingPaymentRoutes = ( @@ -58,35 +50,39 @@ export const createOutgoingPaymentRoutes = ( }) return { - get: (args: GetArgs) => + get: (requestArgs: ResourceRequestArgs) => getOutgoingPayment( { axiosInstance, logger }, - args, + requestArgs, getOutgoingPaymentOpenApiValidator ), - list: (getArgs: ListGetArgs, pagination?: PaginationArgs) => + list: (requestArgs: CollectionRequestArgs, pagination?: PaginationArgs) => listOutgoingPayments( { axiosInstance, logger }, - getArgs, + requestArgs, listOutgoingPaymentOpenApiValidator, pagination ), - create: (args: PostArgs) => + create: ( + requestArgs: CollectionRequestArgs, + createArgs: CreateOutgoingPaymentArgs + ) => createOutgoingPayment( { axiosInstance, logger }, - args, - createOutgoingPaymentOpenApiValidator + requestArgs, + createOutgoingPaymentOpenApiValidator, + createArgs ) } } export const getOutgoingPayment = async ( deps: BaseDeps, - args: GetArgs, + requestArgs: ResourceRequestArgs, validateOpenApiResponse: ResponseValidator ) => { const { axiosInstance, logger } = deps - const { url, accessToken } = args + const { url, accessToken } = requestArgs const outgoingPayment = await get( { axiosInstance, logger }, @@ -106,15 +102,17 @@ export const getOutgoingPayment = async ( export const createOutgoingPayment = async ( deps: BaseDeps, - args: PostArgs, - validateOpenApiResponse: ResponseValidator + requestArgs: CollectionRequestArgs, + 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 +128,12 @@ export const createOutgoingPayment = async ( export const listOutgoingPayments = async ( deps: BaseDeps, - getArgs: ListGetArgs, + requestArgs: CollectionRequestArgs, 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( 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 + ) }) }) }) 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.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) diff --git a/packages/open-payments/src/client/quote.ts b/packages/open-payments/src/client/quote.ts index 17eed9a28e..8eb565221a 100644 --- a/packages/open-payments/src/client/quote.ts +++ b/packages/open-payments/src/client/quote.ts @@ -1,22 +1,17 @@ import { HttpMethod, ResponseValidator } from 'openapi' -import { BaseDeps, RouteDeps } from '.' +import { + ResourceRequestArgs, + CollectionRequestArgs, + BaseDeps, + RouteDeps +} from '.' import { CreateQuoteArgs, getRSPath, Quote } from '../types' import { get, post } from './requests' -interface GetArgs { - url: string - accessToken: string -} - -interface CreateArgs { - paymentPointer: string - accessToken: string -} - export interface QuoteRoutes { - get(args: GetArgs): Promise + get(args: ResourceRequestArgs): Promise create( - createArgs: CreateArgs, + createArgs: CollectionRequestArgs, createQuoteArgs: CreateQuoteArgs ): Promise } @@ -35,9 +30,12 @@ export const createQuoteRoutes = (deps: RouteDeps): QuoteRoutes => { }) return { - get: (args: GetArgs) => + get: (args: ResourceRequestArgs) => getQuote({ axiosInstance, logger }, args, getQuoteOpenApiValidator), - create: (createArgs: CreateArgs, createQuoteArgs: CreateQuoteArgs) => + create: ( + createArgs: CollectionRequestArgs, + createQuoteArgs: CreateQuoteArgs + ) => createQuote( { axiosInstance, logger }, createArgs, @@ -49,7 +47,7 @@ export const createQuoteRoutes = (deps: RouteDeps): QuoteRoutes => { export const getQuote = async ( deps: BaseDeps, - args: GetArgs, + args: ResourceRequestArgs, validateOpenApiResponse: ResponseValidator ) => { const { axiosInstance, logger } = deps @@ -65,7 +63,7 @@ export const getQuote = async ( export const createQuote = async ( deps: BaseDeps, - createArgs: CreateArgs, + createArgs: CollectionRequestArgs, validateOpenApiResponse: ResponseValidator, createQuoteArgs: CreateQuoteArgs ) => { 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/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 8531cb3f51..29e7d01aed 100644 --- a/packages/open-payments/src/test/helpers.ts +++ b/packages/open-payments/src/test/helpers.ts @@ -10,6 +10,8 @@ import { NonInteractiveGrant, OutgoingPayment, OutgoingPaymentPaginationResult, + PaymentPointer, + JWK, AccessToken, Quote, IncomingPaymentPaginationResult @@ -30,6 +32,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) + + Object.assign(process.env, override) + + try { + await testCallback() + } finally { + process.env = savedEnvVars + } + } +} + export const mockOpenApiResponseValidators = () => ({ successfulValidator: ((data: unknown): data is unknown => // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -40,6 +59,25 @@ 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', + authServer: 'https://auth.wallet.example/authorize', + assetScale: 2, + assetCode: 'USD', + ...overrides +}) + export const mockILPStreamConnection = ( overrides?: Partial ): ILPStreamConnection => ({