summaryrefslogtreecommitdiff
path: root/spec/frontend/lib/utils/rails_ujs_spec.js
blob: da9cc5c6f3c0b626dcf8f3e9866fad1e89171b61 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import { setHTMLFixture } from 'helpers/fixtures';
import waitForPromises from 'helpers/wait_for_promises';

beforeAll(async () => {
  // @rails/ujs expects jQuery.ajaxPrefilter to exist if jQuery exists at
  // import time. This is only a problem in tests, since we expose jQuery
  // globally earlier than in production builds. Work around this by pretending
  // that jQuery isn't available *before* we import @rails/ujs.
  delete global.jQuery;

  const { initRails } = await import('~/lib/utils/rails_ujs');
  initRails();
});

function mockXHRResponse({ responseText, responseContentType } = {}) {
  jest
    .spyOn(global.XMLHttpRequest.prototype, 'getResponseHeader')
    .mockReturnValue(responseContentType);

  jest.spyOn(global.XMLHttpRequest.prototype, 'send').mockImplementation(function send() {
    Object.defineProperties(this, {
      readyState: { value: XMLHttpRequest.DONE },
      status: { value: 200 },
      response: { value: responseText },
    });
    this.onreadystatechange();
  });
}

// This is a test to make sure that the patch-package patch correctly disables
// script execution for data-remote attributes.
it('does not perform script execution via data-remote', async () => {
  global.scriptExecutionSpy = jest.fn();

  mockXHRResponse({
    responseText: 'scriptExecutionSpy();',
    responseContentType: 'application/javascript',
  });

  setHTMLFixture(`
    <a href="/foo/evil.js"
       data-remote="true"
       data-method="get"
       data-type="script"
       data-testid="evil-link"
    >XSS</a>
  `);

  const link = document.querySelector('[data-testid="evil-link"]');
  const ajaxSuccessSpy = jest.fn();
  link.addEventListener('ajax:success', ajaxSuccessSpy);

  link.click();

  await waitForPromises();

  // Make sure Rails ajax machinery finished working as expected to avoid false
  // positives
  expect(ajaxSuccessSpy).toHaveBeenCalledTimes(1);

  // If @rails/ujs has been patched correctly, this next assertion should pass.
  //
  // Because it's asserting something didn't happen, it is possible for it to
  // pass for the wrong reason. So, to verify that this test correctly fails
  // when @rails/ujs has not been patched, run:
  //
  //     yarn patch-package --reverse
  //
  // And then re-run this test. The spy should now be called, and correctly
  // fail the test.
  //
  // To restore the patch(es), run:
  //
  //     yarn install
  expect(global.scriptExecutionSpy).not.toHaveBeenCalled();
});