index.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. 'use strict';
  2. const chai = require('chai');
  3. const chaiAsPromised = require('chai-as-promised');
  4. chai.use(chaiAsPromised);
  5. chai.should();
  6. const childProcess = require('child_process');
  7. const fetch = require('node-fetch');
  8. const fetchRetry = require('../../')(fetch);
  9. describe('fetch-retry integration tests', () => {
  10. const baseUrl = 'http://localhost:3000/mock';
  11. before(() => {
  12. const process = childProcess.fork('./test/integration/mock-api/index.js');
  13. process.on('error', err => {
  14. console.log(err);
  15. });
  16. });
  17. after(() => {
  18. return fetchRetry(baseUrl + '/stop', {
  19. method: 'POST'
  20. });
  21. });
  22. const setupResponses = (responses) => {
  23. return fetchRetry(baseUrl, {
  24. method: 'POST',
  25. body: JSON.stringify(responses),
  26. headers: {
  27. 'content-type': 'application/json'
  28. }
  29. });
  30. };
  31. const getCallCount = () => {
  32. return fetchRetry(`${baseUrl}/calls`)
  33. .then(response => {
  34. return response.text();
  35. })
  36. .then(text => {
  37. return Number.parseInt(text);
  38. });
  39. };
  40. [
  41. ['with url string', (baseUrl, init) => fetchRetry(baseUrl, init)],
  42. ['with URL type', (baseUrl, init) => {
  43. const url = new URL(baseUrl);
  44. return fetchRetry(url, init);
  45. }],
  46. ['with Request argument', (baseUrl, init) => {
  47. const request = new fetch.Request(baseUrl);
  48. return fetchRetry(request, init);
  49. }],
  50. ].forEach(([usagePatternDescription, fetchInvocation]) => {
  51. describe(usagePatternDescription, () => {
  52. [200, 503, 404].forEach(statusCode => {
  53. describe('when endpoint returns ' + statusCode, () => {
  54. before(() => {
  55. return setupResponses([statusCode]);
  56. });
  57. it('does not retry request', () => {
  58. return fetchInvocation(baseUrl)
  59. .then(getCallCount)
  60. .should.eventually.equal(1);
  61. });
  62. });
  63. });
  64. describe('when configured to retry on a specific HTTP code', () => {
  65. describe('and it never succeeds', () => {
  66. const retryOn = [503];
  67. beforeEach(() => {
  68. return setupResponses([503, 503, 503, 503]);
  69. });
  70. it('retries the request #retries times', () => {
  71. const init = {
  72. retries: 3,
  73. retryDelay: 100,
  74. retryOn
  75. };
  76. const expectedCallCount = init.retries + 1;
  77. return fetchInvocation(baseUrl, init)
  78. .then(getCallCount)
  79. .should.eventually.equal(expectedCallCount);
  80. });
  81. it('eventually resolves the promise with the response of the last request', () => {
  82. const init = {
  83. retries: 3,
  84. retryDelay: 100,
  85. retryOn
  86. };
  87. const expectedResponse = {
  88. status: 503,
  89. ok: false
  90. };
  91. return fetchInvocation(baseUrl, init)
  92. .then(response => {
  93. return {
  94. status: response.status,
  95. ok: response.ok
  96. };
  97. })
  98. .should.become(expectedResponse);
  99. });
  100. });
  101. describe('and it eventually succeeds', () => {
  102. const retryOnStatus = 503;
  103. const responses = [503, 503, 200];
  104. const requestsToRetry = responses
  105. .filter(response => response === retryOnStatus)
  106. .length;
  107. beforeEach(() => {
  108. return setupResponses(responses);
  109. });
  110. it('retries the request up to #retries times', () => {
  111. const init = {
  112. retries: 3,
  113. retryDelay: 100,
  114. retryOn: [retryOnStatus]
  115. };
  116. const expectedCallCount = requestsToRetry + 1;
  117. return fetchInvocation(baseUrl, init)
  118. .then(getCallCount)
  119. .should.eventually.equal(expectedCallCount);
  120. });
  121. it('eventually resolves the promise with the received response of the last request', () => {
  122. const init = {
  123. retries: 3,
  124. retryDelay: 100,
  125. retryOn: [retryOnStatus]
  126. };
  127. const expectedResponse = {
  128. status: 200,
  129. ok: true
  130. };
  131. return fetchInvocation(baseUrl, init)
  132. .then(response => {
  133. return {
  134. status: response.status,
  135. ok: response.ok
  136. };
  137. })
  138. .should.become(expectedResponse);
  139. });
  140. });
  141. });
  142. describe('when configured to retry on a set of HTTP codes', () => {
  143. describe('and it never succeeds', () => {
  144. const retryOn = [503, 404];
  145. beforeEach(() => {
  146. return setupResponses([503, 404, 404, 503]);
  147. });
  148. it('retries the request #retries times', () => {
  149. const init = {
  150. retries: 3,
  151. retryDelay: 100,
  152. retryOn
  153. };
  154. const expectedCallCount = init.retries + 1;
  155. return fetchInvocation(baseUrl, init)
  156. .then(getCallCount)
  157. .should.eventually.equal(expectedCallCount);
  158. });
  159. });
  160. });
  161. });
  162. });
  163. });