'use strict';
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
chai.should();
const childProcess = require('child_process');
const fetch = require('node-fetch');
const fetchRetry = require('../../')(fetch);

describe('fetch-retry integration tests', () => {

  const baseUrl = 'http://localhost:3000/mock';

  before(() => {
    const process = childProcess.fork('./test/integration/mock-api/index.js');

    process.on('error', err => {
      console.log(err);
    });
  });

  after(() => {
    return fetchRetry(baseUrl + '/stop', {
      method: 'POST'
    });
  });

  const setupResponses = (responses) => {
    return fetchRetry(baseUrl, {
      method: 'POST',
      body: JSON.stringify(responses),
      headers: {
        'content-type': 'application/json'
      }
    });
  };

  const getCallCount = () => {
    return fetchRetry(`${baseUrl}/calls`)
      .then(response => {
        return response.text();
      })
      .then(text => {
        return Number.parseInt(text);
      });
  };

  [
    ['with url string', (baseUrl, init) => fetchRetry(baseUrl, init)],
    ['with URL type', (baseUrl, init) => {
      const url = new URL(baseUrl);
      return fetchRetry(url, init);
    }],
    ['with Request argument', (baseUrl, init) => {
      const request = new fetch.Request(baseUrl);
      return fetchRetry(request, init);
    }], 
  ].forEach(([usagePatternDescription, fetchInvocation]) => {

    describe(usagePatternDescription, () => {

      [200, 503, 404].forEach(statusCode => {

        describe('when endpoint returns ' + statusCode, () => {
    
          before(() => {
            return setupResponses([statusCode]);
          });
    
          it('does not retry request', () => {
            return fetchInvocation(baseUrl)
              .then(getCallCount)
              .should.eventually.equal(1);
          });
    
        });
    
      });
    
      describe('when configured to retry on a specific HTTP code', () => {
    
        describe('and it never succeeds', () => {
    
          const retryOn = [503];
    
          beforeEach(() => {
            return setupResponses([503, 503, 503, 503]);
          });
    
          it('retries the request #retries times', () => {
            const init = {
              retries: 3,
              retryDelay: 100,
              retryOn
            };
    
            const expectedCallCount = init.retries + 1;
    
            return fetchInvocation(baseUrl, init)
              .then(getCallCount)
              .should.eventually.equal(expectedCallCount);
          });
    
          it('eventually resolves the promise with the response of the last request', () => {
            const init = {
              retries: 3,
              retryDelay: 100,
              retryOn
            };
    
            const expectedResponse = {
              status: 503,
              ok: false
            };
    
            return fetchInvocation(baseUrl, init)
              .then(response => {
                return {
                  status: response.status,
                  ok: response.ok
                };
              })
              .should.become(expectedResponse);
          });
    
        });
    
        describe('and it eventually succeeds', () => {
    
          const retryOnStatus = 503;
          const responses = [503, 503, 200];
          const requestsToRetry = responses
            .filter(response => response === retryOnStatus)
            .length;
    
          beforeEach(() => {
            return setupResponses(responses);
          });
    
          it('retries the request up to #retries times', () => {
            const init = {
              retries: 3,
              retryDelay: 100,
              retryOn: [retryOnStatus]
            };
    
            const expectedCallCount = requestsToRetry + 1;
    
            return fetchInvocation(baseUrl, init)
              .then(getCallCount)
              .should.eventually.equal(expectedCallCount);
          });
    
          it('eventually resolves the promise with the received response of the last request', () => {
            const init = {
              retries: 3,
              retryDelay: 100,
              retryOn: [retryOnStatus]
            };
    
            const expectedResponse = {
              status: 200,
              ok: true
            };
    
            return fetchInvocation(baseUrl, init)
              .then(response => {
                return {
                  status: response.status,
                  ok: response.ok
                };
              })
              .should.become(expectedResponse);
          });
    
        });
    
      });
    
      describe('when configured to retry on a set of HTTP codes', () => {
    
        describe('and it never succeeds', () => {
    
          const retryOn = [503, 404];
    
          beforeEach(() => {
            return setupResponses([503, 404, 404, 503]);
          });
    
          it('retries the request #retries times', () => {
            const init = {
              retries: 3,
              retryDelay: 100,
              retryOn
            };
    
            const expectedCallCount = init.retries + 1;
    
            return fetchInvocation(baseUrl, init)
              .then(getCallCount)
              .should.eventually.equal(expectedCallCount);
          });
    
        });
    
      });
  
    });

  });

});
