Commit 8672da3c authored by Ash McKenzie's avatar Ash McKenzie

Merge branch 'djadmin-scanner-profile-edit' into 'master'

DAST Scanner Profile - Add edit capability

See merge request gitlab-org/gitlab!41255
parents cfb1e302 7dc2c2da
...@@ -3142,6 +3142,11 @@ enum DastScanTypeEnum { ...@@ -3142,6 +3142,11 @@ enum DastScanTypeEnum {
Represents a DAST scanner profile. Represents a DAST scanner profile.
""" """
type DastScannerProfile { type DastScannerProfile {
"""
Relative web path to the edit page of a scanner profile
"""
editPath: String
""" """
ID of the DAST scanner profile ID of the DAST scanner profile
""" """
......
...@@ -8546,6 +8546,20 @@ ...@@ -8546,6 +8546,20 @@
"name": "DastScannerProfile", "name": "DastScannerProfile",
"description": "Represents a DAST scanner profile.", "description": "Represents a DAST scanner profile.",
"fields": [ "fields": [
{
"name": "editPath",
"description": "Relative web path to the edit page of a scanner profile",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "globalId", "name": "globalId",
"description": "ID of the DAST scanner profile", "description": "ID of the DAST scanner profile",
...@@ -537,6 +537,7 @@ Represents a DAST scanner profile. ...@@ -537,6 +537,7 @@ Represents a DAST scanner profile.
| Field | Type | Description | | Field | Type | Description |
| ----- | ---- | ----------- | | ----- | ---- | ----------- |
| `editPath` | String | Relative web path to the edit page of a scanner profile |
| `globalId` | DastScannerProfileID! | ID of the DAST scanner profile | | `globalId` | DastScannerProfileID! | ID of the DAST scanner profile |
| `id` **{warning-solid}** | ID! | **Deprecated:** Use `global_id`. Deprecated in 13.4 | | `id` **{warning-solid}** | ID! | **Deprecated:** Use `global_id`. Deprecated in 13.4 |
| `profileName` | String | Name of the DAST scanner profile | | `profileName` | String | Name of the DAST scanner profile |
......
...@@ -154,21 +154,6 @@ export default { ...@@ -154,21 +154,6 @@ export default {
@click="prepareProfileDeletion(item.id)" @click="prepareProfileDeletion(item.id)"
/> />
<gl-button v-if="item.editPath" :href="item.editPath">{{ __('Edit') }}</gl-button> <gl-button v-if="item.editPath" :href="item.editPath">{{ __('Edit') }}</gl-button>
<!--
NOTE: The tooltip and `disable` on the button is temporary until the edit feature has been implemented
further details: https://gitlab.com/groups/gitlab-org/-/epics/3786 (iteration outline)
-->
<span
v-else
v-gl-tooltip.hover
:title="
s__(
'DastProfiles|Edit feature will come soon. Please create a new profile if changes needed',
)
"
>
<gl-button disabled>{{ __('Edit') }}</gl-button>
</span>
</div> </div>
</template> </template>
......
...@@ -24,6 +24,7 @@ query DastScannerProfiles( ...@@ -24,6 +24,7 @@ query DastScannerProfiles(
profileName profileName
spiderTimeout spiderTimeout
targetTimeout targetTimeout
editPath
} }
} }
} }
......
...@@ -17,6 +17,7 @@ import { __, s__ } from '~/locale'; ...@@ -17,6 +17,7 @@ import { __, s__ } from '~/locale';
import { redirectTo } from '~/lib/utils/url_utility'; import { redirectTo } from '~/lib/utils/url_utility';
import { serializeFormObject, isEmptyValue } from '~/lib/utils/forms'; import { serializeFormObject, isEmptyValue } from '~/lib/utils/forms';
import dastScannerProfileCreateMutation from '../graphql/dast_scanner_profile_create.mutation.graphql'; import dastScannerProfileCreateMutation from '../graphql/dast_scanner_profile_create.mutation.graphql';
import dastScannerProfileUpdateMutation from '../graphql/dast_scanner_profile_update.mutation.graphql';
const initField = (value, isRequired = false) => ({ const initField = (value, isRequired = false) => ({
value, value,
...@@ -55,12 +56,19 @@ export default { ...@@ -55,12 +56,19 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
profile: {
type: Object,
required: false,
default: () => ({}),
},
}, },
data() { data() {
const { name = '', spiderTimeout = '', targetTimeout = '' } = this.profile;
const form = { const form = {
profileName: initField('', true), profileName: initField(name, true),
spiderTimeout: initField('', true), spiderTimeout: initField(spiderTimeout, true),
targetTimeout: initField('', true), targetTimeout: initField(targetTimeout, true),
}; };
return { return {
...@@ -79,6 +87,35 @@ export default { ...@@ -79,6 +87,35 @@ export default {
max: TARGET_TIMEOUT_MAX, max: TARGET_TIMEOUT_MAX,
}, },
computed: { computed: {
isEdit() {
return Boolean(this.profile.id);
},
i18n() {
const { isEdit } = this;
return {
title: isEdit
? s__('DastProfiles|Edit scanner profile')
: s__('DastProfiles|New scanner profile'),
errorMessage: isEdit
? s__('DastProfiles|Could not update the scanner profile. Please try again.')
: s__('DastProfiles|Could not create the scanner profile. Please try again.'),
modal: {
title: isEdit
? s__('DastProfiles|Do you want to discard your changes?')
: s__('DastProfiles|Do you want to discard this scanner profile?'),
okTitle: __('Discard'),
cancelTitle: __('Cancel'),
},
tooltips: {
spiderTimeout: s__(
'DastProfiles|The maximum number of minutes allowed for the spider to traverse the site.',
),
targetTimeout: s__(
'DastProfiles|The maximum number of seconds allowed for the site under test to respond to a request.',
),
},
};
},
formTouched() { formTouched() {
return !isEqual(serializeFormObject(this.form), this.initialFormValues); return !isEqual(serializeFormObject(this.form), this.initialFormValues);
}, },
...@@ -122,22 +159,33 @@ export default { ...@@ -122,22 +159,33 @@ export default {
const variables = { const variables = {
projectFullPath: this.projectFullPath, projectFullPath: this.projectFullPath,
...(this.isEdit ? { id: this.profile.id } : {}),
...serializeFormObject(this.form), ...serializeFormObject(this.form),
}; };
this.$apollo this.$apollo
.mutate({ .mutate({
mutation: dastScannerProfileCreateMutation, mutation: this.isEdit
? dastScannerProfileUpdateMutation
: dastScannerProfileCreateMutation,
variables, variables,
}) })
.then(({ data: { dastScannerProfileCreate: { errors = [] } } }) => { .then(
if (errors.length > 0) { ({
this.showErrors(errors); data: {
this.loading = false; [this.isEdit ? 'dastScannerProfileUpdate' : 'dastScannerProfileCreate']: {
} else { errors = [],
redirectTo(this.profilesLibraryPath); },
} },
}) }) => {
if (errors.length > 0) {
this.showErrors(errors);
this.loading = false;
} else {
redirectTo(this.profilesLibraryPath);
}
},
)
.catch(e => { .catch(e => {
Sentry.captureException(e); Sentry.captureException(e);
this.showErrors(); this.showErrors();
...@@ -164,24 +212,13 @@ export default { ...@@ -164,24 +212,13 @@ export default {
}, },
}, },
modalId: 'deleteDastProfileModal', modalId: 'deleteDastProfileModal',
i18n: {
modalTitle: s__('DastProfiles|Do you want to discard this scanner profile?'),
modalOkTitle: __('Discard'),
modalCancelTitle: __('Cancel'),
spiderTimeoutTooltip: s__(
'DastProfiles|The maximum number of minutes allowed for the spider to traverse the site.',
),
targetTimeoutTooltip: s__(
'DastProfiles|The maximum number of seconds allowed for the site under test to respond to a request.',
),
},
}; };
</script> </script>
<template> <template>
<gl-form @submit.prevent="onSubmit"> <gl-form @submit.prevent="onSubmit">
<h2 class="gl-mb-6"> <h2 class="gl-mb-6">
{{ s__('DastProfiles|New scanner profile') }} {{ i18n.title }}
</h2> </h2>
<gl-alert v-if="showAlert" variant="danger" class="gl-mb-5" @dismiss="hideErrors"> <gl-alert v-if="showAlert" variant="danger" class="gl-mb-5" @dismiss="hideErrors">
...@@ -214,7 +251,7 @@ export default { ...@@ -214,7 +251,7 @@ export default {
v-gl-tooltip.hover v-gl-tooltip.hover
name="information-o" name="information-o"
class="gl-vertical-align-text-bottom gl-text-gray-400 gl-ml-2" class="gl-vertical-align-text-bottom gl-text-gray-400 gl-ml-2"
:title="$options.i18n.spiderTimeoutTooltip" :title="i18n.tooltips.spiderTimeout"
/> />
</template> </template>
<gl-form-input-group <gl-form-input-group
...@@ -246,7 +283,7 @@ export default { ...@@ -246,7 +283,7 @@ export default {
v-gl-tooltip.hover v-gl-tooltip.hover
name="information-o" name="information-o"
class="gl-vertical-align-text-bottom gl-text-gray-400 gl-ml-2" class="gl-vertical-align-text-bottom gl-text-gray-400 gl-ml-2"
:title="$options.i18n.targetTimeoutTooltip" :title="i18n.tooltips.targetTimeout"
/> />
</template> </template>
<gl-form-input-group <gl-form-input-group
...@@ -291,9 +328,9 @@ export default { ...@@ -291,9 +328,9 @@ export default {
<gl-modal <gl-modal
:ref="$options.modalId" :ref="$options.modalId"
:modal-id="$options.modalId" :modal-id="$options.modalId"
:title="$options.i18n.modalTitle" :title="i18n.modal.title"
:ok-title="$options.i18n.modalOkTitle" :ok-title="i18n.modal.okTitle"
:cancel-title="$options.i18n.modalCancelTitle" :cancel-title="i18n.modal.cancelTitle"
ok-variant="danger" ok-variant="danger"
body-class="gl-display-none" body-class="gl-display-none"
data-testid="dast-scanner-profile-form-cancel-modal" data-testid="dast-scanner-profile-form-cancel-modal"
......
import Vue from 'vue'; import Vue from 'vue';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import apolloProvider from './graphql/provider'; import apolloProvider from './graphql/provider';
import DastScannerProfileForm from './components/dast_scanner_profile_form.vue'; import DastScannerProfileForm from './components/dast_scanner_profile_form.vue';
...@@ -15,6 +16,10 @@ export default () => { ...@@ -15,6 +16,10 @@ export default () => {
profilesLibraryPath, profilesLibraryPath,
}; };
if (el.dataset.scannerProfile) {
props.profile = convertObjectPropsToCamelCase(JSON.parse(el.dataset.scannerProfile));
}
return new Vue({ return new Vue({
el, el,
apolloProvider, apolloProvider,
......
mutation dastScannerProfileUpdate(
$id: DastScannerProfileID!
$projectFullPath: ID!
$profileName: String!
$spiderTimeout: Int!
$targetTimeout: Int!
) {
dastScannerProfileUpdate(
input: {
id: $id
fullPath: $projectFullPath
profileName: $profileName
spiderTimeout: $spiderTimeout
targetTimeout: $targetTimeout
}
) {
id
errors
}
}
import initDastScannerProfileForm from 'ee/dast_scanner_profiles/dast_scanner_profiles_bundle';
document.addEventListener('DOMContentLoaded', initDastScannerProfileForm);
...@@ -6,5 +6,11 @@ module Projects ...@@ -6,5 +6,11 @@ module Projects
def new def new
end end
def edit
@scanner_profile = @project
.dast_scanner_profiles
.find(params[:id])
end
end end
end end
...@@ -24,5 +24,11 @@ module Types ...@@ -24,5 +24,11 @@ module Types
field :target_timeout, GraphQL::INT_TYPE, null: true, field :target_timeout, GraphQL::INT_TYPE, null: true,
description: 'The maximum number of seconds allowed for the site under test to respond to a request' description: 'The maximum number of seconds allowed for the site under test to respond to a request'
field :edit_path, GraphQL::STRING_TYPE, null: true,
description: 'Relative web path to the edit page of a scanner profile',
resolve: -> (obj, _args, _ctx) do
Rails.application.routes.url_helpers.edit_project_dast_scanner_profile_path(obj.project, obj)
end
end end
end end
...@@ -165,6 +165,7 @@ module EE ...@@ -165,6 +165,7 @@ module EE
projects/dast_site_profiles#new projects/dast_site_profiles#new
projects/dast_site_profiles#edit projects/dast_site_profiles#edit
projects/dast_scanner_profiles#new projects/dast_scanner_profiles#new
projects/dast_scanner_profiles#edit
projects/dependencies#index projects/dependencies#index
projects/licenses#index projects/licenses#index
projects/threat_monitoring#show projects/threat_monitoring#show
...@@ -186,6 +187,7 @@ module EE ...@@ -186,6 +187,7 @@ module EE
projects/dast_site_profiles#new projects/dast_site_profiles#new
projects/dast_site_profiles#edit projects/dast_site_profiles#edit
projects/dast_scanner_profiles#new projects/dast_scanner_profiles#new
projects/dast_scanner_profiles#edit
] ]
end end
......
- add_to_breadcrumbs s_('OnDemandScans|On-demand Scans'), project_on_demand_scans_path(@project)
- add_to_breadcrumbs s_('DastProfiles|Manage profiles'), project_profiles_path(@project, anchor: 'scanner-profiles')
- breadcrumb_title s_('DastProfiles|Edit scanner profile')
- page_title s_('DastProfiles|Edit scanner profile')
.js-dast-scanner-profile-form{ data: { project_full_path: @project.path_with_namespace,
profiles_library_path: project_profiles_path(@project, anchor: 'scanner-profiles'),
scanner_profile: { id: @scanner_profile.to_global_id.to_s, name: @scanner_profile.name, spider_timeout: @scanner_profile.spider_timeout, target_timeout: @scanner_profile.target_timeout }.to_json } }
...@@ -94,7 +94,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -94,7 +94,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
scope :profiles do scope :profiles do
root 'dast_profiles#index', as: 'profiles' root 'dast_profiles#index', as: 'profiles'
resources :dast_site_profiles, only: [:new, :edit] resources :dast_site_profiles, only: [:new, :edit]
resources :dast_scanner_profiles, only: [:new] resources :dast_scanner_profiles, only: [:new, :edit]
end end
end end
......
...@@ -121,7 +121,7 @@ describe('EE - DastProfilesList', () => { ...@@ -121,7 +121,7 @@ describe('EE - DastProfilesList', () => {
id: 3, id: 3,
profileName: 'Profile 2', profileName: 'Profile 2',
targetUrl: 'http://example-2.com', targetUrl: 'http://example-2.com',
editPath: '', editPath: '/3/edit',
validationStatus: 'Pending', validationStatus: 'Pending',
}, },
]; ];
...@@ -155,19 +155,9 @@ describe('EE - DastProfilesList', () => { ...@@ -155,19 +155,9 @@ describe('EE - DastProfilesList', () => {
expect(validationStatusCell.innerText).toContain(profile.validationStatus); expect(validationStatusCell.innerText).toContain(profile.validationStatus);
expect(within(actionsCell).getByRole('button', { name: /delete/i })).not.toBe(null); expect(within(actionsCell).getByRole('button', { name: /delete/i })).not.toBe(null);
if (profile.editPath) { const editLink = within(actionsCell).getByRole('link', { name: /edit/i });
const editLink = within(actionsCell).getByRole('link', { name: /edit/i }); expect(editLink).not.toBe(null);
expect(editLink).not.toBe(null); expect(editLink.getAttribute('href')).toBe(profile.editPath);
expect(editLink.getAttribute('href')).toBe(profile.editPath);
} else {
const editButton = within(actionsCell).getByRole('button', { name: /edit/i });
const helpText = within(actionsCell).getByTitle(
/edit feature will come soon. please create a new profile if changes needed/i,
);
expect(helpText).not.toBe(null);
expect(editButton).not.toBe(null);
expect(editButton.getAttribute('disabled')).not.toBe(null);
}
}); });
}); });
......
import merge from 'lodash/merge'; import merge from 'lodash/merge';
import { within } from '@testing-library/dom';
import { mount, shallowMount } from '@vue/test-utils'; import { mount, shallowMount } from '@vue/test-utils';
import { GlAlert, GlForm, GlModal } from '@gitlab/ui'; import { GlAlert, GlForm, GlModal } from '@gitlab/ui';
import { TEST_HOST } from 'helpers/test_constants'; import { TEST_HOST } from 'helpers/test_constants';
import DastScannerProfileForm from 'ee/dast_scanner_profiles/components/dast_scanner_profile_form.vue'; import DastScannerProfileForm from 'ee/dast_scanner_profiles/components/dast_scanner_profile_form.vue';
import dastScannerProfileCreateMutation from 'ee/dast_scanner_profiles/graphql/dast_scanner_profile_create.mutation.graphql'; import dastScannerProfileCreateMutation from 'ee/dast_scanner_profiles/graphql/dast_scanner_profile_create.mutation.graphql';
import dastScannerProfileUpdateMutation from 'ee/dast_scanner_profiles/graphql/dast_scanner_profile_update.mutation.graphql';
import { redirectTo } from '~/lib/utils/url_utility'; import { redirectTo } from '~/lib/utils/url_utility';
jest.mock('~/lib/utils/url_utility', () => ({ jest.mock('~/lib/utils/url_utility', () => ({
...@@ -24,6 +26,8 @@ const defaultProps = { ...@@ -24,6 +26,8 @@ const defaultProps = {
describe('DAST Scanner Profile', () => { describe('DAST Scanner Profile', () => {
let wrapper; let wrapper;
const withinComponent = () => within(wrapper.element);
const findForm = () => wrapper.find(GlForm); const findForm = () => wrapper.find(GlForm);
const findProfileNameInput = () => wrapper.find('[data-testid="profile-name-input"]'); const findProfileNameInput = () => wrapper.find('[data-testid="profile-name-input"]');
const findSpiderTimeoutInput = () => wrapper.find('[data-testid="spider-timeout-input"]'); const findSpiderTimeoutInput = () => wrapper.find('[data-testid="spider-timeout-input"]');
...@@ -131,118 +135,139 @@ describe('DAST Scanner Profile', () => { ...@@ -131,118 +135,139 @@ describe('DAST Scanner Profile', () => {
}); });
}); });
describe('submission', () => { describe.each`
const createdProfileId = 30203; title | profile | mutation | mutationVars | mutationKind
${'New scanner profile'} | ${{}} | ${dastScannerProfileCreateMutation} | ${{}} | ${'dastScannerProfileCreate'}
describe('on success', () => { ${'Edit scanner profile'} | ${{ id: 1, name: 'foo', spiderTimeout: 2, targetTimeout: 12 }} | ${dastScannerProfileUpdateMutation} | ${{ id: 1 }} | ${'dastScannerProfileUpdate'}
beforeEach(() => { `('$title', ({ profile, title, mutation, mutationVars, mutationKind }) => {
createComponent(); beforeEach(() => {
jest createFullComponent({
.spyOn(wrapper.vm.$apollo, 'mutate') propsData: {
.mockResolvedValue({ data: { dastScannerProfileCreate: { id: createdProfileId } } }); profile,
findProfileNameInput().vm.$emit('input', profileName); },
findSpiderTimeoutInput().vm.$emit('input', spiderTimeout);
findTargetTimeoutInput().vm.$emit('input', targetTimeout);
submitForm();
}); });
});
it('sets loading state', () => { it('sets the correct title', () => {
expect(findSubmitButton().props('loading')).toBe(true); expect(withinComponent().getByRole('heading', { name: title })).not.toBeNull();
}); });
it('triggers GraphQL mutation', () => { it('populates the fields with the data passed in via the profile prop', () => {
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ expect(findProfileNameInput().element.value).toBe(profile?.name ?? '');
mutation: dastScannerProfileCreateMutation, });
variables: {
profileName, describe('submission', () => {
spiderTimeout, const createdProfileId = 30203;
targetTimeout,
projectFullPath, describe('on success', () => {
}, beforeEach(() => {
jest
.spyOn(wrapper.vm.$apollo, 'mutate')
.mockResolvedValue({ data: { [mutationKind]: { id: createdProfileId } } });
findProfileNameInput().vm.$emit('input', profileName);
findSpiderTimeoutInput().vm.$emit('input', spiderTimeout);
findTargetTimeoutInput().vm.$emit('input', targetTimeout);
submitForm();
}); });
});
it('redirects to the profiles library', () => { it('sets loading state', () => {
expect(redirectTo).toHaveBeenCalledWith(profilesLibraryPath); expect(findSubmitButton().props('loading')).toBe(true);
}); });
it('does not show an alert', () => { it('triggers GraphQL mutation', () => {
expect(findAlert().exists()).toBe(false); expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
}); mutation,
}); variables: {
profileName,
spiderTimeout,
targetTimeout,
projectFullPath,
...mutationVars,
},
});
});
describe('on top-level error', () => { it('redirects to the profiles library', () => {
beforeEach(() => { expect(redirectTo).toHaveBeenCalledWith(profilesLibraryPath);
createComponent(); });
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue();
const input = findTargetTimeoutInput();
input.vm.$emit('input', targetTimeout);
submitForm();
});
it('resets loading state', () => { it('does not show an alert', () => {
expect(findSubmitButton().props('loading')).toBe(false); expect(findAlert().exists()).toBe(false);
});
}); });
it('shows an error alert', () => { describe('on top-level error', () => {
expect(findAlert().exists()).toBe(true); beforeEach(() => {
}); createComponent();
}); jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue();
const input = findTargetTimeoutInput();
input.vm.$emit('input', targetTimeout);
submitForm();
});
describe('on errors as data', () => { it('resets loading state', () => {
const errors = ['Name is already taken', 'Value should be Int', 'error#3']; expect(findSubmitButton().props('loading')).toBe(false);
});
beforeEach(() => { it('shows an error alert', () => {
createComponent(); expect(findAlert().exists()).toBe(true);
jest });
.spyOn(wrapper.vm.$apollo, 'mutate')
.mockResolvedValue({ data: { dastScannerProfileCreate: { pipelineUrl: null, errors } } });
const input = findSpiderTimeoutInput();
input.vm.$emit('input', spiderTimeout);
submitForm();
}); });
it('resets loading state', () => { describe('on errors as data', () => {
expect(findSubmitButton().props('loading')).toBe(false); const errors = ['Name is already taken', 'Value should be Int', 'error#3'];
});
it('shows an alert with the returned errors', () => { beforeEach(() => {
const alert = findAlert(); jest
.spyOn(wrapper.vm.$apollo, 'mutate')
.mockResolvedValue({ data: { [mutationKind]: { errors } } });
const input = findSpiderTimeoutInput();
input.vm.$emit('input', spiderTimeout);
submitForm();
});
expect(alert.exists()).toBe(true); it('resets loading state', () => {
errors.forEach(error => { expect(findSubmitButton().props('loading')).toBe(false);
expect(alert.text()).toContain(error);
}); });
});
});
});
describe('cancellation', () => { it('shows an alert with the returned errors', () => {
beforeEach(() => { const alert = findAlert();
createFullComponent();
});
describe('form empty', () => { expect(alert.exists()).toBe(true);
it('redirects to the profiles library', () => { errors.forEach(error => {
findCancelButton().vm.$emit('click'); expect(alert.text()).toContain(error);
expect(redirectTo).toHaveBeenCalledWith(profilesLibraryPath); });
});
}); });
}); });
describe('form not empty', () => { describe('cancellation', () => {
beforeEach(() => { beforeEach(() => {
findProfileNameInput().setValue(profileName); createFullComponent();
}); });
it('asks the user to confirm the action', () => { describe('form empty', () => {
jest.spyOn(findCancelModal().vm, 'show').mockReturnValue(); it('redirects to the profiles library', () => {
findCancelButton().trigger('click'); findCancelButton().vm.$emit('click');
expect(findCancelModal().vm.show).toHaveBeenCalled(); expect(redirectTo).toHaveBeenCalledWith(profilesLibraryPath);
});
}); });
it('redirects to the profiles library if confirmed', () => { describe('form not empty', () => {
findCancelModal().vm.$emit('ok'); beforeEach(() => {
expect(redirectTo).toHaveBeenCalledWith(profilesLibraryPath); findProfileNameInput().setValue(profileName);
});
it('asks the user to confirm the action', () => {
jest.spyOn(findCancelModal().vm, 'show').mockReturnValue();
findCancelButton().trigger('click');
expect(findCancelModal().vm.show).toHaveBeenCalled();
});
it('redirects to the profiles library if confirmed', () => {
findCancelModal().vm.$emit('ok');
expect(redirectTo).toHaveBeenCalledWith(profilesLibraryPath);
});
}); });
}); });
}); });
......
...@@ -6,7 +6,7 @@ RSpec.describe GitlabSchema.types['DastScannerProfile'] do ...@@ -6,7 +6,7 @@ RSpec.describe GitlabSchema.types['DastScannerProfile'] do
let_it_be(:dast_scanner_profile) { create(:dast_scanner_profile) } let_it_be(:dast_scanner_profile) { create(:dast_scanner_profile) }
let_it_be(:project) { dast_scanner_profile.project } let_it_be(:project) { dast_scanner_profile.project }
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:fields) { %i[id globalId profileName spiderTimeout targetTimeout] } let_it_be(:fields) { %i[id globalId profileName spiderTimeout targetTimeout editPath] }
let(:response) do let(:response) do
GitlabSchema.execute( GitlabSchema.execute(
......
...@@ -178,6 +178,7 @@ RSpec.describe ProjectsHelper do ...@@ -178,6 +178,7 @@ RSpec.describe ProjectsHelper do
projects/dast_site_profiles#new projects/dast_site_profiles#new
projects/dast_site_profiles#edit projects/dast_site_profiles#edit
projects/dast_scanner_profiles#new projects/dast_scanner_profiles#new
projects/dast_scanner_profiles#edit
projects/dependencies#index projects/dependencies#index
projects/licenses#index projects/licenses#index
projects/threat_monitoring#show projects/threat_monitoring#show
...@@ -199,6 +200,7 @@ RSpec.describe ProjectsHelper do ...@@ -199,6 +200,7 @@ RSpec.describe ProjectsHelper do
projects/dast_site_profiles#new projects/dast_site_profiles#new
projects/dast_site_profiles#edit projects/dast_site_profiles#edit
projects/dast_scanner_profiles#new projects/dast_scanner_profiles#new
projects/dast_scanner_profiles#edit
] ]
end end
......
...@@ -80,4 +80,20 @@ RSpec.describe Projects::DastScannerProfilesController, type: :request do ...@@ -80,4 +80,20 @@ RSpec.describe Projects::DastScannerProfilesController, type: :request do
let(:path) { new_project_dast_scanner_profile_path(project) } let(:path) { new_project_dast_scanner_profile_path(project) }
end end
end end
describe 'GET #edit' do
include_context 'user authorized'
include_context 'on-demand scans feature available'
let(:edit_path) { edit_project_dast_scanner_profile_path(project, dast_scanner_profile) }
it_behaves_like 'a GET request' do
let(:path) { edit_path }
end
it 'sets scanner_profile' do
get edit_path
expect(assigns(:scanner_profile)).to eq(dast_scanner_profile)
end
end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe "projects/dast_scanner_profiles/edit", type: :view do
let_it_be(:scanner_profile) { create(:dast_scanner_profile) }
let_it_be(:scanner_profile_gid) { ::URI::GID.parse("gid://gitlab/DastScannerProfile/#{scanner_profile.id}") }
before do
assign(:project, scanner_profile.project)
assign(:scanner_profile, scanner_profile)
assign(:scanner_profile_gid, scanner_profile_gid)
render
end
it 'renders Vue app root' do
expect(rendered).to have_selector('.js-dast-scanner-profile-form')
end
it 'passes project\'s full path' do
expect(rendered).to include scanner_profile.project.path_with_namespace
end
it 'passes DAST profiles library URL' do
expect(rendered).to include '/on_demand_scans/profiles'
end
it 'passes DAST scanner profile\'s data' do
expect(rendered).to include scanner_profile_gid.to_s
expect(rendered).to include scanner_profile.name
expect(rendered).to include scanner_profile.spider_timeout.to_s
expect(rendered).to include scanner_profile.target_timeout.to_s
end
end
...@@ -7864,6 +7864,9 @@ msgstr "" ...@@ -7864,6 +7864,9 @@ msgstr ""
msgid "DastProfiles|Could not retrieve site validation status. Please refresh the page, or try again later." msgid "DastProfiles|Could not retrieve site validation status. Please refresh the page, or try again later."
msgstr "" msgstr ""
msgid "DastProfiles|Could not update the scanner profile. Please try again."
msgstr ""
msgid "DastProfiles|Could not update the site profile. Please try again." msgid "DastProfiles|Could not update the site profile. Please try again."
msgstr "" msgstr ""
...@@ -7879,7 +7882,7 @@ msgstr "" ...@@ -7879,7 +7882,7 @@ msgstr ""
msgid "DastProfiles|Download validation text file" msgid "DastProfiles|Download validation text file"
msgstr "" msgstr ""
msgid "DastProfiles|Edit feature will come soon. Please create a new profile if changes needed" msgid "DastProfiles|Edit scanner profile"
msgstr "" msgstr ""
msgid "DastProfiles|Edit site profile" msgid "DastProfiles|Edit site profile"
......
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