142 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			142 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
import MockAdapter from 'axios-mock-adapter';
 | 
						|
 | 
						|
import { registerCaptchaModalInterceptor } from '~/captcha/captcha_modal_axios_interceptor';
 | 
						|
import UnsolvedCaptchaError from '~/captcha/unsolved_captcha_error';
 | 
						|
import { waitForCaptchaToBeSolved } from '~/captcha/wait_for_captcha_to_be_solved';
 | 
						|
import axios from '~/lib/utils/axios_utils';
 | 
						|
import {
 | 
						|
  HTTP_STATUS_CONFLICT,
 | 
						|
  HTTP_STATUS_METHOD_NOT_ALLOWED,
 | 
						|
  HTTP_STATUS_NOT_FOUND,
 | 
						|
  HTTP_STATUS_OK,
 | 
						|
} from '~/lib/utils/http_status';
 | 
						|
 | 
						|
jest.mock('~/captcha/wait_for_captcha_to_be_solved');
 | 
						|
 | 
						|
describe('registerCaptchaModalInterceptor', () => {
 | 
						|
  const SPAM_LOG_ID = 'SPAM_LOG_ID';
 | 
						|
  const CAPTCHA_SITE_KEY = 'CAPTCHA_SITE_KEY';
 | 
						|
  const CAPTCHA_SUCCESS = 'CAPTCHA_SUCCESS';
 | 
						|
  const CAPTCHA_RESPONSE = 'CAPTCHA_RESPONSE';
 | 
						|
  const AXIOS_RESPONSE = { text: 'AXIOS_RESPONSE' };
 | 
						|
  const NEEDS_CAPTCHA_RESPONSE = {
 | 
						|
    needs_captcha_response: true,
 | 
						|
    captcha_site_key: CAPTCHA_SITE_KEY,
 | 
						|
    spam_log_id: SPAM_LOG_ID,
 | 
						|
  };
 | 
						|
 | 
						|
  const unsupportedMethods = ['delete', 'get', 'head', 'options'];
 | 
						|
  const supportedMethods = ['patch', 'post', 'put'];
 | 
						|
 | 
						|
  let mock;
 | 
						|
 | 
						|
  beforeEach(() => {
 | 
						|
    waitForCaptchaToBeSolved.mockRejectedValue(new UnsolvedCaptchaError());
 | 
						|
 | 
						|
    mock = new MockAdapter(axios);
 | 
						|
    mock.onAny('/endpoint-without-captcha').reply(HTTP_STATUS_OK, AXIOS_RESPONSE);
 | 
						|
    mock.onAny('/endpoint-with-unrelated-error').reply(HTTP_STATUS_NOT_FOUND, AXIOS_RESPONSE);
 | 
						|
    mock.onAny('/endpoint-with-captcha').reply((config) => {
 | 
						|
      if (!supportedMethods.includes(config.method)) {
 | 
						|
        return [HTTP_STATUS_METHOD_NOT_ALLOWED, { method: config.method }];
 | 
						|
      }
 | 
						|
 | 
						|
      const data = JSON.parse(config.data);
 | 
						|
      const {
 | 
						|
        'X-GitLab-Captcha-Response': captchaResponse,
 | 
						|
        'X-GitLab-Spam-Log-Id': spamLogId,
 | 
						|
      } = config.headers;
 | 
						|
 | 
						|
      if (captchaResponse === CAPTCHA_RESPONSE && spamLogId === SPAM_LOG_ID) {
 | 
						|
        return [HTTP_STATUS_OK, { ...data, method: config.method, CAPTCHA_SUCCESS }];
 | 
						|
      }
 | 
						|
 | 
						|
      return [HTTP_STATUS_CONFLICT, NEEDS_CAPTCHA_RESPONSE];
 | 
						|
    });
 | 
						|
 | 
						|
    axios.interceptors.response.handlers = [];
 | 
						|
    registerCaptchaModalInterceptor(axios);
 | 
						|
  });
 | 
						|
 | 
						|
  afterEach(() => {
 | 
						|
    mock.restore();
 | 
						|
  });
 | 
						|
 | 
						|
  describe.each([...supportedMethods, ...unsupportedMethods])('For HTTP method %s', (method) => {
 | 
						|
    it('successful requests are passed through', async () => {
 | 
						|
      const { data, status } = await axios[method]('/endpoint-without-captcha');
 | 
						|
 | 
						|
      expect(status).toEqual(HTTP_STATUS_OK);
 | 
						|
      expect(data).toEqual(AXIOS_RESPONSE);
 | 
						|
      expect(mock.history[method]).toHaveLength(1);
 | 
						|
    });
 | 
						|
 | 
						|
    it('error requests without needs_captcha_response_errors are passed through', async () => {
 | 
						|
      await expect(() => axios[method]('/endpoint-with-unrelated-error')).rejects.toThrow(
 | 
						|
        expect.objectContaining({
 | 
						|
          response: expect.objectContaining({
 | 
						|
            status: HTTP_STATUS_NOT_FOUND,
 | 
						|
            data: AXIOS_RESPONSE,
 | 
						|
          }),
 | 
						|
        }),
 | 
						|
      );
 | 
						|
      expect(mock.history[method]).toHaveLength(1);
 | 
						|
    });
 | 
						|
  });
 | 
						|
 | 
						|
  describe.each(supportedMethods)('For HTTP method %s', (method) => {
 | 
						|
    describe('error requests with needs_captcha_response_errors', () => {
 | 
						|
      const submittedData = { ID: 12345 };
 | 
						|
      const submittedHeaders = { 'Submitted-Header': 67890 };
 | 
						|
 | 
						|
      it('re-submits request if captcha was solved correctly', async () => {
 | 
						|
        waitForCaptchaToBeSolved.mockResolvedValueOnce(CAPTCHA_RESPONSE);
 | 
						|
        const axiosResponse = await axios[method]('/endpoint-with-captcha', submittedData, {
 | 
						|
          headers: submittedHeaders,
 | 
						|
        });
 | 
						|
        const {
 | 
						|
          data: returnedData,
 | 
						|
          config: { headers: returnedHeaders },
 | 
						|
        } = axiosResponse;
 | 
						|
 | 
						|
        expect(waitForCaptchaToBeSolved).toHaveBeenCalledWith(CAPTCHA_SITE_KEY);
 | 
						|
 | 
						|
        expect(returnedData).toEqual({ ...submittedData, CAPTCHA_SUCCESS, method });
 | 
						|
        expect(returnedHeaders).toEqual(
 | 
						|
          expect.objectContaining({
 | 
						|
            ...submittedHeaders,
 | 
						|
            'X-GitLab-Captcha-Response': CAPTCHA_RESPONSE,
 | 
						|
            'X-GitLab-Spam-Log-Id': SPAM_LOG_ID,
 | 
						|
          }),
 | 
						|
        );
 | 
						|
        expect(mock.history[method]).toHaveLength(2);
 | 
						|
      });
 | 
						|
 | 
						|
      it('does not re-submit request if captcha was not solved', async () => {
 | 
						|
        await expect(() => axios[method]('/endpoint-with-captcha', submittedData)).rejects.toThrow(
 | 
						|
          new UnsolvedCaptchaError(),
 | 
						|
        );
 | 
						|
 | 
						|
        expect(waitForCaptchaToBeSolved).toHaveBeenCalledWith(CAPTCHA_SITE_KEY);
 | 
						|
        expect(mock.history[method]).toHaveLength(1);
 | 
						|
      });
 | 
						|
    });
 | 
						|
  });
 | 
						|
 | 
						|
  describe.each(unsupportedMethods)('For HTTP method %s', (method) => {
 | 
						|
    it('ignores captcha response', async () => {
 | 
						|
      await expect(() => axios[method]('/endpoint-with-captcha')).rejects.toThrow(
 | 
						|
        expect.objectContaining({
 | 
						|
          response: expect.objectContaining({
 | 
						|
            status: HTTP_STATUS_METHOD_NOT_ALLOWED,
 | 
						|
            data: { method },
 | 
						|
          }),
 | 
						|
        }),
 | 
						|
      );
 | 
						|
 | 
						|
      expect(waitForCaptchaToBeSolved).not.toHaveBeenCalled();
 | 
						|
      expect(mock.history[method]).toHaveLength(1);
 | 
						|
    });
 | 
						|
  });
 | 
						|
});
 |