Commit 2eb065b2 authored by Miguel Rincon's avatar Miguel Rincon

Merge branch 'jnnkl-vuln-details-refactor-response-section' into 'master'

Vulnerability Details - Refactor Request/Response sections into a component

See merge request gitlab-org/gitlab!57821
parents 563940ba 1030a56f
<script>
import VulnerabilityDetails from './details.vue';
import VulnerabilityFooter from './footer.vue';
import VulnerabilityHeader from './header.vue';
import VulnerabilityDetails from './vulnerability_details.vue';
export default {
components: { VulnerabilityHeader, VulnerabilityDetails, VulnerabilityFooter },
......
<script>
import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
import CodeBlock from '~/vue_shared/components/code_block.vue';
import DetailItem from './detail_item.vue';
export default {
name: 'VulnerabilityDetailSection',
components: { CodeBlock, DetailItem, GlIcon },
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
listData: {
type: Array,
required: true,
},
iconTitle: {
type: String,
required: false,
default: '',
},
heading: {
type: String,
required: false,
default: '',
},
},
};
</script>
<template>
<section>
<h3 v-if="heading">{{ heading }}</h3>
<ul>
<detail-item
v-for="({ label, isCode, content }, index) in listData"
:key="`${index}:${label}`"
:sprintf-message="label"
>
<gl-icon
v-if="iconTitle"
v-gl-tooltip
name="information-o"
class="gl-hover-cursor-pointer gl-mr-3"
:title="iconTitle"
/>
<code-block v-if="isCode" class="gl-mt-2" :code="content" max-height="225px" />
<template v-else>
<span data-testid="detail-item-content-template">
{{ content }}
</span>
</template>
</detail-item>
</ul>
</section>
</template>
<script>
import { GlLink, GlSprintf, GlTooltipDirective, GlIcon } from '@gitlab/ui';
import { GlLink, GlSprintf } from '@gitlab/ui';
import { bodyWithFallBack } from 'ee/vue_shared/security_reports/components/helpers';
import SeverityBadge from 'ee/vue_shared/security_reports/components/severity_badge.vue';
import { SUPPORTING_MESSAGE_TYPES } from 'ee/vulnerabilities/constants';
import { __ } from '~/locale';
import { s__, __ } from '~/locale';
import CodeBlock from '~/vue_shared/components/code_block.vue';
import DetailItem from './detail_item.vue';
import VulnerabilityDetailSection from './vulnerability_detail_section.vue';
export default {
name: 'VulnerabilityDetails',
components: { CodeBlock, GlLink, SeverityBadge, DetailItem, GlSprintf, GlIcon },
directives: {
GlTooltip: GlTooltipDirective,
components: {
CodeBlock,
GlLink,
SeverityBadge,
DetailItem,
GlSprintf,
VulnerabilityDetailSection,
},
props: {
vulnerability: {
......@@ -160,6 +165,15 @@ export default {
: '';
},
},
i18n: {
requestResponse: s__('Vulnerability|Request/Response'),
unmodifiedResponse: s__(
'Vulnerability|The unmodified response is the original response that had no mutations done to the request',
),
actualResponse: s__(
'Vulnerability|Actual received response is the one received when this fault was detected',
),
},
};
</script>
......@@ -293,81 +307,29 @@ export default {
</li>
</ul>
</template>
<section v-if="hasRequest" data-testid="request">
<h3>{{ s__('Vulnerability|Request/Response') }}</h3>
<ul>
<detail-item
v-for="({ label, isCode, content }, index) in requestData"
:key="`${index}:${label}`"
:sprintf-message="label"
>
<code-block v-if="isCode" class="gl-mt-2" :code="content" max-height="225px" />
<template v-else>
{{ content }}
</template>
</detail-item>
</ul>
</section>
<vulnerability-detail-section
v-if="hasRequest"
data-testid="request"
:list-data="requestData"
:heading="$options.i18n.requestResponse"
/>
<div v-if="hasResponses" class="row">
<section
<vulnerability-detail-section
v-if="hasRecordedResponse"
:class="hasResponse ? 'col-6' : 'col'"
data-testid="recorded-response"
>
<ul>
<detail-item
v-for="({ label, isCode, content }, index) in recordedResponseData"
:key="`${index}:${label}`"
:sprintf-message="label"
>
<gl-icon
v-gl-tooltip
name="information-o"
class="gl-hover-cursor-pointer gl-mr-3"
:title="
s__(
'Vulnerability|The unmodified response is the original response that had no mutations done to the request',
)
"
:class="hasResponse ? 'col-6' : 'col'"
:list-data="recordedResponseData"
:icon-title="$options.i18n.unmodifiedResponse"
/>
<code-block v-if="isCode" class="gl-mt-2" :code="content" max-height="225px" />
<template v-else>
{{ content }}
</template>
</detail-item>
</ul>
</section>
<section
<vulnerability-detail-section
v-if="hasResponse"
:class="hasRecordedResponse ? 'col-6' : 'col'"
data-testid="response"
>
<ul>
<detail-item
v-for="({ label, isCode, content }, index) in responseData"
:key="`${index}:${label}`"
:sprintf-message="label"
>
<gl-icon
v-gl-tooltip
name="information-o"
class="gl-hover-cursor-pointer gl-mr-3"
:title="
s__(
'Vulnerability|Actual received response is the one received when this fault was detected',
)
"
:class="hasRecordedResponse ? 'col-6' : 'col'"
:list-data="responseData"
:icon-title="$options.i18n.actualResponse"
/>
<code-block v-if="isCode" class="gl-mt-2" :code="content" max-height="225px" />
<template v-else>
{{ content }}
</template>
</detail-item>
</ul>
</section>
</div>
<template v-if="assertion">
......
import { GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import DetailItem from 'ee/vulnerabilities/components/detail_item.vue';
import VulnerabilityDetailSection from 'ee/vulnerabilities/components/vulnerability_detail_section.vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import CodeBlock from '~/vue_shared/components/code_block.vue';
describe('VulnerabilityDetails component', () => {
let wrapper;
const findHeading = () => wrapper.find('h3');
const findIcon = () => wrapper.find(GlIcon);
const findCodeBlock = () => wrapper.find(CodeBlock);
const findDetailItem = () => wrapper.find(DetailItem);
const findPlainContent = () => wrapper.findByTestId('detail-item-content-template');
const createComponent = (
iconTitle = 'exampleIconTitle',
heading = 'exampleHeading',
listData = [
{
label: 'Sent request:%{labelEnd}',
content: 'GET http://nginx\nCache-Control: ',
isCode: true,
},
],
) => {
wrapper = extendedWrapper(
shallowMount(VulnerabilityDetailSection, {
propsData: {
listData,
iconTitle,
heading,
},
}),
);
};
afterEach(() => {
wrapper.destroy();
});
it('should render DetailItem Component,', () => {
createComponent();
expect(findDetailItem().exists()).toBe(true);
});
it('limits the code-blocks maximum height', () => {
createComponent();
expect(findCodeBlock().props('maxHeight')).not.toBeFalsy();
expect(findCodeBlock().props('maxHeight')).toEqual(expect.any(String));
});
describe('if isCode prop is true', () => {
it('should display code block', () => {
createComponent();
expect(findCodeBlock().exists()).toBe(true);
});
it('should not display content in plain text', () => {
createComponent();
expect(findPlainContent().exists()).toBe(false);
});
});
describe('if isCode prop is false', () => {
it('should not display code block', () => {
createComponent('', '', [{ label: '', content: 'testcontent', isCode: false }]);
expect(findCodeBlock().exists()).toBe(false);
});
it('should display content in plain text', () => {
createComponent('', '', [{ label: '', content: 'testcontent', isCode: false }]);
expect(findPlainContent().exists()).toBe(true);
});
});
describe('if heading props is set', () => {
it('should render Heading', () => {
createComponent();
expect(findHeading().exists()).toBe(true);
});
});
describe('if heading props is not set', () => {
it('should not render Heading', () => {
createComponent('', '');
expect(findHeading().exists()).toBe(false);
});
});
describe('if iconTitle props is set', () => {
it('should render Icon', () => {
createComponent();
expect(findIcon().exists()).toBe(true);
});
});
describe('if iconTitle props is not set', () => {
it('should not render Icon', () => {
createComponent('');
expect(findIcon().exists()).toBe(false);
});
});
});
......@@ -6,7 +6,6 @@ import SeverityBadge from 'ee/vue_shared/security_reports/components/severity_ba
import VulnerabilityDetails from 'ee/vue_shared/security_reports/components/vulnerability_details.vue';
import { SUPPORTING_MESSAGE_TYPES } from 'ee/vulnerabilities/constants';
import { TEST_HOST } from 'helpers/test_constants';
import CodeBlock from '~/vue_shared/components/code_block.vue';
import { mockFindings } from '../mock_data';
......@@ -178,7 +177,7 @@ describe('VulnerabilityDetails component', () => {
[undefined, EMPTY_BODY_MESSAGE],
[null, EMPTY_BODY_MESSAGE],
[USER_NOT_FOUND_MESSAGE, USER_NOT_FOUND_MESSAGE],
])('with request information and body set to: %s', (body, renderedBody) => {
])('with request information and body set to: %s', (body) => {
let vulnerability;
beforeEach(() => {
......@@ -199,20 +198,6 @@ describe('VulnerabilityDetails component', () => {
it('renders the request-url', () => {
expect(findLink('url').attributes('href')).toBe('http://foo.bar/path');
});
it('renders a code-block containing the http request', () => {
const { method, url } = vulnerability.request;
expect(findRequest().is(CodeBlock)).toBe(true);
expect(findRequest().text()).toContain(method);
expect(findRequest().text()).toContain(url);
expect(findRequest().text()).toContain('key1: value1\nkey2: value2');
expect(findRequest().text()).toContain(renderedBody);
});
it('limits the code-blocks maximum height', () => {
expect(findRequest().props('maxHeight')).not.toBeFalsy();
expect(findRequest().props('maxHeight')).toEqual(expect.any(String));
});
});
describe('without request information', () => {
......@@ -240,7 +225,7 @@ describe('VulnerabilityDetails component', () => {
[undefined, EMPTY_BODY_MESSAGE],
[null, EMPTY_BODY_MESSAGE],
[USER_NOT_FOUND_MESSAGE, USER_NOT_FOUND_MESSAGE],
])('with response information and body set to: %s', (body, renderedBody) => {
])('with response information and body set to: %s', (body) => {
let vulnerability;
beforeEach(() => {
......@@ -261,16 +246,6 @@ describe('VulnerabilityDetails component', () => {
it('renders the response status code', () => {
expect(findUnmodifiedResponse().text()).toContain('200');
});
it('renders a code block containing the response', () => {
const { reason_phrase } = vulnerability.response;
const response = findUnmodifiedResponse();
expect(response.is(CodeBlock)).toBe(true);
expect(response.text()).toContain(reason_phrase);
expect(response.text()).toContain('key1: value1\nkey2: value2');
expect(response.text()).toContain(renderedBody);
});
});
describe('without unmodified response information', () => {
......@@ -289,7 +264,7 @@ describe('VulnerabilityDetails component', () => {
[undefined, EMPTY_BODY_MESSAGE],
[null, EMPTY_BODY_MESSAGE],
[USER_NOT_FOUND_MESSAGE, USER_NOT_FOUND_MESSAGE],
])('with recorded response information and body set to: %s', (body, renderedBody) => {
])('with recorded response information and body set to: %s', (body) => {
let vulnerability;
beforeEach(() => {
......@@ -315,16 +290,6 @@ describe('VulnerabilityDetails component', () => {
it('renders the recorded response status code', () => {
expect(findRecordedResponse().text()).toContain('200');
});
it('renders a code block containing the recorded response', () => {
const { reason_phrase } = vulnerability.supporting_messages[0].response;
const response = findRecordedResponse();
expect(response.is(CodeBlock)).toBe(true);
expect(response.text()).toContain(reason_phrase);
expect(response.text()).toContain('key1: value1\nkey2: value2');
expect(response.text()).toContain(renderedBody);
});
});
describe('without response information', () => {
......
......@@ -2,7 +2,7 @@ import { GlLink } from '@gitlab/ui';
import { getAllByRole, getByTestId } from '@testing-library/dom';
import { mount } from '@vue/test-utils';
import SeverityBadge from 'ee/vue_shared/security_reports/components/severity_badge.vue';
import VulnerabilityDetails from 'ee/vulnerabilities/components/details.vue';
import VulnerabilityDetails from 'ee/vulnerabilities/components/vulnerability_details.vue';
import { SUPPORTING_MESSAGE_TYPES } from 'ee/vulnerabilities/constants';
describe('Vulnerability Details', () => {
......
import { shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
import Details from 'ee/vulnerabilities/components/details.vue';
import Footer from 'ee/vulnerabilities/components/footer.vue';
import Header from 'ee/vulnerabilities/components/header.vue';
import Main from 'ee/vulnerabilities/components/vulnerability.vue';
import Details from 'ee/vulnerabilities/components/vulnerability_details.vue';
const mockAxios = new AxiosMockAdapter();
......
......@@ -10,7 +10,7 @@ module QA
element :vulnerability_header
end
view 'ee/app/assets/javascripts/vulnerabilities/components/details.vue' do
view 'ee/app/assets/javascripts/vulnerabilities/components/vulnerability_details.vue' do
element :vulnerability_details
element :vulnerability_title
element :vulnerability_description
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment