diff --git a/.changeset/fair-spiders-eat.md b/.changeset/fair-spiders-eat.md new file mode 100644 index 00000000..651174b9 --- /dev/null +++ b/.changeset/fair-spiders-eat.md @@ -0,0 +1,5 @@ +--- +"@tbdex/http-server": patch +--- + +Fixed bugs in tests involving calledWith() and increased code coverage. diff --git a/packages/http-server/package.json b/packages/http-server/package.json index dcc43089..91f5a41f 100644 --- a/packages/http-server/package.json +++ b/packages/http-server/package.json @@ -39,9 +39,12 @@ "@types/mocha": "10.0.1", "@types/node": "20.9.4", "@types/sinon": "^17.0.3", + "@types/sinon-chai": "^3.2.12", "chai": "4.3.10", + "query-string": "8.2.0", "rimraf": "5.0.1", "sinon": "17.0.1", + "sinon-chai": "3.7.0", "supertest": "6.3.3", "typescript": "5.2.2" }, diff --git a/packages/http-server/tests/create-exchange.spec.ts b/packages/http-server/tests/create-exchange.spec.ts index 19c4576f..49388515 100644 --- a/packages/http-server/tests/create-exchange.spec.ts +++ b/packages/http-server/tests/create-exchange.spec.ts @@ -1,7 +1,7 @@ import { ErrorDetail, Offering, Order, Rfq } from '@tbdex/http-client' import type { Server } from 'http' -import { DevTools, RequestContext, TbdexHttpServer } from '../src/main.js' +import { CallbackError, DevTools, RequestContext, TbdexHttpServer } from '../src/main.js' import { expect } from 'chai' import { InMemoryExchangesApi } from '../src/in-memory-exchanges-api.js' import { InMemoryOfferingsApi } from '../src/in-memory-offerings-api.js' @@ -342,6 +342,44 @@ describe('POST /exchanges/:exchangeId/rfq', () => { expect(lastCallbackArg.replyTo).to.be.undefined }) + it('propagates the status code and detail if custom handler throws a custom error', async () => { + const customErrorStatus = 456 + const customErrorDetail: ErrorDetail[] = [{ detail: 'custom-error-detail' }] + const callbackSpy = Sinon.spy( + (_ctx: RequestContext, _message: Rfq, _opts: { offering: Offering, replyTo?: string }) => { + return Promise.reject(new CallbackError(customErrorStatus, customErrorDetail)) + }) + api.onCreateExchange(callbackSpy) + + const response = await fetch('http://localhost:8000/exchanges', { + method : 'POST', + body : JSON.stringify({ rfq }) + }) + + expect(response.status).to.equal(customErrorStatus) + + const responseBody = await response.json() as { errors: ErrorDetail[] } + expect(responseBody.errors).to.deep.equal(customErrorDetail) + }) + + it('returns a 500 Internal Server Error if custom handler throws a generic unexpected error', async () => { + const callbackSpy = Sinon.spy( + (_ctx: RequestContext, _message: Rfq, _opts: { offering: Offering, replyTo?: string }) => { + return Promise.reject(new Error('generic unexpected error')) + }) + api.onCreateExchange(callbackSpy) + + const response = await fetch('http://localhost:8000/exchanges', { + method : 'POST', + body : JSON.stringify({ rfq }) + }) + + expect(response.status).to.equal(500) + + const responseBody = await response.json() as { errors: ErrorDetail[] } + expect(responseBody.errors).to.deep.equal([{ detail: 'Internal Server Error' }]) + }) + it('passes replyTo to the callback if it is provided in the request', async () => { const callbackSpy = Sinon.spy( (_ctx: RequestContext, _message: Rfq, _opts: { offering: Offering, replyTo?: string }) =>{ diff --git a/packages/http-server/tests/get-exchange.spec.ts b/packages/http-server/tests/get-exchange.spec.ts index cf832d35..272ba299 100644 --- a/packages/http-server/tests/get-exchange.spec.ts +++ b/packages/http-server/tests/get-exchange.spec.ts @@ -1,5 +1,7 @@ import type { Server } from 'http' import Sinon, * as sinon from 'sinon' +import sinonChai from 'sinon-chai' +import chai from 'chai' import { TbdexHttpServer, RequestContext } from '../src/main.js' import { BearerDid, DidDht, DidJwk } from '@web5/dids' @@ -7,6 +9,8 @@ import { expect } from 'chai' import { InMemoryExchangesApi } from '../src/in-memory-exchanges-api.js' import { DevTools, ErrorDetail, TbdexHttpClient } from '@tbdex/http-client' +chai.use(sinonChai) + describe('GET /exchanges', () => { let server: Server let api: TbdexHttpServer @@ -112,9 +116,9 @@ describe('GET /exchanges', () => { expect(resp.ok).to.be.true expect(exchangesApiSpy.calledOnce).to.be.true - expect(exchangesApiSpy.calledWith({ + expect(exchangesApiSpy).to.have.been.calledWith({ id: exchangeId, - })).to.be.true + }) exchangesApiSpy.restore() }) diff --git a/packages/http-server/tests/get-exchanges.spec.ts b/packages/http-server/tests/get-exchanges.spec.ts index 4d54f96a..a6f9359b 100644 --- a/packages/http-server/tests/get-exchanges.spec.ts +++ b/packages/http-server/tests/get-exchanges.spec.ts @@ -1,5 +1,8 @@ import type { Server } from 'http' import Sinon, * as sinon from 'sinon' +import queryString from 'query-string' +import sinonChai from 'sinon-chai' +import chai from 'chai' import { TbdexHttpServer, RequestContext, GetExchangesFilter } from '../src/main.js' import { DidJwk } from '@web5/dids' @@ -7,6 +10,8 @@ import { expect } from 'chai' import { InMemoryExchangesApi } from '../src/in-memory-exchanges-api.js' import { DevTools, ErrorDetail, TbdexHttpClient } from '@tbdex/http-client' +chai.use(sinonChai) + describe('GET /exchanges', () => { let server: Server let api: TbdexHttpServer @@ -74,11 +79,11 @@ describe('GET /exchanges', () => { expect(resp.ok).to.be.true expect(exchangesApiSpy.calledOnce).to.be.true - expect(exchangesApiSpy.calledWith({ + expect(exchangesApiSpy).to.have.been.calledWith({ filter: { from: alice.uri } - })).to.be.true + }) exchangesApiSpy.restore() }) @@ -100,39 +105,38 @@ describe('GET /exchanges', () => { expect(resp.ok).to.be.true expect(exchangesApiSpy.calledOnce).to.be.true - expect(exchangesApiSpy.calledWith({ + expect(exchangesApiSpy).to.have.been.calledWith({ filter: { from : alice.uri, id : [idQueryParam] } - })) + }) exchangesApiSpy.restore() }) - it('passes the id array query param as an array to the filter of ExchangesApi.getExchanges', async () => { + it.only('passes the id array query param as an array to the filter of ExchangesApi.getExchanges', async () => { const alice = await DidJwk.create() const exchangesApiSpy = sinon.spy(api.exchangesApi, 'getExchanges') const requestToken = await TbdexHttpClient.generateRequestToken({ requesterDid: alice, pfiDid: api.pfiDid }) - // `id` query param contains an array - const idQueryParam = ['1234', '5678'] - const resp = await fetch(`http://localhost:8000/exchanges?id=[${idQueryParam.join(',')}]`, { + const queryParams = { id: ['1234', '5678'] } + const queryParamsString = queryString.stringify(queryParams) + const resp = await fetch(`http://localhost:8000/exchanges?${queryParamsString}`, { headers: { 'Authorization': `Bearer ${requestToken}` } }) expect(resp.ok).to.be.true - expect(exchangesApiSpy.calledOnce).to.be.true - expect(exchangesApiSpy.calledWith({ + expect(exchangesApiSpy).to.have.been.calledWith({ filter: { - from : alice.uri, - id : idQueryParam + from: alice.uri, + ...queryParams } - })) + }) exchangesApiSpy.restore() }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5650e395..54e2a44c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -193,15 +193,24 @@ importers: '@types/sinon': specifier: ^17.0.3 version: 17.0.3 + '@types/sinon-chai': + specifier: ^3.2.12 + version: 3.2.12 chai: specifier: 4.3.10 version: 4.3.10 + query-string: + specifier: 8.2.0 + version: 8.2.0 rimraf: specifier: 5.0.1 version: 5.0.1 sinon: specifier: 17.0.1 version: 17.0.1 + sinon-chai: + specifier: 3.7.0 + version: 3.7.0(chai@4.3.10)(sinon@17.0.1) supertest: specifier: 6.3.3 version: 6.3.3 @@ -1448,6 +1457,13 @@ packages: '@types/node': 20.9.4 dev: true + /@types/sinon-chai@3.2.12: + resolution: {integrity: sha512-9y0Gflk3b0+NhQZ/oxGtaAJDvRywCa5sIyaVnounqLvmf93yBF4EgIRspePtkMs3Tr844nCclYMlcCNmLCvjuQ==} + dependencies: + '@types/chai': 4.3.6 + '@types/sinon': 17.0.3 + dev: true + /@types/sinon@17.0.1: resolution: {integrity: sha512-Q2Go6TJetYn5Za1+RJA1Aik61Oa2FS8SuJ0juIqUuJ5dZR4wvhKfmSdIqWtQ3P6gljKWjW0/R7FZkA4oXVL6OA==} dependencies: @@ -3076,7 +3092,6 @@ packages: /decode-uri-component@0.4.1: resolution: {integrity: sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==} engines: {node: '>=14.16'} - dev: false /deep-eql@4.1.3: resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} @@ -3754,7 +3769,6 @@ packages: /filter-obj@5.1.0: resolution: {integrity: sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==} engines: {node: '>=14.16'} - dev: false /finalhandler@1.2.0: resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} @@ -5911,7 +5925,6 @@ packages: decode-uri-component: 0.4.1 filter-obj: 5.1.0 split-on-first: 3.0.0 - dev: false /querystring-es3@0.2.1: resolution: {integrity: sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==} @@ -6359,6 +6372,16 @@ packages: engines: {node: '>=14'} dev: true + /sinon-chai@3.7.0(chai@4.3.10)(sinon@17.0.1): + resolution: {integrity: sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g==} + peerDependencies: + chai: ^4.0.0 + sinon: '>=4.0.0' + dependencies: + chai: 4.3.10 + sinon: 17.0.1 + dev: true + /sinon@17.0.1: resolution: {integrity: sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==} dependencies: @@ -6462,7 +6485,6 @@ packages: /split-on-first@3.0.0: resolution: {integrity: sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==} engines: {node: '>=12'} - dev: false /sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}