diff options
Diffstat (limited to 'spec/frontend/captcha/apollo_captcha_link_spec.js')
-rw-r--r-- | spec/frontend/captcha/apollo_captcha_link_spec.js | 165 |
1 files changed, 165 insertions, 0 deletions
diff --git a/spec/frontend/captcha/apollo_captcha_link_spec.js b/spec/frontend/captcha/apollo_captcha_link_spec.js new file mode 100644 index 00000000000..e7ff4812ee7 --- /dev/null +++ b/spec/frontend/captcha/apollo_captcha_link_spec.js @@ -0,0 +1,165 @@ +import { ApolloLink, Observable } from 'apollo-link'; + +import { apolloCaptchaLink } from '~/captcha/apollo_captcha_link'; +import UnsolvedCaptchaError from '~/captcha/unsolved_captcha_error'; +import { waitForCaptchaToBeSolved } from '~/captcha/wait_for_captcha_to_be_solved'; + +jest.mock('~/captcha/wait_for_captcha_to_be_solved'); + +describe('apolloCaptchaLink', () => { + const SPAM_LOG_ID = 'SPAM_LOG_ID'; + const CAPTCHA_SITE_KEY = 'CAPTCHA_SITE_KEY'; + const CAPTCHA_RESPONSE = 'CAPTCHA_RESPONSE'; + + const SUCCESS_RESPONSE = { + data: { + user: { + id: 3, + name: 'foo', + }, + }, + errors: [], + }; + + const NON_CAPTCHA_ERROR_RESPONSE = { + data: { + user: null, + }, + errors: [ + { + message: 'Something is severely wrong with your query.', + path: ['user'], + locations: [{ line: 2, column: 3 }], + extensions: { + message: 'Object not found', + type: 2, + }, + }, + ], + }; + + const SPAM_ERROR_RESPONSE = { + data: { + user: null, + }, + errors: [ + { + message: 'Your Query was detected to be spam.', + path: ['user'], + locations: [{ line: 2, column: 3 }], + extensions: { + spam: true, + }, + }, + ], + }; + + const CAPTCHA_ERROR_RESPONSE = { + data: { + user: null, + }, + errors: [ + { + message: 'This is an unrelated error, captcha should still work despite this.', + path: ['user'], + locations: [{ line: 2, column: 3 }], + }, + { + message: 'You need to solve a Captcha.', + path: ['user'], + locations: [{ line: 2, column: 3 }], + extensions: { + spam: true, + needs_captcha_response: true, + captcha_site_key: CAPTCHA_SITE_KEY, + spam_log_id: SPAM_LOG_ID, + }, + }, + ], + }; + + let link; + + let mockLinkImplementation; + let mockContext; + + const setupLink = (...responses) => { + mockLinkImplementation = jest.fn().mockImplementation(() => { + return Observable.of(responses.shift()); + }); + link = ApolloLink.from([apolloCaptchaLink, new ApolloLink(mockLinkImplementation)]); + }; + + function mockOperation() { + mockContext = jest.fn(); + return { operationName: 'operation', variables: {}, setContext: mockContext }; + } + + it('successful responses are passed through', (done) => { + setupLink(SUCCESS_RESPONSE); + link.request(mockOperation()).subscribe((result) => { + expect(result).toEqual(SUCCESS_RESPONSE); + expect(mockLinkImplementation).toHaveBeenCalledTimes(1); + expect(waitForCaptchaToBeSolved).not.toHaveBeenCalled(); + done(); + }); + }); + + it('non-spam related errors are passed through', (done) => { + setupLink(NON_CAPTCHA_ERROR_RESPONSE); + link.request(mockOperation()).subscribe((result) => { + expect(result).toEqual(NON_CAPTCHA_ERROR_RESPONSE); + expect(mockLinkImplementation).toHaveBeenCalledTimes(1); + expect(mockContext).not.toHaveBeenCalled(); + expect(waitForCaptchaToBeSolved).not.toHaveBeenCalled(); + done(); + }); + }); + + it('unresolvable spam errors are passed through', (done) => { + setupLink(SPAM_ERROR_RESPONSE); + link.request(mockOperation()).subscribe((result) => { + expect(result).toEqual(SPAM_ERROR_RESPONSE); + expect(mockLinkImplementation).toHaveBeenCalledTimes(1); + expect(mockContext).not.toHaveBeenCalled(); + expect(waitForCaptchaToBeSolved).not.toHaveBeenCalled(); + done(); + }); + }); + + describe('resolvable spam errors', () => { + it('re-submits request with spam headers if the captcha modal was solved correctly', (done) => { + waitForCaptchaToBeSolved.mockResolvedValue(CAPTCHA_RESPONSE); + setupLink(CAPTCHA_ERROR_RESPONSE, SUCCESS_RESPONSE); + link.request(mockOperation()).subscribe((result) => { + expect(result).toEqual(SUCCESS_RESPONSE); + expect(waitForCaptchaToBeSolved).toHaveBeenCalledWith(CAPTCHA_SITE_KEY); + expect(mockContext).toHaveBeenCalledWith({ + headers: { + 'X-GitLab-Captcha-Response': CAPTCHA_RESPONSE, + 'X-GitLab-Spam-Log-Id': SPAM_LOG_ID, + }, + }); + expect(mockLinkImplementation).toHaveBeenCalledTimes(2); + done(); + }); + }); + + it('throws error if the captcha modal was not solved correctly', (done) => { + const error = new UnsolvedCaptchaError(); + waitForCaptchaToBeSolved.mockRejectedValue(error); + + setupLink(CAPTCHA_ERROR_RESPONSE, SUCCESS_RESPONSE); + link.request(mockOperation()).subscribe({ + next: done.catch, + error: (result) => { + expect(result).toEqual(error); + expect(waitForCaptchaToBeSolved).toHaveBeenCalledWith(CAPTCHA_SITE_KEY); + expect(mockContext).not.toHaveBeenCalled(); + expect(mockLinkImplementation).toHaveBeenCalledTimes(1); + done(); + }, + }); + }); + }); +}); |