diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-03-16 21:18:33 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-03-16 21:18:33 +0300 |
commit | f64a639bcfa1fc2bc89ca7db268f594306edfd7c (patch) | |
tree | a2c3c2ebcc3b45e596949db485d6ed18ffaacfa1 /spec/frontend/captcha | |
parent | bfbc3e0d6583ea1a91f627528bedc3d65ba4b10f (diff) |
Add latest changes from gitlab-org/gitlab@13-10-stable-eev13.10.0-rc40
Diffstat (limited to 'spec/frontend/captcha')
-rw-r--r-- | spec/frontend/captcha/captcha_modal_axios_interceptor_spec.js | 119 | ||||
-rw-r--r-- | spec/frontend/captcha/wait_for_captcha_to_be_solved_spec.js | 56 |
2 files changed, 175 insertions, 0 deletions
diff --git a/spec/frontend/captcha/captcha_modal_axios_interceptor_spec.js b/spec/frontend/captcha/captcha_modal_axios_interceptor_spec.js new file mode 100644 index 00000000000..df81b78d010 --- /dev/null +++ b/spec/frontend/captcha/captcha_modal_axios_interceptor_spec.js @@ -0,0 +1,119 @@ +import MockAdapter from 'axios-mock-adapter'; + +import { registerCaptchaModalInterceptor } from '~/captcha/captcha_modal_axios_interceptor'; +import { waitForCaptchaToBeSolved } from '~/captcha/wait_for_captcha_to_be_solved'; +import axios from '~/lib/utils/axios_utils'; +import httpStatusCodes 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(() => { + mock = new MockAdapter(axios); + mock.onAny('/no-captcha').reply(200, AXIOS_RESPONSE); + mock.onAny('/error').reply(404, AXIOS_RESPONSE); + mock.onAny('/captcha').reply((config) => { + if (!supportedMethods.includes(config.method)) { + return [httpStatusCodes.METHOD_NOT_ALLOWED, { method: config.method }]; + } + + try { + const { captcha_response, spam_log_id, ...rest } = JSON.parse(config.data); + // eslint-disable-next-line babel/camelcase + if (captcha_response === CAPTCHA_RESPONSE && spam_log_id === SPAM_LOG_ID) { + return [httpStatusCodes.OK, { ...rest, method: config.method, CAPTCHA_SUCCESS }]; + } + } catch (e) { + return [httpStatusCodes.BAD_REQUEST, { method: config.method }]; + } + + return [httpStatusCodes.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]('/no-captcha'); + + expect(status).toEqual(httpStatusCodes.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]('/error')).rejects.toThrow( + expect.objectContaining({ + response: expect.objectContaining({ + status: httpStatusCodes.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 }; + + it('re-submits request if captcha was solved correctly', async () => { + waitForCaptchaToBeSolved.mockResolvedValue(CAPTCHA_RESPONSE); + const { data: returnedData } = await axios[method]('/captcha', submittedData); + + expect(waitForCaptchaToBeSolved).toHaveBeenCalledWith(CAPTCHA_SITE_KEY); + + expect(returnedData).toEqual({ ...submittedData, CAPTCHA_SUCCESS, method }); + expect(mock.history[method]).toHaveLength(2); + }); + + it('does not re-submit request if captcha was not solved', async () => { + const error = new Error('Captcha not solved'); + waitForCaptchaToBeSolved.mockRejectedValue(error); + await expect(() => axios[method]('/captcha', submittedData)).rejects.toThrow(error); + + 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]('/captcha')).rejects.toThrow( + expect.objectContaining({ + response: expect.objectContaining({ + status: httpStatusCodes.METHOD_NOT_ALLOWED, + data: { method }, + }), + }), + ); + + expect(waitForCaptchaToBeSolved).not.toHaveBeenCalled(); + expect(mock.history[method]).toHaveLength(1); + }); + }); +}); diff --git a/spec/frontend/captcha/wait_for_captcha_to_be_solved_spec.js b/spec/frontend/captcha/wait_for_captcha_to_be_solved_spec.js new file mode 100644 index 00000000000..08d031a4fa7 --- /dev/null +++ b/spec/frontend/captcha/wait_for_captcha_to_be_solved_spec.js @@ -0,0 +1,56 @@ +import CaptchaModal from '~/captcha/captcha_modal.vue'; +import { waitForCaptchaToBeSolved } from '~/captcha/wait_for_captcha_to_be_solved'; + +jest.mock('~/captcha/captcha_modal.vue', () => ({ + mounted: jest.fn(), + render(h) { + return h('div', { attrs: { id: 'mock-modal' } }); + }, +})); + +describe('waitForCaptchaToBeSolved', () => { + const response = 'CAPTCHA_RESPONSE'; + + const findModal = () => document.querySelector('#mock-modal'); + + it('opens a modal, resolves with captcha response on success', async () => { + CaptchaModal.mounted.mockImplementationOnce(function mounted() { + requestAnimationFrame(() => { + this.$emit('receivedCaptchaResponse', response); + this.$emit('hidden'); + }); + }); + + expect(findModal()).toBeNull(); + + const promise = waitForCaptchaToBeSolved('FOO'); + + expect(findModal()).not.toBeNull(); + + const result = await promise; + expect(result).toEqual(response); + + expect(findModal()).toBeNull(); + expect(document.body.innerHTML).toEqual(''); + }); + + it("opens a modal, rejects with error in case the captcha isn't solved", async () => { + CaptchaModal.mounted.mockImplementationOnce(function mounted() { + requestAnimationFrame(() => { + this.$emit('receivedCaptchaResponse', null); + this.$emit('hidden'); + }); + }); + + expect(findModal()).toBeNull(); + + const promise = waitForCaptchaToBeSolved('FOO'); + + expect(findModal()).not.toBeNull(); + + await expect(promise).rejects.toThrow(/You must solve the CAPTCHA in order to submit/); + + expect(findModal()).toBeNull(); + expect(document.body.innerHTML).toEqual(''); + }); +}); |