import { mount, shallowMount } from '@vue/test-utils';
import VulnerabilityDetails from 'ee/vue_shared/security_reports/components/vulnerability_details.vue';
import SeverityBadge from 'ee/vue_shared/security_reports/components/severity_badge.vue';
import { TEST_HOST } from 'helpers/test_constants';
import { cloneDeep } from 'lodash';
import { GlLink, GlBadge } from '@gitlab/ui';
import { mockFindings } from '../mock_data';
import CodeBlock from '~/vue_shared/components/code_block.vue';

function makeVulnerability(changes = {}) {
  return Object.assign(cloneDeep(mockFindings[0]), changes);
}

describe('VulnerabilityDetails component', () => {
  let wrapper;

  const componentFactory = vulnerability => {
    wrapper = mount(VulnerabilityDetails, {
      propsData: { vulnerability },
    });
  };

  const expectSafeLink = ({ link, href, text, isExternal = true }) => {
    expect(link.is(GlLink)).toBe(true);
    expect(link.attributes('href')).toBe(href);
    expect(link.text()).toBe(text);
    if (isExternal) {
      expect(link.attributes('rel')).toContain('noopener noreferrer');
    }
  };

  const findLink = name => wrapper.find({ ref: `${name}Link` });
  const findRequestHeaders = () => wrapper.find({ ref: 'requestHeaders' });
  const findResponseHeaders = () => wrapper.find({ ref: 'responseHeaders' });
  const findResponseStatusCode = () => wrapper.find({ ref: 'responseStatusCode' });
  const findCrashAddress = () => wrapper.find({ ref: 'crashAddress' });
  const findCrashState = () => wrapper.find({ ref: 'crashState' });
  const findCrashType = () => wrapper.find({ ref: 'crashType' });
  const findStacktraceSnippet = () => wrapper.find({ ref: 'stacktraceSnippet' });

  afterEach(() => {
    wrapper.destroy();
  });

  it('renders severity with a badge', () => {
    const vulnerability = makeVulnerability({ severity: 'critical' });
    componentFactory(vulnerability);
    const badge = wrapper.find(SeverityBadge);

    expect(badge.props('severity')).toBe(vulnerability.severity);
  });

  it('renders status with a badge', () => {
    const vulnerability = makeVulnerability({ state: 'detected' });
    componentFactory(vulnerability);
    const badge = wrapper.find(GlBadge);

    expect(badge.text()).toContain(vulnerability.state);
  });

  it('renders link fields with link', () => {
    const vulnerability = makeVulnerability();
    componentFactory(vulnerability);

    expectSafeLink({
      link: findLink('project'),
      href: vulnerability.project.full_path,
      text: vulnerability.project.full_name,
      isExternal: false,
    });
  });

  it('renders wrapped file paths', () => {
    const vulnerability = makeVulnerability({
      blob_path: `${TEST_HOST}/bar`,
      location: {
        file: '/some/file/path',
      },
    });

    componentFactory(vulnerability);

    expect(findLink('file').html()).toMatch('/<wbr>some/<wbr>file/<wbr>path');
  });

  it('escapes wrapped file paths', () => {
    const vulnerability = makeVulnerability({
      blob_path: `${TEST_HOST}/bar`,
      location: {
        file: '/unsafe/path<script></script>',
      },
    });

    componentFactory(vulnerability);

    expect(findLink('file').html()).toMatch(
      '/<wbr>unsafe/<wbr>path&lt;script&gt;&lt;/<wbr>script&gt;',
    );
  });

  describe('does not render XSS links', () => {
    // eslint-disable-next-line no-script-url
    const badUrl = 'javascript:alert("")';

    beforeEach(() => {
      const vulnerability = makeVulnerability({
        blob_path: badUrl,
        location: {
          file: 'badFile.lock',
        },
        links: [{ url: badUrl }],
        identifiers: [{ name: 'BAD_URL', url: badUrl }],
      });

      componentFactory(vulnerability);
    });

    it('for the link field', () => {
      expectSafeLink({ link: findLink('links'), href: 'about:blank', text: badUrl });
    });

    it('for the identifiers field', () => {
      expectSafeLink({ link: findLink('identifiers'), href: 'about:blank', text: 'BAD_URL' });
    });

    it('for the file field', () => {
      expectSafeLink({
        link: findLink('file'),
        href: 'about:blank',
        text: 'badFile.lock',
        isExternal: false,
      });
    });
  });

  describe('with coverage fuzzing information', () => {
    let vulnerability;
    beforeEach(() => {
      vulnerability = makeVulnerability({
        location: {
          crash_address: '0x602000001573',
          crash_state: 'FuzzMe\nstart\nstart+0x0\n\n',
          crash_type: 'Heap-buffer-overflow\nREAD 1',
          stacktrace_snippet: 'test snippet',
        },
      });
      componentFactory(vulnerability);
    });

    it('renders crash_address', () => {
      expect(findCrashAddress().exists()).toBe(true);
      expect(findStacktraceSnippet().exists()).toBe(true);
    });

    it('renders crash_state', () => {
      expect(findCrashState().exists()).toBe(true);
      expect(findCrashState().html()).toContain(vulnerability.location.crash_state);
    });

    it('renders crash_type', () => {
      expect(findCrashType().exists()).toBe(true);
      expect(findCrashType().text()).toContain(vulnerability.location.crash_type);
    });
  });

  describe('with request information', () => {
    beforeEach(() => {
      const vulnerability = makeVulnerability({
        request: {
          url: 'http://foo.bar/path',
          headers: [{ name: 'key1', value: 'value1' }, { name: 'key2', value: 'value2' }],
        },
      });
      componentFactory(vulnerability);
    });

    it('renders the request-url', () => {
      expect(findLink('url').attributes('href')).toBe('http://foo.bar/path');
    });

    it('renders a code-block containing the http headers', () => {
      expect(findRequestHeaders().is(CodeBlock)).toBe(true);
      expect(findRequestHeaders().text()).toBe('key1: value1\nkey2: value2');
    });

    it('limits the code-blocks maximum height', () => {
      expect(findRequestHeaders().props('maxHeight')).not.toBeFalsy();
      expect(findRequestHeaders().props('maxHeight')).toEqual(expect.any(String));
    });
  });

  describe('without request information', () => {
    beforeEach(() => {
      const vulnerability = makeVulnerability({
        location: {
          hostname: 'http://foo.com',
          path: '/bar',
        },
      });
      componentFactory(vulnerability);
    });

    it('renders the location-url', () => {
      expect(findLink('url').text()).toBe('http://foo.com/bar');
    });

    it('does not render a code block containing the request-headers', () => {
      expect(findRequestHeaders().exists()).toBe(false);
    });
  });

  describe('with response information', () => {
    beforeEach(() => {
      const vulnerability = makeVulnerability({
        response: {
          status_code: '200',
          headers: [{ name: 'key1', value: 'value1' }, { name: 'key2', value: 'value2' }],
        },
      });
      componentFactory(vulnerability);
    });

    it('renders the response status code', () => {
      expect(findResponseStatusCode().text()).toBe('200');
    });

    it('renders a code block containing the request-headers', () => {
      const responseHeaders = findResponseHeaders();

      expect(responseHeaders.is(CodeBlock)).toBe(true);
      expect(responseHeaders.text()).toBe('key1: value1\nkey2: value2');
    });
  });

  describe('without response information', () => {
    beforeEach(() => {
      const vulnerability = makeVulnerability();
      componentFactory(vulnerability);
    });

    it('does not render the status code', () => {
      expect(findResponseStatusCode().exists()).toBe(false);
    });

    it('does not render the http-headers', () => {
      expect(findResponseHeaders().exists()).toBe(false);
    });
  });

  describe('scanner details', () => {
    describe('with additional information', () => {
      beforeEach(() => {
        const vulnerability = makeVulnerability();
        componentFactory(vulnerability);
      });

      it('should include version information', () => {
        expect(findLink('scanner').text()).toBe('Gemnasium (version 1.1.1)');
      });

      it('should render link', () => {
        expect(
          findLink('scanner')
            .find('a')
            .exists(),
        ).toBe(true);
      });
    });

    describe('without additional information', () => {
      beforeEach(() => {
        const vulnerability = makeVulnerability({
          scanner: {
            id: 'clair',
            name: 'Clair',
          },
        });
        componentFactory(vulnerability);
      });

      it('should not render the link', () => {
        expect(findLink('scanner').exists()).toBe(false);
      });
    });
  });

  describe('pin test', () => {
    const factory = vulnFinding => {
      wrapper = shallowMount(VulnerabilityDetails, {
        propsData: {
          vulnerability: vulnFinding,
        },
      });
    };

    it('renders correctly', () => {
      factory(
        makeVulnerability({
          request: {
            url: 'http://foo.bar/path',
            headers: [{ name: 'key1', value: 'value1' }, { name: 'key2', value: 'value2' }],
          },
          response: {
            status_code: '200',
            headers: [{ name: 'key1', value: 'value1' }, { name: 'key2', value: 'value2' }],
          },
        }),
      );

      expect(wrapper.element).toMatchSnapshot();
    });
  });
});
