From 70fc645de895f73c3507449b96ac656ab15ef042 Mon Sep 17 00:00:00 2001 From: Alan Cruikshanks Date: Sat, 4 Mar 2023 10:38:59 +0000 Subject: [PATCH] Implement patch but it is broken! That is a little harsh. It does work, but it has now retry support from Got unlike the other types of requests. So, our retry tests fail. Good to know they are working as expected! --- app/lib/request.lib.js | 5 + test/lib/request.lib.test.js | 195 +++++++++++++++++++++++++++++++++++ 2 files changed, 200 insertions(+) diff --git a/app/lib/request.lib.js b/app/lib/request.lib.js index e3d72eac5b..939aec2731 100644 --- a/app/lib/request.lib.js +++ b/app/lib/request.lib.js @@ -34,6 +34,10 @@ async function get (url, additionalOptions = {}) { return await _sendRequest('get', url, additionalOptions) } +async function patch (url, additionalOptions = {}) { + return await _sendRequest('patch', url, additionalOptions) +} + async function post (url, additionalOptions = {}) { return await _sendRequest('post', url, additionalOptions) } @@ -162,5 +166,6 @@ function _requestOptions (additionalOptions) { module.exports = { get, + patch, post } diff --git a/test/lib/request.lib.test.js b/test/lib/request.lib.test.js index 5b4becffd6..0c0afd7ac2 100644 --- a/test/lib/request.lib.test.js +++ b/test/lib/request.lib.test.js @@ -228,6 +228,201 @@ describe('RequestLib', () => { }) }) + describe.only('#patch()', () => { + describe('When a request succeeds', () => { + beforeEach(() => { + Nock(testDomain).patch('/').reply(200, { data: 'hello world' }) + }) + + describe('the result it returns', () => { + it("has a 'succeeded' property marked as true", async () => { + const result = await RequestLib.patch(testDomain) + + expect(result.succeeded).to.be.true() + }) + + it("has a 'response' property containing the web response", async () => { + const result = await RequestLib.patch(testDomain) + + expect(result.response).to.exist() + expect(result.response.statusCode).to.equal(200) + expect(result.response.body).to.equal('{"data":"hello world"}') + }) + }) + }) + + describe('When a request fails', () => { + describe('because the response was a 500', () => { + beforeEach(() => { + Nock(testDomain).patch('/').reply(500, { data: 'hello world' }) + }) + + it('logs the failure', async () => { + await RequestLib.patch(testDomain) + + const logDataArg = notifierStub.omg.args[0][1] + + expect(notifierStub.omg.calledWith('PATCH request failed')).to.be.true() + expect(logDataArg.method).to.equal('PATCH') + expect(logDataArg.url).to.equal('http://example.com') + expect(logDataArg.additionalOptions).to.equal({}) + expect(logDataArg.result.succeeded).to.be.false() + expect(logDataArg.result.response).to.equal({ + statusCode: 500, + body: '{"data":"hello world"}' + }) + }) + + describe('the result it returns', () => { + it("has a 'succeeded' property marked as false", async () => { + const result = await RequestLib.patch(testDomain) + + expect(result.succeeded).to.be.false() + }) + + it("has a 'response' property containing the web response", async () => { + const result = await RequestLib.patch(testDomain) + + expect(result.response).to.exist() + expect(result.response.statusCode).to.equal(500) + expect(result.response.body).to.equal('{"data":"hello world"}') + }) + }) + }) + + describe('because there was a network issue', () => { + beforeEach(() => { + Nock(testDomain).patch('/').replyWithError({ code: 'ECONNRESET' }) + }) + + it('logs and records the error', async () => { + await RequestLib.patch(testDomain) + + const logDataArg = notifierStub.omfg.args[0][1] + + expect(notifierStub.omfg.calledWith('PATCH request errored')).to.be.true() + expect(logDataArg.method).to.equal('PATCH') + expect(logDataArg.url).to.equal('http://example.com') + expect(logDataArg.additionalOptions).to.equal({}) + expect(logDataArg.result.succeeded).to.be.false() + expect(logDataArg.result.response).to.be.an.error() + }) + + describe('the result it returns', () => { + it("has a 'succeeded' property marked as false", async () => { + const result = await RequestLib.patch(testDomain) + + expect(result.succeeded).to.be.false() + }) + + it("has a 'response' property containing the error", async () => { + const result = await RequestLib.patch(testDomain) + + expect(result.response).to.exist() + expect(result.response).to.be.an.error() + expect(result.response.code).to.equal('ECONNRESET') + }) + }) + }) + + describe('because request timed out', () => { + beforeEach(async () => { + // Set the timeout value to 50ms for these tests + Sinon.replace(requestConfig, 'timeout', 50) + }) + + // Because of the fake delay in this test, Lab will timeout (by default tests have 2 seconds to finish). So, we + // have to override the timeout for this specific test to all it to complete + describe('and all retries fail', { timeout: 5000 }, () => { + beforeEach(async () => { + Nock(testDomain) + .patch(() => true) + .delay(100) + .reply(200, { data: 'hello world' }) + .persist() + }) + + describe('the result it returns', () => { + it("has a 'succeeded' property marked as false", async () => { + const result = await RequestLib.patch(testDomain) + + expect(result.succeeded).to.be.false() + }) + + it("has a 'response' property containing the error", async () => { + const result = await RequestLib.patch(testDomain) + + expect(result.response).to.exist() + expect(result.response).to.be.an.error() + expect(result.response.code).to.equal('ETIMEDOUT') + }) + }) + }) + + describe('and a retry succeeds', () => { + beforeEach(async () => { + // The first response will time out, the second response will return OK + Nock(testDomain) + .patch(() => true) + .delay(100) + .reply(200, { data: 'hello world' }) + .patch(() => true) + .reply(200, { data: 'delayed hello world' }) + }) + + describe('the result it returns', () => { + it("has a 'succeeded' property marked as true", async () => { + const result = await RequestLib.patch(testDomain) + + expect(result.succeeded).to.be.true() + }) + + it("has a 'response' property containing the web response", async () => { + const result = await RequestLib.patch(testDomain) + + expect(result.response).to.exist() + expect(result.response.statusCode).to.equal(200) + expect(result.response.body).to.equal('{"data":"delayed hello world"}') + }) + }) + }) + }) + }) + + describe('When I provide additional options', () => { + describe('and they expand the default options', () => { + beforeEach(() => { + Nock(testDomain).patch('/').reply(200, { data: 'hello world' }) + }) + + it('adds them to the options used when making the request', async () => { + // We tell Got to return the response as json rather than text. We can confirm the option has been applied by + // checking the result.response.body below. + const options = { responseType: 'json' } + const result = await RequestLib.patch(testDomain, options) + + expect(result.succeeded).to.be.true() + expect(result.response.body).to.equal({ data: 'hello world' }) + }) + }) + + describe('and they replace a default option', () => { + beforeEach(() => { + Nock(testDomain).patch('/').reply(500) + }) + + it('uses them in the options used when making the request', async () => { + // We tell Got throw an error if the response is not 2xx or 3xx + const options = { throwHttpErrors: true } + const result = await RequestLib.patch(testDomain, options) + + expect(result.succeeded).to.be.false() + expect(result.response).to.be.an.error() + }) + }) + }) + }) + describe('#post()', () => { describe('When a request succeeds', () => { beforeEach(() => {