Commit 8efc7db5 authored by Fernando's avatar Fernando

Part 2 - Artifact download migration to graphql

* Use updated components articact download components
* Update unit tests
* Regenerate pot file
* Update imports

Remove import from fuzzing artifacts component

Update security_dashboard_vuex_spec.js

* Update unit tests

Fix broken pipeline

* Fix unit tests and missing props

Rename import

Fix alignment issues on pipelines page

Resolve rebase conflict

* Change pipeline.Iid to pipeline.iid

Fix linter error

* Fix linter failure from rebase

Rework reportType array

* Rework into $options

Add label slot to allow for custom labels

* Add dropdown label
parent 74da7a21
query securityReportDownloadPaths(
$projectPath: ID!
$iid: String!
$reportTypes: [SecurityReportTypeEnum!]
) {
project(fullPath: $projectPath) {
mergeRequest(iid: $iid) {
headPipeline {
id
jobs(securityReportTypes: $reportTypes) {
nodes {
name
artifacts {
nodes {
downloadPath
fileType
}
}
}
}
}
}
}
}
...@@ -13,7 +13,7 @@ import { ...@@ -13,7 +13,7 @@ import {
REPORT_TYPE_SECRET_DETECTION, REPORT_TYPE_SECRET_DETECTION,
reportTypeToSecurityReportTypeEnum, reportTypeToSecurityReportTypeEnum,
} from './constants'; } from './constants';
import securityReportDownloadPathsQuery from './queries/security_report_download_paths.query.graphql'; import securityReportMergeRequestDownloadPathsQuery from './queries/security_report_merge_request_download_paths.query.graphql';
import store from './store'; import store from './store';
import { MODULE_SAST, MODULE_SECRET_DETECTION } from './store/constants'; import { MODULE_SAST, MODULE_SECRET_DETECTION } from './store/constants';
import { extractSecurityReportArtifactsFromMergeRequest } from './utils'; import { extractSecurityReportArtifactsFromMergeRequest } from './utils';
...@@ -86,7 +86,7 @@ export default { ...@@ -86,7 +86,7 @@ export default {
}, },
apollo: { apollo: {
reportArtifacts: { reportArtifacts: {
query: securityReportDownloadPathsQuery, query: securityReportMergeRequestDownloadPathsQuery,
variables() { variables() {
return { return {
projectPath: this.targetProjectFullPath, projectPath: this.targetProjectFullPath,
......
<script>
import { GlButton, GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { s__ } from '~/locale';
export default {
i18n: {
FUZZING_ARTIFACTS: s__('SecurityReports|Fuzzing artifacts'),
},
components: {
GlButton,
GlDropdown,
GlDropdownItem,
},
props: {
jobs: {
type: Array,
required: true,
},
projectId: {
type: Number,
required: true,
},
},
computed: {
hasDropdown() {
return this.jobs.length > 1;
},
},
methods: {
artifactDownloadUrl(job) {
return `/api/v4/projects/${this.projectId}/jobs/artifacts/${
job.ref
}/download?job=${encodeURIComponent(job.name)}`;
},
},
};
</script>
<template>
<div>
<slot name="label"></slot>
<gl-dropdown
v-if="hasDropdown"
class="d-block mt-1"
:text="$options.i18n.FUZZING_ARTIFACTS"
category="secondary"
size="small"
>
<gl-dropdown-item v-for="job in jobs" :key="job.id" :href="artifactDownloadUrl(job)">{{
job.name
}}</gl-dropdown-item>
</gl-dropdown>
<gl-button
v-else
class="d-block mt-1"
category="secondary"
size="small"
:href="artifactDownloadUrl(jobs[0])"
>
{{ $options.i18n.FUZZING_ARTIFACTS }}
</gl-button>
</div>
</template>
...@@ -89,6 +89,8 @@ export default { ...@@ -89,6 +89,8 @@ export default {
:vulnerabilities-endpoint="vulnerabilitiesEndpoint" :vulnerabilities-endpoint="vulnerabilitiesEndpoint"
:lock-to-project="{ id: projectId }" :lock-to-project="{ id: projectId }"
:pipeline-id="pipeline.id" :pipeline-id="pipeline.id"
:pipeline-iid="pipeline.iid"
:project-full-path="projectFullPath"
:loading-error-illustrations="loadingErrorIllustrations" :loading-error-illustrations="loadingErrorIllustrations"
:security-report-summary="securityReportSummary" :security-report-summary="securityReportSummary"
> >
......
<script> <script>
import { mapActions, mapState, mapGetters } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex';
import PipelineArtifactDownload from 'ee/vue_shared/security_reports/components/artifact_downloads/pipeline_artifact_download.vue';
import IssueModal from 'ee/vue_shared/security_reports/components/modal.vue'; import IssueModal from 'ee/vue_shared/security_reports/components/modal.vue';
import { securityReportTypeEnumToReportType } from 'ee/vue_shared/security_reports/constants';
import { vulnerabilityModalMixin } from 'ee/vue_shared/security_reports/mixins/vulnerability_modal_mixin'; import { vulnerabilityModalMixin } from 'ee/vue_shared/security_reports/mixins/vulnerability_modal_mixin';
import Filters from './filters.vue'; import Filters from './filters.vue';
import FuzzingArtifactsDownload from './fuzzing_artifacts_download.vue';
import LoadingError from './loading_error.vue'; import LoadingError from './loading_error.vue';
import SecurityDashboardLayout from './security_dashboard_layout.vue'; import SecurityDashboardLayout from './security_dashboard_layout.vue';
import SecurityDashboardTable from './security_dashboard_table.vue'; import SecurityDashboardTable from './security_dashboard_table.vue';
...@@ -14,8 +15,8 @@ export default { ...@@ -14,8 +15,8 @@ export default {
IssueModal, IssueModal,
SecurityDashboardLayout, SecurityDashboardLayout,
SecurityDashboardTable, SecurityDashboardTable,
FuzzingArtifactsDownload,
LoadingError, LoadingError,
PipelineArtifactDownload,
}, },
mixins: [vulnerabilityModalMixin('vulnerabilities')], mixins: [vulnerabilityModalMixin('vulnerabilities')],
props: { props: {
...@@ -23,11 +24,20 @@ export default { ...@@ -23,11 +24,20 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
projectFullPath: {
type: String,
required: true,
},
pipelineId: { pipelineId: {
type: Number, type: Number,
required: false, required: false,
default: null, default: null,
}, },
pipelineIid: {
type: Number,
required: false,
default: null,
},
loadingErrorIllustrations: { loadingErrorIllustrations: {
type: Object, type: Object,
required: false, required: false,
...@@ -46,7 +56,9 @@ export default { ...@@ -46,7 +56,9 @@ export default {
...mapState('pipelineJobs', ['projectId']), ...mapState('pipelineJobs', ['projectId']),
...mapState('filters', ['filters']), ...mapState('filters', ['filters']),
...mapGetters('vulnerabilities', ['loadingVulnerabilitiesFailedWithRecognizedErrorCode']), ...mapGetters('vulnerabilities', ['loadingVulnerabilitiesFailedWithRecognizedErrorCode']),
...mapGetters('pipelineJobs', ['hasFuzzingArtifacts', 'fuzzingJobsWithArtifact']), shouldShowDownloadGuidance() {
return this.projectFullPath && this.pipelineIid;
},
canCreateIssue() { canCreateIssue() {
const path = this.vulnerability.create_vulnerability_feedback_issue_path; const path = this.vulnerability.create_vulnerability_feedback_issue_path;
return Boolean(path); return Boolean(path);
...@@ -83,6 +95,9 @@ export default { ...@@ -83,6 +95,9 @@ export default {
...mapActions('pipelineJobs', ['fetchPipelineJobs']), ...mapActions('pipelineJobs', ['fetchPipelineJobs']),
...mapActions('filters', ['lockFilter', 'setHideDismissedToggleInitialState']), ...mapActions('filters', ['lockFilter', 'setHideDismissedToggleInitialState']),
}, },
reportTypes: {
COVERAGE_FUZZING: [securityReportTypeEnumToReportType.COVERAGE_FUZZING],
},
}; };
</script> </script>
...@@ -97,12 +112,17 @@ export default { ...@@ -97,12 +112,17 @@ export default {
<security-dashboard-layout> <security-dashboard-layout>
<template #header> <template #header>
<filters> <filters>
<template v-if="hasFuzzingArtifacts" #buttons> <template v-if="shouldShowDownloadGuidance" #buttons>
<fuzzing-artifacts-download :jobs="fuzzingJobsWithArtifact" :project-id="projectId"> <pipeline-artifact-download
class="gl-display-flex gl-flex-direction-column gl-align-self-center"
:report-types="$options.reportTypes.COVERAGE_FUZZING"
:target-project-full-path="projectFullPath"
:pipeline-iid="pipelineIid"
>
<template #label> <template #label>
<strong>{{ s__('SecurityReports|Download Report') }}</strong> <strong class="gl-mb-2">{{ s__('SecurityReports|Download Report') }}</strong>
</template> </template>
</fuzzing-artifacts-download> </pipeline-artifact-download>
</template> </template>
</filters> </filters>
</template> </template>
......
<script>
import { reportTypeToSecurityReportTypeEnum } from 'ee/vue_shared/security_reports/constants';
import createFlash from '~/flash';
import { s__ } from '~/locale';
import SecurityReportDownloadDropdown from '~/vue_shared/security_reports/components/security_report_download_dropdown.vue';
import securityReportDownloadPathsQuery from '~/vue_shared/security_reports/queries/security_report_download_paths.query.graphql';
import { extractSecurityReportArtifactsFromMergeRequest } from '~/vue_shared/security_reports/utils';
export default {
components: {
SecurityReportDownloadDropdown,
},
props: {
reportTypes: {
type: Array,
required: true,
validator: (reportType) => {
return reportType.every((report) => reportTypeToSecurityReportTypeEnum[report]);
},
},
targetProjectFullPath: {
type: String,
required: true,
},
mrIid: {
type: Number,
required: true,
},
},
data() {
return {
reportArtifacts: [],
};
},
apollo: {
reportArtifacts: {
query: securityReportDownloadPathsQuery,
variables() {
return {
projectPath: this.targetProjectFullPath,
iid: String(this.mrIid),
reportTypes: this.reportTypes.map(
(reportType) => reportTypeToSecurityReportTypeEnum[reportType],
),
};
},
update(data) {
return extractSecurityReportArtifactsFromMergeRequest(this.reportTypes, data);
},
error(error) {
this.showError(error);
},
},
},
computed: {
isLoadingReportArtifacts() {
return this.$apollo.queries.reportArtifacts.loading;
},
},
methods: {
showError(error) {
createFlash({
message: this.$options.i18n.apiError,
captureError: true,
error,
});
},
},
i18n: {
apiError: s__(
'SecurityReports|Failed to get security report information. Please reload the page or try again later.',
),
},
};
</script>
<template>
<security-report-download-dropdown
:artifacts="reportArtifacts"
:loading="isLoadingReportArtifacts"
/>
</template>
...@@ -3,7 +3,7 @@ import { reportTypeToSecurityReportTypeEnum } from 'ee/vue_shared/security_repor ...@@ -3,7 +3,7 @@ import { reportTypeToSecurityReportTypeEnum } from 'ee/vue_shared/security_repor
import createFlash from '~/flash'; import createFlash from '~/flash';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import SecurityReportDownloadDropdown from '~/vue_shared/security_reports/components/security_report_download_dropdown.vue'; import SecurityReportDownloadDropdown from '~/vue_shared/security_reports/components/security_report_download_dropdown.vue';
import securityReportDownloadPathsQuery from '~/vue_shared/security_reports/queries/security_report_mr_download_paths.query.graphql'; import securityReportMergeRequestDownloadPathsQuery from '~/vue_shared/security_reports/queries/security_report_merge_request_download_paths.query.graphql';
import { extractSecurityReportArtifactsFromMergeRequest } from '~/vue_shared/security_reports/utils'; import { extractSecurityReportArtifactsFromMergeRequest } from '~/vue_shared/security_reports/utils';
export default { export default {
...@@ -34,7 +34,7 @@ export default { ...@@ -34,7 +34,7 @@ export default {
}, },
apollo: { apollo: {
reportArtifacts: { reportArtifacts: {
query: securityReportDownloadPathsQuery, query: securityReportMergeRequestDownloadPathsQuery,
variables() { variables() {
return { return {
projectPath: this.targetProjectFullPath, projectPath: this.targetProjectFullPath,
......
...@@ -3,7 +3,7 @@ import { reportTypeToSecurityReportTypeEnum } from 'ee/vue_shared/security_repor ...@@ -3,7 +3,7 @@ import { reportTypeToSecurityReportTypeEnum } from 'ee/vue_shared/security_repor
import createFlash from '~/flash'; import createFlash from '~/flash';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import SecurityReportDownloadDropdown from '~/vue_shared/security_reports/components/security_report_download_dropdown.vue'; import SecurityReportDownloadDropdown from '~/vue_shared/security_reports/components/security_report_download_dropdown.vue';
import securityReportDownloadPathsQuery from '~/vue_shared/security_reports/queries/security_report_pipeline_download_paths.query.graphql'; import securityReportPipelineDownloadPathsQuery from '~/vue_shared/security_reports/queries/security_report_pipeline_download_paths.query.graphql';
import { extractSecurityReportArtifactsFromPipeline } from '~/vue_shared/security_reports/utils'; import { extractSecurityReportArtifactsFromPipeline } from '~/vue_shared/security_reports/utils';
export default { export default {
...@@ -34,11 +34,11 @@ export default { ...@@ -34,11 +34,11 @@ export default {
}, },
apollo: { apollo: {
reportArtifacts: { reportArtifacts: {
query: securityReportDownloadPathsQuery, query: securityReportPipelineDownloadPathsQuery,
variables() { variables() {
return { return {
projectPath: this.targetProjectFullPath, projectPath: this.targetProjectFullPath,
iid: String(this.mrIid), iid: String(this.pipelineIid),
reportTypes: this.reportTypes.map( reportTypes: this.reportTypes.map(
(reportType) => reportTypeToSecurityReportTypeEnum[reportType], (reportType) => reportTypeToSecurityReportTypeEnum[reportType],
), ),
...@@ -75,8 +75,11 @@ export default { ...@@ -75,8 +75,11 @@ export default {
</script> </script>
<template> <template>
<div>
<slot name="label"></slot>
<security-report-download-dropdown <security-report-download-dropdown
:artifacts="reportArtifacts" :artifacts="reportArtifacts"
:loading="isLoadingReportArtifacts" :loading="isLoadingReportArtifacts"
/> />
</div>
</template> </template>
...@@ -3,7 +3,6 @@ import { GlButton, GlSprintf, GlLink, GlModalDirective } from '@gitlab/ui'; ...@@ -3,7 +3,6 @@ import { GlButton, GlSprintf, GlLink, GlModalDirective } from '@gitlab/ui';
import { once } from 'lodash'; import { once } from 'lodash';
import { mapActions, mapState, mapGetters } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex';
import { componentNames } from 'ee/reports/components/issue_body'; import { componentNames } from 'ee/reports/components/issue_body';
import FuzzingArtifactsDownload from 'ee/security_dashboard/components/fuzzing_artifacts_download.vue';
import { fetchPolicies } from '~/lib/graphql'; import { fetchPolicies } from '~/lib/graphql';
import { mrStates } from '~/mr_popover/constants'; import { mrStates } from '~/mr_popover/constants';
import GroupedIssuesList from '~/reports/components/grouped_issues_list.vue'; import GroupedIssuesList from '~/reports/components/grouped_issues_list.vue';
...@@ -12,7 +11,7 @@ import SummaryRow from '~/reports/components/summary_row.vue'; ...@@ -12,7 +11,7 @@ import SummaryRow from '~/reports/components/summary_row.vue';
import { LOADING } from '~/reports/constants'; import { LOADING } from '~/reports/constants';
import Tracking from '~/tracking'; import Tracking from '~/tracking';
import SecuritySummary from '~/vue_shared/security_reports/components/security_summary.vue'; import SecuritySummary from '~/vue_shared/security_reports/components/security_summary.vue';
import ArtifactDownload from './components/artifact_download.vue'; import MrArtifactDownload from './components/artifact_downloads/merge_request_artifact_download.vue';
import DastModal from './components/dast_modal.vue'; import DastModal from './components/dast_modal.vue';
import IssueModal from './components/modal.vue'; import IssueModal from './components/modal.vue';
import { securityReportTypeEnumToReportType } from './constants'; import { securityReportTypeEnumToReportType } from './constants';
...@@ -34,7 +33,7 @@ import { ...@@ -34,7 +33,7 @@ import {
export default { export default {
store: createStore(), store: createStore(),
components: { components: {
ArtifactDownload, MrArtifactDownload,
GroupedIssuesList, GroupedIssuesList,
ReportSection, ReportSection,
SummaryRow, SummaryRow,
...@@ -44,7 +43,6 @@ export default { ...@@ -44,7 +43,6 @@ export default {
GlLink, GlLink,
DastModal, DastModal,
GlButton, GlButton,
FuzzingArtifactsDownload,
}, },
directives: { directives: {
'gl-modal': GlModalDirective, 'gl-modal': GlModalDirective,
...@@ -277,7 +275,6 @@ export default { ...@@ -277,7 +275,6 @@ export default {
'secretDetectionStatusIcon', 'secretDetectionStatusIcon',
]), ]),
...mapGetters(MODULE_API_FUZZING, ['groupedApiFuzzingText', 'apiFuzzingStatusIcon']), ...mapGetters(MODULE_API_FUZZING, ['groupedApiFuzzingText', 'apiFuzzingStatusIcon']),
...mapGetters('pipelineJobs', ['hasFuzzingArtifacts', 'fuzzingJobsWithArtifact']),
securityTab() { securityTab() {
return `${this.pipelinePath}/security`; return `${this.pipelinePath}/security`;
}, },
...@@ -447,7 +444,10 @@ export default { ...@@ -447,7 +444,10 @@ export default {
}, },
}, },
summarySlots: ['success', 'error', 'loading'], summarySlots: ['success', 'error', 'loading'],
reportTypes: securityReportTypeEnumToReportType, reportTypes: {
API_FUZZING: [securityReportTypeEnumToReportType.API_FUZZING],
COVERAGE_FUZZING: [securityReportTypeEnumToReportType.COVERAGE_FUZZING],
},
}; };
</script> </script>
<template> <template>
...@@ -651,10 +651,11 @@ export default { ...@@ -651,10 +651,11 @@ export default {
<template #summary> <template #summary>
<security-summary :message="groupedCoverageFuzzingText" /> <security-summary :message="groupedCoverageFuzzingText" />
</template> </template>
<fuzzing-artifacts-download <mr-artifact-download
v-if="hasFuzzingArtifacts" v-if="shouldShowDownloadGuidance"
:jobs="fuzzingJobsWithArtifact" :report-types="$options.reportTypes.COVERAGE_FUZZING"
:project-id="projectId" :target-project-full-path="targetProjectFullPath"
:mr-iid="mrIid"
/> />
</summary-row> </summary-row>
...@@ -680,9 +681,9 @@ export default { ...@@ -680,9 +681,9 @@ export default {
<security-summary :message="groupedApiFuzzingText" /> <security-summary :message="groupedApiFuzzingText" />
</template> </template>
<artifact-download <mr-artifact-download
v-if="shouldShowDownloadGuidance" v-if="shouldShowDownloadGuidance"
:report-types="[$options.reportTypes.API_FUZZING]" :report-types="$options.reportTypes.API_FUZZING"
:target-project-full-path="targetProjectFullPath" :target-project-full-path="targetProjectFullPath"
:mr-iid="mrIid" :mr-iid="mrIid"
/> />
......
import { GlButton, GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import FuzzingArtifactsDownload from 'ee/security_dashboard/components/fuzzing_artifacts_download.vue';
import createStore from 'ee/security_dashboard/store';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('Filter component', () => {
const projectId = 1;
const jobs = [
{ ref: 'main', name: 'fuzz' },
{ ref: 'main', name: 'fuzz 2' },
];
let wrapper;
let store;
const createWrapper = (props = {}) => {
wrapper = shallowMount(FuzzingArtifactsDownload, {
localVue,
store,
propsData: {
projectId,
...props,
},
});
};
beforeEach(() => {
store = createStore();
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('with one fuzzing job with artifacts', () => {
beforeEach(() => {
createWrapper({ jobs: [jobs[0]] });
});
it('should render a download button', () => {
expect(wrapper.find(GlButton).exists()).toBe(true);
expect(wrapper.find(GlDropdown).exists()).toBe(false);
});
it('should render with href set to the correct filepath', () => {
const href = `/api/v4/projects/${projectId}/jobs/artifacts/${
jobs[0].ref
}/download?job=${encodeURIComponent(jobs[0].name)}`;
expect(wrapper.find(GlButton).attributes('href')).toBe(href);
});
});
describe('with several fuzzing jobs with artifacts', () => {
beforeEach(() => {
createWrapper({ jobs });
});
it('should render a dropdown button with several items', () => {
expect(wrapper.find(GlButton).exists()).toBe(false);
expect(wrapper.find(GlDropdown).exists()).toBe(true);
expect(wrapper.findAll(GlDropdownItem).length).toBe(2);
});
it('should render with href set to the correct filepath for every element', () => {
const wrapperArray = wrapper.findAll(GlDropdownItem);
wrapperArray.wrappers.forEach((_, index) => {
const href = `/api/v4/projects/${projectId}/jobs/artifacts/${
jobs[index].ref
}/download?job=${encodeURIComponent(jobs[index].name)}`;
expect(wrapperArray.at(index).attributes().href).toBe(href);
});
});
});
});
...@@ -52,6 +52,7 @@ describe('Security Dashboard component', () => { ...@@ -52,6 +52,7 @@ describe('Security Dashboard component', () => {
}, },
propsData: { propsData: {
dashboardDocumentation: '', dashboardDocumentation: '',
projectFullPath: '/path',
vulnerabilitiesEndpoint, vulnerabilitiesEndpoint,
pipelineId, pipelineId,
...props, ...props,
......
// import createState from 'ee/security_dashboard/store/modules/pipeline_jobs/state';
import { FUZZING_STAGE } from 'ee/security_dashboard/store/modules/pipeline_jobs/constants';
import * as getters from 'ee/security_dashboard/store/modules/pipeline_jobs/getters';
describe('pipeline jobs module getters', () => {
describe('hasFuzzingArtifacts', () => {
it('should return true when the pipeline has at least one fuzzing job with at least one artifact', () => {
const pipelineJobs = [{ stage: FUZZING_STAGE, artifacts: [{}] }];
const state = { pipelineJobs };
const result = getters.hasFuzzingArtifacts(state);
expect(result).toBe(true);
});
it('should return true when the pipeline has many jobs and at least one fuzzing job with at least one artifact', () => {
const pipelineJobs = [
{ stage: 'other', artifacts: [] },
{ stage: FUZZING_STAGE, artifacts: [{}] },
];
const state = { pipelineJobs };
const result = getters.hasFuzzingArtifacts(state);
expect(result).toBe(true);
});
it('should return false when the pipeline has a fuzzing job with 0 artifacts', () => {
const pipelineJobs = [{ stage: FUZZING_STAGE, artifacts: [] }];
const state = { pipelineJobs };
const result = getters.hasFuzzingArtifacts(state);
expect(result).toBe(false);
});
it('should return false when the pipeline has no fuzzing job with 0 artifacts', () => {
const pipelineJobs = [{ stage: 'other', artifacts: [] }];
const state = { pipelineJobs };
const result = getters.hasFuzzingArtifacts(state);
expect(result).toBe(false);
});
it('should return false when the pipeline has no fuzzing job with 1 artifacts', () => {
const pipelineJobs = [{ stage: 'other', artifacts: [{}] }];
const state = { pipelineJobs };
const result = getters.hasFuzzingArtifacts(state);
expect(result).toBe(false);
});
it('should return false when the pipeline has many jobs and at least one fuzzing job with no fuzzing artifact', () => {
const pipelineJobs = [
{ stage: 'other', artifacts: [] },
{ stage: FUZZING_STAGE, artifacts: [] },
];
const state = { pipelineJobs };
const result = getters.hasFuzzingArtifacts(state);
expect(result).toBe(false);
});
});
describe('fuzzingJobsWithArtifact', () => {
it('should return a fuzzing job when the pipeline has at least one fuzzing job with at least one artifact', () => {
const pipelineJobs = [{ stage: FUZZING_STAGE, artifacts: [{}] }];
const state = { pipelineJobs };
const result = getters.fuzzingJobsWithArtifact(state);
expect(result).toEqual(pipelineJobs);
});
it('should return a fuzzing job when the pipeline has many jobs and at least one fuzzing job with at least one artifact', () => {
const pipelineJobs = [
{ stage: 'other', artifacts: [] },
{ stage: FUZZING_STAGE, artifacts: [{}] },
];
const state = { pipelineJobs };
const result = getters.fuzzingJobsWithArtifact(state);
expect(result).toEqual([pipelineJobs[1]]);
});
it('should not return a fuzzing job when the pipeline has a fuzzing job with 0 artifacts', () => {
const pipelineJobs = [{ stage: FUZZING_STAGE, artifacts: [] }];
const state = { pipelineJobs };
const result = getters.fuzzingJobsWithArtifact(state);
expect(result).toEqual([]);
});
it('should not return a fuzzing job when the pipeline has no fuzzing job with 0 artifacts', () => {
const pipelineJobs = [{ stage: 'other', artifacts: [] }];
const state = { pipelineJobs };
const result = getters.fuzzingJobsWithArtifact(state);
expect(result).toEqual([]);
});
it('should not return a fuzzing job when the pipeline has no fuzzing job with 1 artifacts', () => {
const pipelineJobs = [{ stage: 'other', artifacts: [{}] }];
const state = { pipelineJobs };
const result = getters.fuzzingJobsWithArtifact(state);
expect(result).toEqual([]);
});
it('should not return a fuzzing job when the pipeline has many jobs and at least one fuzzing job with no fuzzing artifact', () => {
const pipelineJobs = [
{ stage: 'other', artifacts: [] },
{ stage: FUZZING_STAGE, artifacts: [] },
];
const state = { pipelineJobs };
const result = getters.fuzzingJobsWithArtifact(state);
expect(result).toEqual([]);
});
});
});
...@@ -29,7 +29,7 @@ import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/consta ...@@ -29,7 +29,7 @@ import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/consta
// Force Jest to transpile and cache // Force Jest to transpile and cache
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
import _Deployment from '~/vue_merge_request_widget/components/deployment/deployment.vue'; import _Deployment from '~/vue_merge_request_widget/components/deployment/deployment.vue';
import securityReportMergeRequestDownloadPathsQuery from '~/vue_shared/security_reports/queries/security_report_mr_download_paths.query.graphql'; import securityReportMergeRequestDownloadPathsQuery from '~/vue_shared/security_reports/queries/security_report_merge_request_download_paths.query.graphql';
import mockData, { import mockData, {
baseBrowserPerformance, baseBrowserPerformance,
......
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import Component from 'ee/vue_shared/security_reports/components/artifact_download.vue';
import {
REPORT_TYPE_SAST,
REPORT_TYPE_SECRET_DETECTION,
} from 'ee/vue_shared/security_reports/constants';
import createMockApollo from 'helpers/mock_apollo_helper';
import {
expectedDownloadDropdownProps,
securityReportDownloadPathsQueryResponse,
} from 'jest/vue_shared/security_reports/mock_data';
import createFlash from '~/flash';
import SecurityReportDownloadDropdown from '~/vue_shared/security_reports/components/security_report_download_dropdown.vue';
import securityReportDownloadPathsQuery from '~/vue_shared/security_reports/queries/security_report_download_paths.query.graphql';
jest.mock('~/flash');
describe('Artifact Download', () => {
let wrapper;
const defaultProps = {
reportTypes: [REPORT_TYPE_SAST, REPORT_TYPE_SECRET_DETECTION],
targetProjectFullPath: '/path',
mrIid: 123,
};
const createWrapper = ({ propsData, options }) => {
wrapper = shallowMount(Component, {
stubs: {
SecurityReportDownloadDropdown,
},
propsData: {
...defaultProps,
...propsData,
},
...options,
});
};
const pendingHandler = () => new Promise(() => {});
const successHandler = () => Promise.resolve({ data: securityReportDownloadPathsQueryResponse });
const failureHandler = () => Promise.resolve({ errors: [{ message: 'some error' }] });
const createMockApolloProvider = (handler) => {
Vue.use(VueApollo);
const requestHandlers = [[securityReportDownloadPathsQuery, handler]];
return createMockApollo(requestHandlers);
};
const findDownloadDropdown = () => wrapper.find(SecurityReportDownloadDropdown);
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('given the query is loading', () => {
beforeEach(() => {
createWrapper({
options: {
apolloProvider: createMockApolloProvider(pendingHandler),
},
});
});
it('loading is true', () => {
expect(findDownloadDropdown().props('loading')).toBe(true);
});
});
describe('given the query loads successfully', () => {
beforeEach(() => {
createWrapper({
options: {
apolloProvider: createMockApolloProvider(successHandler),
},
});
});
it('renders the download dropdown', () => {
expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownProps);
});
});
describe('given the query fails', () => {
beforeEach(() => {
createWrapper({
options: {
apolloProvider: createMockApolloProvider(failureHandler),
},
});
});
it('calls createFlash correctly', () => {
expect(createFlash).toHaveBeenCalledWith({
message: Component.i18n.apiError,
captureError: true,
error: expect.any(Error),
});
});
it('renders nothing', () => {
expect(findDownloadDropdown().props('artifacts')).toEqual([]);
});
});
});
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import Component from 'ee/vue_shared/security_reports/components/artifact_downloads/mr_artifact_download.vue'; import Component from 'ee/vue_shared/security_reports/components/artifact_downloads/merge_request_artifact_download.vue';
import { import {
REPORT_TYPE_SAST, REPORT_TYPE_SAST,
REPORT_TYPE_SECRET_DETECTION, REPORT_TYPE_SECRET_DETECTION,
...@@ -13,7 +13,7 @@ import { ...@@ -13,7 +13,7 @@ import {
} from 'jest/vue_shared/security_reports/mock_data'; } from 'jest/vue_shared/security_reports/mock_data';
import createFlash from '~/flash'; import createFlash from '~/flash';
import SecurityReportDownloadDropdown from '~/vue_shared/security_reports/components/security_report_download_dropdown.vue'; import SecurityReportDownloadDropdown from '~/vue_shared/security_reports/components/security_report_download_dropdown.vue';
import securityReportMergeRequestDownloadPathsQuery from '~/vue_shared/security_reports/queries/security_report_mr_download_paths.query.graphql'; import securityReportMergeRequestDownloadPathsQuery from '~/vue_shared/security_reports/queries/security_report_merge_request_download_paths.query.graphql';
jest.mock('~/flash'); jest.mock('~/flash');
......
...@@ -28702,9 +28702,6 @@ msgstr "" ...@@ -28702,9 +28702,6 @@ msgstr ""
msgid "SecurityReports|Download %{artifactName}" msgid "SecurityReports|Download %{artifactName}"
msgstr "" msgstr ""
msgid "SecurityReports|Download Report"
msgstr ""
msgid "SecurityReports|Download results" msgid "SecurityReports|Download results"
msgstr "" msgstr ""
...@@ -28726,9 +28723,6 @@ msgstr "" ...@@ -28726,9 +28723,6 @@ msgstr ""
msgid "SecurityReports|Failed to get security report information. Please reload the page or try again later." msgid "SecurityReports|Failed to get security report information. Please reload the page or try again later."
msgstr "" msgstr ""
msgid "SecurityReports|Fuzzing artifacts"
msgstr ""
msgid "SecurityReports|Hide dismissed" msgid "SecurityReports|Hide dismissed"
msgstr "" msgstr ""
......
...@@ -3,7 +3,7 @@ import MockAdapter from 'axios-mock-adapter'; ...@@ -3,7 +3,7 @@ import MockAdapter from 'axios-mock-adapter';
import Vue, { nextTick } from 'vue'; import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import { securityReportDownloadPathsQueryResponse } from 'jest/vue_shared/security_reports/mock_data'; import { securityReportMergeRequestDownloadPathsQueryResponse } from 'jest/vue_shared/security_reports/mock_data';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { setFaviconOverlay } from '~/lib/utils/favicon'; import { setFaviconOverlay } from '~/lib/utils/favicon';
import notify from '~/lib/utils/notify'; import notify from '~/lib/utils/notify';
...@@ -12,7 +12,7 @@ import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/consta ...@@ -12,7 +12,7 @@ import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/consta
import eventHub from '~/vue_merge_request_widget/event_hub'; import eventHub from '~/vue_merge_request_widget/event_hub';
import MrWidgetOptions from '~/vue_merge_request_widget/mr_widget_options.vue'; import MrWidgetOptions from '~/vue_merge_request_widget/mr_widget_options.vue';
import { stateKey } from '~/vue_merge_request_widget/stores/state_maps'; import { stateKey } from '~/vue_merge_request_widget/stores/state_maps';
import securityReportDownloadPathsQuery from '~/vue_shared/security_reports/queries/security_report_download_paths.query.graphql'; import securityReportMergeRequestDownloadPathsQuery from '~/vue_shared/security_reports/queries/security_report_merge_request_download_paths.query.graphql';
import { faviconDataUrl, overlayDataUrl } from '../lib/utils/mock_data'; import { faviconDataUrl, overlayDataUrl } from '../lib/utils/mock_data';
import mockData from './mock_data'; import mockData from './mock_data';
...@@ -830,8 +830,8 @@ describe('MrWidgetOptions', () => { ...@@ -830,8 +830,8 @@ describe('MrWidgetOptions', () => {
return createComponent(mrData, { return createComponent(mrData, {
apolloProvider: createMockApollo([ apolloProvider: createMockApollo([
[ [
securityReportDownloadPathsQuery, securityReportMergeRequestDownloadPathsQuery,
async () => ({ data: securityReportDownloadPathsQueryResponse }), async () => ({ data: securityReportMergeRequestDownloadPathsQueryResponse }),
], ],
]), ]),
}); });
......
...@@ -322,7 +322,7 @@ export const secretScanningDiffSuccessMock = { ...@@ -322,7 +322,7 @@ export const secretScanningDiffSuccessMock = {
head_report_created_at: '2020-01-10T10:00:00.000Z', head_report_created_at: '2020-01-10T10:00:00.000Z',
}; };
export const securityReportDownloadPathsQueryNoArtifactsResponse = { export const securityReportMergeRequestDownloadPathsQueryNoArtifactsResponse = {
project: { project: {
mergeRequest: { mergeRequest: {
headPipeline: { headPipeline: {
...@@ -447,8 +447,6 @@ export const securityReportMergeRequestDownloadPathsQueryResponse = { ...@@ -447,8 +447,6 @@ export const securityReportMergeRequestDownloadPathsQueryResponse = {
}, },
}; };
export const securityReportDownloadPathsQueryResponse = securityReportMergeRequestDownloadPathsQueryResponse;
export const securityReportPipelineDownloadPathsQueryResponse = { export const securityReportPipelineDownloadPathsQueryResponse = {
project: { project: {
pipeline: { pipeline: {
...@@ -556,7 +554,7 @@ export const securityReportPipelineDownloadPathsQueryResponse = { ...@@ -556,7 +554,7 @@ export const securityReportPipelineDownloadPathsQueryResponse = {
}; };
/** /**
* These correspond to SAST jobs in the securityReportDownloadPathsQueryResponse above. * These correspond to SAST jobs in the securityReportMergeRequestDownloadPathsQueryResponse above.
*/ */
export const sastArtifacts = [ export const sastArtifacts = [
{ {
...@@ -572,7 +570,7 @@ export const sastArtifacts = [ ...@@ -572,7 +570,7 @@ export const sastArtifacts = [
]; ];
/** /**
* These correspond to Secret Detection jobs in the securityReportDownloadPathsQueryResponse above. * These correspond to Secret Detection jobs in the securityReportMergeRequestDownloadPathsQueryResponse above.
*/ */
export const secretDetectionArtifacts = [ export const secretDetectionArtifacts = [
{ {
...@@ -589,7 +587,7 @@ export const expectedDownloadDropdownProps = { ...@@ -589,7 +587,7 @@ export const expectedDownloadDropdownProps = {
}; };
/** /**
* These correspond to any jobs with zip archives in the securityReportDownloadPathsQueryResponse above. * These correspond to any jobs with zip archives in the securityReportMergeRequestDownloadPathsQueryResponse above.
*/ */
export const archiveArtifacts = [ export const archiveArtifacts = [
{ {
...@@ -600,7 +598,7 @@ export const archiveArtifacts = [ ...@@ -600,7 +598,7 @@ export const archiveArtifacts = [
]; ];
/** /**
* These correspond to any jobs with trace data in the securityReportDownloadPathsQueryResponse above. * These correspond to any jobs with trace data in the securityReportMergeRequestDownloadPathsQueryResponse above.
*/ */
export const traceArtifacts = [ export const traceArtifacts = [
{ {
...@@ -626,7 +624,7 @@ export const traceArtifacts = [ ...@@ -626,7 +624,7 @@ export const traceArtifacts = [
]; ];
/** /**
* These correspond to any jobs with metadata data in the securityReportDownloadPathsQueryResponse above. * These correspond to any jobs with metadata data in the securityReportMergeRequestDownloadPathsQueryResponse above.
*/ */
export const metadataArtifacts = [ export const metadataArtifacts = [
{ {
......
...@@ -9,8 +9,8 @@ import { trimText } from 'helpers/text_helper'; ...@@ -9,8 +9,8 @@ import { trimText } from 'helpers/text_helper';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import { import {
expectedDownloadDropdownProps, expectedDownloadDropdownProps,
securityReportDownloadPathsQueryNoArtifactsResponse, securityReportMergeRequestDownloadPathsQueryNoArtifactsResponse,
securityReportDownloadPathsQueryResponse, securityReportMergeRequestDownloadPathsQueryResponse,
sastDiffSuccessMock, sastDiffSuccessMock,
secretScanningDiffSuccessMock, secretScanningDiffSuccessMock,
} from 'jest/vue_shared/security_reports/mock_data'; } from 'jest/vue_shared/security_reports/mock_data';
...@@ -22,7 +22,7 @@ import { ...@@ -22,7 +22,7 @@ import {
REPORT_TYPE_SAST, REPORT_TYPE_SAST,
REPORT_TYPE_SECRET_DETECTION, REPORT_TYPE_SECRET_DETECTION,
} from '~/vue_shared/security_reports/constants'; } from '~/vue_shared/security_reports/constants';
import securityReportDownloadPathsQuery from '~/vue_shared/security_reports/queries/security_report_download_paths.query.graphql'; import securityReportMergeRequestDownloadPathsQuery from '~/vue_shared/security_reports/queries/security_report_merge_request_download_paths.query.graphql';
import SecurityReportsApp from '~/vue_shared/security_reports/security_reports_app.vue'; import SecurityReportsApp from '~/vue_shared/security_reports/security_reports_app.vue';
jest.mock('~/flash'); jest.mock('~/flash');
...@@ -59,12 +59,13 @@ describe('Security reports app', () => { ...@@ -59,12 +59,13 @@ describe('Security reports app', () => {
}; };
const pendingHandler = () => new Promise(() => {}); const pendingHandler = () => new Promise(() => {});
const successHandler = () => Promise.resolve({ data: securityReportDownloadPathsQueryResponse }); const successHandler = () =>
Promise.resolve({ data: securityReportMergeRequestDownloadPathsQueryResponse });
const successEmptyHandler = () => const successEmptyHandler = () =>
Promise.resolve({ data: securityReportDownloadPathsQueryNoArtifactsResponse }); Promise.resolve({ data: securityReportMergeRequestDownloadPathsQueryNoArtifactsResponse });
const failureHandler = () => Promise.resolve({ errors: [{ message: 'some error' }] }); const failureHandler = () => Promise.resolve({ errors: [{ message: 'some error' }] });
const createMockApolloProvider = (handler) => { const createMockApolloProvider = (handler) => {
const requestHandlers = [[securityReportDownloadPathsQuery, handler]]; const requestHandlers = [[securityReportMergeRequestDownloadPathsQuery, handler]];
return createMockApollo(requestHandlers); return createMockApollo(requestHandlers);
}; };
......
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