Commit 8ff24e29 authored by David O'Regan's avatar David O'Regan

Merge branch '273423-implement-counts-in-core-security-mr-widget' into 'master'

Implement vulnerability counts in basic security MR widget [RUN AS-IF-FOSS]

See merge request gitlab-org/gitlab!47656
parents f86a621c 71adfb2f
......@@ -3,7 +3,7 @@ import { __ } from '~/locale';
import StatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
import Popover from '~/vue_shared/components/help_popover.vue';
import IssuesList from './issues_list.vue';
import { status } from '../constants';
import { status, SLOT_SUCCESS, SLOT_LOADING, SLOT_ERROR } from '../constants';
export default {
name: 'ReportSection',
......@@ -152,12 +152,12 @@ export default {
},
slotName() {
if (this.isSuccess) {
return 'success';
return SLOT_SUCCESS;
} else if (this.isLoading) {
return 'loading';
return SLOT_LOADING;
}
return 'error';
return SLOT_ERROR;
},
},
methods: {
......
......@@ -25,3 +25,11 @@ export const status = {
export const ACCESSIBILITY_ISSUE_ERROR = 'error';
export const ACCESSIBILITY_ISSUE_WARNING = 'warning';
/**
* Slot names for the ReportSection component, corresponding to the success,
* loading and error statuses.
*/
export const SLOT_SUCCESS = 'success';
export const SLOT_LOADING = 'loading';
export const SLOT_ERROR = 'error';
......@@ -242,6 +242,10 @@ export default class MergeRequestStore {
this.baseBlobPath = blobPath.base_path || '';
this.codequalityHelpPath = data.codequality_help_path;
this.codeclimate = data.codeclimate;
// Security reports
this.sastComparisonPath = data.sast_comparison_path;
this.secretScanningComparisonPath = data.secret_scanning_comparison_path;
}
get isNothingToMergeState() {
......
export const SEVERITY_CLASS_NAME_MAP = {
critical: 'text-danger-800',
high: 'text-danger-600',
medium: 'text-warning-400',
low: 'text-warning-200',
info: 'text-primary-400',
unknown: 'text-secondary-400',
};
export const FEEDBACK_TYPE_DISMISSAL = 'dismissal';
export const FEEDBACK_TYPE_ISSUE = 'issue';
export const FEEDBACK_TYPE_MERGE_REQUEST = 'merge_request';
/**
* Security scan report types, as provided by the backend.
*/
export const REPORT_TYPE_SAST = 'sast';
export const REPORT_TYPE_SECRET_DETECTION = 'secret_detection';
<script>
import { mapActions, mapGetters } from 'vuex';
import { GlIcon, GlLink, GlSprintf } from '@gitlab/ui';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import ReportSection from '~/reports/components/report_section.vue';
import { status } from '~/reports/constants';
import { LOADING, ERROR, SLOT_SUCCESS, SLOT_LOADING, SLOT_ERROR } from '~/reports/constants';
import { s__ } from '~/locale';
import { normalizeHeaders, parseIntPagination } from '~/lib/utils/common_utils';
import Flash from '~/flash';
import createFlash from '~/flash';
import Api from '~/api';
import SecuritySummary from './components/security_summary.vue';
import store from './store';
import { MODULE_SAST, MODULE_SECRET_DETECTION } from './store/constants';
import { REPORT_TYPE_SAST, REPORT_TYPE_SECRET_DETECTION } from './constants';
export default {
store,
components: {
GlIcon,
GlLink,
GlSprintf,
ReportSection,
SecuritySummary,
},
mixins: [glFeatureFlagsMixin()],
props: {
pipelineId: {
type: Number,
......@@ -27,25 +36,53 @@ export default {
type: String,
required: true,
},
sastComparisonPath: {
type: String,
required: false,
default: '',
},
secretScanningComparisonPath: {
type: String,
required: false,
default: '',
},
},
data() {
return {
hasSecurityReports: false,
availableSecurityReports: [],
canShowCounts: false,
// Error state is shown even when successfully loaded, since success
// When core_security_mr_widget_counts is not enabled, the
// error state is shown even when successfully loaded, since success
// state suggests that the security scans detected no security problems,
// which is not necessarily the case. A future iteration will actually
// check whether problems were found and display the appropriate status.
status: status.ERROR,
status: ERROR,
};
},
computed: {
...mapGetters(['groupedSummaryText', 'summaryStatus']),
hasSecurityReports() {
return this.availableSecurityReports.length > 0;
},
hasSastReports() {
return this.availableSecurityReports.includes(REPORT_TYPE_SAST);
},
hasSecretDetectionReports() {
return this.availableSecurityReports.includes(REPORT_TYPE_SECRET_DETECTION);
},
isLoaded() {
return this.summaryStatus !== LOADING;
},
},
created() {
this.checkHasSecurityReports(this.$options.reportTypes)
.then(hasSecurityReports => {
this.hasSecurityReports = hasSecurityReports;
this.checkAvailableSecurityReports(this.$options.reportTypes)
.then(availableSecurityReports => {
this.availableSecurityReports = Array.from(availableSecurityReports);
this.fetchCounts();
})
.catch(error => {
Flash({
createFlash({
message: this.$options.i18n.apiError,
captureError: true,
error,
......@@ -53,7 +90,18 @@ export default {
});
},
methods: {
async checkHasSecurityReports(reportTypes) {
...mapActions(MODULE_SAST, {
setSastDiffEndpoint: 'setDiffEndpoint',
fetchSastDiff: 'fetchDiff',
}),
...mapActions(MODULE_SECRET_DETECTION, {
setSecretDetectionDiffEndpoint: 'setDiffEndpoint',
fetchSecretDetectionDiff: 'fetchDiff',
}),
async checkAvailableSecurityReports(reportTypes) {
const reportTypesSet = new Set(reportTypes);
const availableReportTypes = new Set();
let page = 1;
while (page) {
// eslint-disable-next-line no-await-in-loop
......@@ -62,18 +110,40 @@ export default {
page,
});
const hasSecurityReports = jobs.some(({ artifacts = [] }) =>
artifacts.some(({ file_type }) => reportTypes.includes(file_type)),
);
jobs.forEach(({ artifacts = [] }) => {
artifacts.forEach(({ file_type }) => {
if (reportTypesSet.has(file_type)) {
availableReportTypes.add(file_type);
}
});
});
if (hasSecurityReports) {
return true;
// If we've found artifacts for all the report types, stop looking!
if (availableReportTypes.size === reportTypesSet.size) {
return availableReportTypes;
}
page = parseIntPagination(normalizeHeaders(headers)).nextPage;
}
return false;
return availableReportTypes;
},
fetchCounts() {
if (!this.glFeatures.coreSecurityMrWidgetCounts) {
return;
}
if (this.sastComparisonPath && this.hasSastReports) {
this.setSastDiffEndpoint(this.sastComparisonPath);
this.fetchSastDiff();
this.canShowCounts = true;
}
if (this.secretScanningComparisonPath && this.hasSecretDetectionReports) {
this.setSecretDetectionDiffEndpoint(this.secretScanningComparisonPath);
this.fetchSecretDetectionDiff();
this.canShowCounts = true;
}
},
activatePipelinesTab() {
if (window.mrTabs) {
......@@ -81,7 +151,7 @@ export default {
}
},
},
reportTypes: ['sast', 'secret_detection'],
reportTypes: [REPORT_TYPE_SAST, REPORT_TYPE_SECRET_DETECTION],
i18n: {
apiError: s__(
'SecurityReports|Failed to get security report information. Please reload the page or try again later.',
......@@ -89,13 +159,57 @@ export default {
scansHaveRun: s__(
'SecurityReports|Security scans have run. Go to the %{linkStart}pipelines tab%{linkEnd} to download the security reports',
),
downloadFromPipelineTab: s__(
'SecurityReports|Go to the %{linkStart}pipelines tab%{linkEnd} to download the security reports',
),
securityReportsHelp: s__('SecurityReports|Security reports help page link'),
},
summarySlots: [SLOT_SUCCESS, SLOT_LOADING, SLOT_ERROR],
};
</script>
<template>
<report-section
v-if="hasSecurityReports"
v-if="canShowCounts"
:status="summaryStatus"
:has-issues="false"
class="mr-widget-border-top mr-report"
data-testid="security-mr-widget"
>
<template v-for="slot in $options.summarySlots" #[slot]>
<span :key="slot">
<security-summary :message="groupedSummaryText" />
<gl-link
target="_blank"
data-testid="help"
:href="securityReportsDocsPath"
:aria-label="$options.i18n.securityReportsHelp"
>
<gl-icon name="question" />
</gl-link>
</span>
</template>
<template v-if="isLoaded" #sub-heading>
<span class="gl-font-sm">
<gl-sprintf :message="$options.i18n.downloadFromPipelineTab">
<template #link="{ content }">
<gl-link
class="gl-font-sm"
data-testid="show-pipelines"
@click="activatePipelinesTab"
>{{ content }}</gl-link
>
</template>
</gl-sprintf>
</span>
</template>
</report-section>
<!-- TODO: Remove this section when removing core_security_mr_widget_counts
feature flag. See https://gitlab.com/gitlab-org/gitlab/-/issues/284097 -->
<report-section
v-else-if="hasSecurityReports"
:status="status"
:has-issues="false"
class="mr-widget-border-top mr-report"
......
......@@ -39,6 +39,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:highlight_current_diff_row, @project)
push_frontend_feature_flag(:default_merge_ref_for_diffs, @project)
push_frontend_feature_flag(:core_security_mr_widget, @project, default_enabled: true)
push_frontend_feature_flag(:core_security_mr_widget_counts, @project)
push_frontend_feature_flag(:remove_resolve_note, @project, default_enabled: true)
push_frontend_feature_flag(:test_failure_history, @project)
......
---
name: core_security_mr_widget_counts
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47656
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/284097
milestone: '13.7'
type: development
group: group::static analysis
default_enabled: false
......@@ -318,6 +318,8 @@ export default {
:pipeline-id="mr.pipeline.id"
:project-id="mr.targetProjectId"
:security-reports-docs-path="mr.securityReportsDocsPath"
:sast-comparison-path="mr.sastComparisonPath"
:secret-scanning-comparison-path="mr.secretScanningComparisonPath"
/>
<grouped-security-reports-app
v-else-if="shouldRenderExtendedSecurityReport"
......@@ -349,6 +351,12 @@ export default {
:mr-state="mr.state"
:target-branch-tree-path="mr.targetBranchTreePath"
:new-pipeline-path="mr.newPipelinePath"
:container-scanning-comparison-path="mr.containerScanningComparisonPath"
:coverage-fuzzing-comparison-path="mr.coverageFuzzingComparisonPath"
:dast-comparison-path="mr.dastComparisonPath"
:dependency-scanning-comparison-path="mr.dependencyScanningComparisonPath"
:sast-comparison-path="mr.sastComparisonPath"
:secret-scanning-comparison-path="mr.secretScanningComparisonPath"
class="js-security-widget"
/>
<mr-widget-licenses
......
......@@ -50,6 +50,17 @@ export default class MergeRequestStore extends CEMergeRequestStore {
super.setData(data, isRebased);
}
setPaths(data) {
// Paths are set on the first load of the page and not auto-refreshed
super.setPaths(data);
// Security scan diff paths
this.containerScanningComparisonPath = data.container_scanning_comparison_path;
this.coverageFuzzingComparisonPath = data.coverage_fuzzing_comparison_path;
this.dastComparisonPath = data.dast_comparison_path;
this.dependencyScanningComparisonPath = data.dependency_scanning_comparison_path;
}
initGeo(data) {
this.isGeoSecondaryNode = this.isGeoSecondaryNode || data.is_geo_secondary_node;
this.geoSecondaryHelpPath = this.geoSecondaryHelpPath || data.geo_secondary_help_path;
......
import { s__ } from '~/locale';
export const SEVERITY_CLASS_NAME_MAP = {
critical: 'text-danger-800',
high: 'text-danger-600',
medium: 'text-warning-400',
low: 'text-warning-200',
info: 'text-primary-400',
unknown: 'text-secondary-400',
};
export * from '~/vue_shared/security_reports/components/constants';
export const SEVERITY_TOOLTIP_TITLE_MAP = {
unknown: s__(
......
......@@ -9,6 +9,7 @@ import ReportSection from '~/reports/components/report_section.vue';
import SummaryRow from '~/reports/components/summary_row.vue';
import Tracking from '~/tracking';
import GroupedIssuesList from '~/reports/components/grouped_issues_list.vue';
import SecuritySummary from '~/vue_shared/security_reports/components/security_summary.vue';
import IssueModal from './components/modal.vue';
import DastModal from './components/dast_modal.vue';
import securityReportsMixin from './mixins/security_report_mixin';
......@@ -16,7 +17,6 @@ import createStore from './store';
import { mrStates } from '~/mr_popover/constants';
import { fetchPolicies } from '~/lib/graphql';
import securityReportSummaryQuery from './graphql/mr_security_report_summary.graphql';
import SecuritySummary from './components/security_summary.vue';
import {
MODULE_CONTAINER_SCANNING,
MODULE_COVERAGE_FUZZING,
......@@ -190,6 +190,36 @@ export default {
type: String,
required: true,
},
containerScanningComparisonPath: {
type: String,
required: false,
default: '',
},
coverageFuzzingComparisonPath: {
type: String,
required: false,
default: '',
},
dastComparisonPath: {
type: String,
required: false,
default: '',
},
dependencyScanningComparisonPath: {
type: String,
required: false,
default: '',
},
sastComparisonPath: {
type: String,
required: false,
default: '',
},
secretScanningComparisonPath: {
type: String,
required: false,
default: '',
},
},
componentNames,
computed: {
......@@ -305,44 +335,33 @@ export default {
this.setPipelineId(this.pipelineId);
this.setPipelineJobsId(this.pipelineId);
const sastDiffEndpoint = gl?.mrWidgetData?.sast_comparison_path;
if (sastDiffEndpoint && this.hasSastReports) {
this.setSastDiffEndpoint(sastDiffEndpoint);
if (this.sastComparisonPath && this.hasSastReports) {
this.setSastDiffEndpoint(this.sastComparisonPath);
this.fetchSastDiff();
}
const containerScanningDiffEndpoint = gl?.mrWidgetData?.container_scanning_comparison_path;
if (containerScanningDiffEndpoint && this.hasContainerScanningReports) {
this.setContainerScanningDiffEndpoint(containerScanningDiffEndpoint);
if (this.containerScanningComparisonPath && this.hasContainerScanningReports) {
this.setContainerScanningDiffEndpoint(this.containerScanningComparisonPath);
this.fetchContainerScanningDiff();
}
const dastDiffEndpoint = gl?.mrWidgetData?.dast_comparison_path;
if (dastDiffEndpoint && this.hasDastReports) {
this.setDastDiffEndpoint(dastDiffEndpoint);
if (this.dastComparisonPath && this.hasDastReports) {
this.setDastDiffEndpoint(this.dastComparisonPath);
this.fetchDastDiff();
}
const dependencyScanningDiffEndpoint = gl?.mrWidgetData?.dependency_scanning_comparison_path;
if (dependencyScanningDiffEndpoint && this.hasDependencyScanningReports) {
this.setDependencyScanningDiffEndpoint(dependencyScanningDiffEndpoint);
if (this.dependencyScanningComparisonPath && this.hasDependencyScanningReports) {
this.setDependencyScanningDiffEndpoint(this.dependencyScanningComparisonPath);
this.fetchDependencyScanningDiff();
}
const secretDetectionDiffEndpoint = gl?.mrWidgetData?.secret_scanning_comparison_path;
if (secretDetectionDiffEndpoint && this.hasSecretDetectionReports) {
this.setSecretDetectionDiffEndpoint(secretDetectionDiffEndpoint);
if (this.secretScanningComparisonPath && this.hasSecretDetectionReports) {
this.setSecretDetectionDiffEndpoint(this.secretScanningComparisonPath);
this.fetchSecretDetectionDiff();
}
const coverageFuzzingDiffEndpoint = gl?.mrWidgetData?.coverage_fuzzing_comparison_path;
if (coverageFuzzingDiffEndpoint && this.hasCoverageFuzzingReports) {
this.setCoverageFuzzingDiffEndpoint(coverageFuzzingDiffEndpoint);
if (this.coverageFuzzingComparisonPath && this.hasCoverageFuzzingReports) {
this.setCoverageFuzzingDiffEndpoint(this.coverageFuzzingComparisonPath);
this.fetchCoverageFuzzingDiff();
this.fetchPipelineJobs();
}
......
......@@ -12,6 +12,10 @@ export default {
license_management: false,
secret_detection: false,
},
container_scanning_comparison_path: '/container_scanning_comparison_path',
dependency_scanning_comparison_path: '/dependency_scanning_comparison_path',
dast_comparison_path: '/dast_comparison_path',
coverage_fuzzing_comparison_path: '/coverage_fuzzing_comparison_path',
};
// Browser Performance Testing
......
import MergeRequestStore from 'ee/vue_merge_request_widget/stores/mr_widget_store';
import mockData from 'ee_jest/vue_mr_widget/mock_data';
import { stateKey } from '~/vue_merge_request_widget/stores/state_maps';
import { convertToCamelCase } from '~/lib/utils/text_utility';
describe('MergeRequestStore', () => {
let store;
......@@ -66,4 +67,23 @@ describe('MergeRequestStore', () => {
});
});
});
describe('setPaths', () => {
it.each([
'container_scanning_comparison_path',
'dependency_scanning_comparison_path',
'sast_comparison_path',
'dast_comparison_path',
'secret_scanning_comparison_path',
'coverage_fuzzing_comparison_path',
])('should set %s path', property => {
// Ensure something is set in the mock data
expect(property in mockData).toBe(true);
const expectedValue = mockData[property];
store.setPaths({ ...mockData });
expect(store[convertToCamelCase(property)]).toBe(expectedValue);
});
});
});
......@@ -54,6 +54,12 @@ describe('Grouped security reports app', () => {
pipelineId: 123,
projectId: 321,
projectFullPath: 'path',
containerScanningComparisonPath: CONTAINER_SCANNING_DIFF_ENDPOINT,
coverageFuzzingComparisonPath: COVERAGE_FUZZING_DIFF_ENDPOINT,
dastComparisonPath: DAST_DIFF_ENDPOINT,
dependencyScanningComparisonPath: DEPENDENCY_SCANNING_DIFF_ENDPOINT,
sastComparisonPath: SAST_DIFF_ENDPOINT,
secretScanningComparisonPath: SECRET_DETECTION_DIFF_ENDPOINT,
};
const defaultDastSummary = {
......@@ -112,16 +118,6 @@ describe('Grouped security reports app', () => {
},
};
beforeEach(() => {
gl.mrWidgetData = gl.mrWidgetData || {};
gl.mrWidgetData.container_scanning_comparison_path = CONTAINER_SCANNING_DIFF_ENDPOINT;
gl.mrWidgetData.dependency_scanning_comparison_path = DEPENDENCY_SCANNING_DIFF_ENDPOINT;
gl.mrWidgetData.dast_comparison_path = DAST_DIFF_ENDPOINT;
gl.mrWidgetData.sast_comparison_path = SAST_DIFF_ENDPOINT;
gl.mrWidgetData.secret_scanning_comparison_path = SECRET_DETECTION_DIFF_ENDPOINT;
gl.mrWidgetData.coverage_fuzzing_comparison_path = COVERAGE_FUZZING_DIFF_ENDPOINT;
});
describe('with error', () => {
beforeEach(() => {
mock.onGet(CONTAINER_SCANNING_DIFF_ENDPOINT).reply(500);
......@@ -394,41 +390,32 @@ describe('Grouped security reports app', () => {
});
describe('coverage fuzzing reports', () => {
describe.each([true, false])(
'given coverage fuzzing comparison endpoint is /fuzzing and featureEnabled is %s',
shouldShowFuzzing => {
beforeEach(() => {
gl.mrWidgetData = gl.mrWidgetData || {};
gl.mrWidgetData.coverage_fuzzing_comparison_path = '/fuzzing';
createWrapper(
{
...props,
enabledReports: {
coverageFuzzing: true,
},
},
{},
{
glFeatures: { coverageFuzzingMrWidget: shouldShowFuzzing },
describe.each([true, false])('given featureEnabled is %s', shouldShowFuzzing => {
beforeEach(() => {
createWrapper(
{
...props,
enabledReports: {
coverageFuzzing: true,
},
);
});
},
{},
{
glFeatures: { coverageFuzzingMrWidget: shouldShowFuzzing },
},
);
});
it(`${shouldShowFuzzing ? 'renders' : 'does not render'}`, () => {
expect(wrapper.find('[data-qa-selector="coverage_fuzzing_report"]').exists()).toBe(
shouldShowFuzzing,
);
});
},
);
it(`${shouldShowFuzzing ? 'renders' : 'does not render'}`, () => {
expect(wrapper.find('[data-qa-selector="coverage_fuzzing_report"]').exists()).toBe(
shouldShowFuzzing,
);
});
});
});
describe('container scanning reports', () => {
beforeEach(() => {
gl.mrWidgetData = gl.mrWidgetData || {};
gl.mrWidgetData.container_scanning_comparison_path = CONTAINER_SCANNING_DIFF_ENDPOINT;
mock.onGet(CONTAINER_SCANNING_DIFF_ENDPOINT).reply(200, containerScanningDiffSuccessMock);
createWrapper({
......@@ -456,9 +443,6 @@ describe('Grouped security reports app', () => {
describe('dependency scanning reports', () => {
beforeEach(() => {
gl.mrWidgetData = gl.mrWidgetData || {};
gl.mrWidgetData.dependency_scanning_comparison_path = DEPENDENCY_SCANNING_DIFF_ENDPOINT;
mock.onGet(DEPENDENCY_SCANNING_DIFF_ENDPOINT).reply(200, dependencyScanningDiffSuccessMock);
createWrapper({
......@@ -486,9 +470,6 @@ describe('Grouped security reports app', () => {
describe('dast reports', () => {
beforeEach(() => {
gl.mrWidgetData = gl.mrWidgetData || {};
gl.mrWidgetData.dast_comparison_path = DAST_DIFF_ENDPOINT;
mock.onGet(DAST_DIFF_ENDPOINT).reply(200, {
...dastDiffSuccessMock,
base_report_out_of_date: true,
......@@ -562,9 +543,6 @@ describe('Grouped security reports app', () => {
describe('secret scanning reports', () => {
const initSecretScan = (isEnabled = true) => {
gl.mrWidgetData = gl.mrWidgetData || {};
gl.mrWidgetData.secret_scanning_comparison_path = SECRET_DETECTION_DIFF_ENDPOINT;
mock.onGet(SECRET_DETECTION_DIFF_ENDPOINT).reply(200, secretScanningDiffSuccessMock);
createWrapper({
......@@ -615,9 +593,6 @@ describe('Grouped security reports app', () => {
describe('sast reports', () => {
beforeEach(() => {
gl.mrWidgetData = gl.mrWidgetData || {};
gl.mrWidgetData.sast_comparison_path = SAST_DIFF_ENDPOINT;
mock.onGet(SAST_DIFF_ENDPOINT).reply(200, { ...sastDiffSuccessMock });
createWrapper({
......@@ -643,9 +618,6 @@ describe('Grouped security reports app', () => {
describe('Out of date report', () => {
const createComponent = (extraProp, done) => {
gl.mrWidgetData = gl.mrWidgetData || {};
gl.mrWidgetData.sast_comparison_path = SAST_DIFF_ENDPOINT;
mock
.onGet(SAST_DIFF_ENDPOINT)
.reply(200, { ...sastDiffSuccessMock, base_report_out_of_date: true });
......
......@@ -24171,6 +24171,9 @@ msgstr ""
msgid "SecurityReports|Fuzzing artifacts"
msgstr ""
msgid "SecurityReports|Go to the %{linkStart}pipelines tab%{linkEnd} to download the security reports"
msgstr ""
msgid "SecurityReports|Hide dismissed"
msgstr ""
......
......@@ -264,6 +264,8 @@ export default {
merge_trains_count: 3,
merge_train_index: 1,
security_reports_docs_path: 'security-reports-docs-path',
sast_comparison_path: '/sast_comparison_path',
secret_scanning_comparison_path: '/secret_scanning_comparison_path',
};
export const mockStore = {
......
import { convertToCamelCase } from '~/lib/utils/text_utility';
import MergeRequestStore from '~/vue_merge_request_widget/stores/mr_widget_store';
import { stateKey } from '~/vue_merge_request_widget/stores/state_maps';
import mockData from '../mock_data';
......@@ -152,5 +153,18 @@ describe('MergeRequestStore', () => {
expect(store.securityReportsDocsPath).toBe('security-reports-docs-path');
});
it.each(['sast_comparison_path', 'secret_scanning_comparison_path'])(
'should set %s path',
property => {
// Ensure something is set in the mock data
expect(property in mockData).toBe(true);
const expectedValue = mockData[property];
store.setPaths({ ...mockData });
expect(store[convertToCamelCase(property)]).toBe(expectedValue);
},
);
});
});
import { GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import SecuritySummary from 'ee/vue_shared/security_reports/components/security_summary.vue';
import { groupedTextBuilder } from 'ee/vue_shared/security_reports/store/utils';
import SecuritySummary from '~/vue_shared/security_reports/components/security_summary.vue';
import { groupedTextBuilder } from '~/vue_shared/security_reports/store/utils';
describe('Severity Summary', () => {
let wrapper;
......
This diff is collapsed.
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