summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-09-15 15:10:08 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-09-15 15:10:08 +0000
commit06c127aa72cff78235426341081837cff0b6f78b (patch)
tree614c9d9e029adcac31f290d4f953fe8b02aaf0f1 /spec
parent33212c8ff1f99cdb896e8fc6f6450882287e0de5 (diff)
downloadgitlab-ce-06c127aa72cff78235426341081837cff0b6f78b.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/features/merge_request/user_expands_diff_spec.rb4
-rw-r--r--spec/features/projects/releases/user_views_releases_spec.rb156
-rw-r--r--spec/frontend/diffs/components/diff_file_spec.js16
-rw-r--r--spec/frontend/issue_show/components/app_spec.js2
-rw-r--r--spec/frontend/notes/components/notes_app_spec.js2
-rw-r--r--spec/frontend/notes/stores/actions_spec.js3
-rw-r--r--spec/frontend/pipelines/time_ago_spec.js23
-rw-r--r--spec/frontend/releases/__snapshots__/util_spec.js.snap113
-rw-r--r--spec/frontend/releases/components/app_index_spec.js32
-rw-r--r--spec/frontend/releases/mock_data.js128
-rw-r--r--spec/frontend/releases/stores/modules/list/actions_spec.js144
-rw-r--r--spec/frontend/releases/util_spec.js55
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js13
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/branch_token_spec.js207
-rw-r--r--spec/frontend/vue_shared/components/markdown/suggestion_diff_row_spec.js18
-rw-r--r--spec/helpers/releases_helper_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/none_spec.rb29
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies_spec.rb4
-rw-r--r--spec/services/admin/propagate_integration_service_spec.rb7
19 files changed, 832 insertions, 126 deletions
diff --git a/spec/features/merge_request/user_expands_diff_spec.rb b/spec/features/merge_request/user_expands_diff_spec.rb
index fc17ef011c2..b0cfa8d0d54 100644
--- a/spec/features/merge_request/user_expands_diff_spec.rb
+++ b/spec/features/merge_request/user_expands_diff_spec.rb
@@ -17,11 +17,11 @@ RSpec.describe 'User expands diff', :js do
it 'allows user to expand diff' do
page.within find('[id="19763941ab80e8c09871c0a425f0560d9053bcb3"]') do
- click_link 'Click to expand it.'
+ find('[data-testid="expandButton"]').click
wait_for_requests
- expect(page).not_to have_content('Click to expand it.')
+ expect(page).not_to have_content('Expand File')
expect(page).to have_selector('.code')
end
end
diff --git a/spec/features/projects/releases/user_views_releases_spec.rb b/spec/features/projects/releases/user_views_releases_spec.rb
index 962d5551631..993d3371904 100644
--- a/spec/features/projects/releases/user_views_releases_spec.rb
+++ b/spec/features/projects/releases/user_views_releases_spec.rb
@@ -13,37 +13,25 @@ RSpec.describe 'User views releases', :js do
project.add_guest(guest)
end
- context('when the user is a maintainer') do
- before do
- gitlab_sign_in(maintainer)
- end
-
- it 'sees the release' do
- visit project_releases_path(project)
-
- expect(page).to have_content(release.name)
- expect(page).to have_content(release.tag)
- expect(page).not_to have_content('Upcoming Release')
- end
-
- shared_examples 'asset link tests' do
- context 'when there is a link as an asset' do
- let!(:release_link) { create(:release_link, release: release, url: url ) }
- let(:url) { "#{project.web_url}/-/jobs/1/artifacts/download" }
- let(:direct_asset_link) { Gitlab::Routing.url_helpers.project_release_url(project, release) << release_link.filepath }
+ shared_examples 'releases page' do
+ context('when the user is a maintainer') do
+ before do
+ gitlab_sign_in(maintainer)
+ end
- it 'sees the link' do
- visit project_releases_path(project)
+ it 'sees the release' do
+ visit project_releases_path(project)
- page.within('.js-assets-list') do
- expect(page).to have_link release_link.name, href: direct_asset_link
- expect(page).not_to have_css('[data-testid="external-link-indicator"]')
- end
- end
+ expect(page).to have_content(release.name)
+ expect(page).to have_content(release.tag)
+ expect(page).not_to have_content('Upcoming Release')
+ end
- context 'when there is a link redirect' do
- let!(:release_link) { create(:release_link, release: release, name: 'linux-amd64 binaries', filepath: '/binaries/linux-amd64', url: url) }
+ shared_examples 'asset link tests' do
+ context 'when there is a link as an asset' do
+ let!(:release_link) { create(:release_link, release: release, url: url ) }
let(:url) { "#{project.web_url}/-/jobs/1/artifacts/download" }
+ let(:direct_asset_link) { Gitlab::Routing.url_helpers.project_release_url(project, release) << release_link.filepath }
it 'sees the link' do
visit project_releases_path(project)
@@ -53,77 +41,103 @@ RSpec.describe 'User views releases', :js do
expect(page).not_to have_css('[data-testid="external-link-indicator"]')
end
end
- end
- context 'when url points to external resource' do
- let(:url) { 'http://google.com/download' }
+ context 'when there is a link redirect' do
+ let!(:release_link) { create(:release_link, release: release, name: 'linux-amd64 binaries', filepath: '/binaries/linux-amd64', url: url) }
+ let(:url) { "#{project.web_url}/-/jobs/1/artifacts/download" }
- it 'sees that the link is external resource' do
- visit project_releases_path(project)
+ it 'sees the link' do
+ visit project_releases_path(project)
- page.within('.js-assets-list') do
- expect(page).to have_css('[data-testid="external-link-indicator"]')
+ page.within('.js-assets-list') do
+ expect(page).to have_link release_link.name, href: direct_asset_link
+ expect(page).not_to have_css('[data-testid="external-link-indicator"]')
+ end
+ end
+ end
+
+ context 'when url points to external resource' do
+ let(:url) { 'http://google.com/download' }
+
+ it 'sees that the link is external resource' do
+ visit project_releases_path(project)
+
+ page.within('.js-assets-list') do
+ expect(page).to have_css('[data-testid="external-link-indicator"]')
+ end
end
end
end
end
- end
- context 'when the release_asset_link_type feature flag is enabled' do
- before do
- stub_feature_flags(release_asset_link_type: true)
+ context 'when the release_asset_link_type feature flag is enabled' do
+ before do
+ stub_feature_flags(release_asset_link_type: true)
+ end
+
+ it_behaves_like 'asset link tests'
end
- it_behaves_like 'asset link tests'
- end
+ context 'when the release_asset_link_type feature flag is disabled' do
+ before do
+ stub_feature_flags(release_asset_link_type: false)
+ end
- context 'when the release_asset_link_type feature flag is disabled' do
- before do
- stub_feature_flags(release_asset_link_type: false)
+ it_behaves_like 'asset link tests'
end
- it_behaves_like 'asset link tests'
- end
+ context 'with an upcoming release' do
+ let(:tomorrow) { Time.zone.now + 1.day }
+ let!(:release) { create(:release, project: project, released_at: tomorrow ) }
- context 'with an upcoming release' do
- let(:tomorrow) { Time.zone.now + 1.day }
- let!(:release) { create(:release, project: project, released_at: tomorrow ) }
+ it 'sees the upcoming tag' do
+ visit project_releases_path(project)
- it 'sees the upcoming tag' do
- visit project_releases_path(project)
+ expect(page).to have_content('Upcoming Release')
+ end
+ end
+
+ context 'with a tag containing a slash' do
+ it 'sees the release' do
+ release = create :release, project: project, tag: 'debian/2.4.0-1'
+ visit project_releases_path(project)
- expect(page).to have_content('Upcoming Release')
+ expect(page).to have_content(release.name)
+ expect(page).to have_content(release.tag)
+ end
end
end
- context 'with a tag containing a slash' do
- it 'sees the release' do
- release = create :release, project: project, tag: 'debian/2.4.0-1'
+ context('when the user is a guest') do
+ before do
+ gitlab_sign_in(guest)
+ end
+
+ it 'renders release info except for Git-related data' do
visit project_releases_path(project)
- expect(page).to have_content(release.name)
- expect(page).to have_content(release.tag)
+ within('.release-block') do
+ expect(page).to have_content(release.description)
+
+ # The following properties (sometimes) include Git info,
+ # so they are not rendered for Guest users
+ expect(page).not_to have_content(release.name)
+ expect(page).not_to have_content(release.tag)
+ expect(page).not_to have_content(release.commit.short_id)
+ end
end
end
end
- context('when the user is a guest') do
+ context 'when the graphql_releases_page feature flag is enabled' do
+ it_behaves_like 'releases page'
+ end
+
+ context 'when the graphql_releases_page feature flag is disabled' do
before do
- gitlab_sign_in(guest)
+ stub_feature_flags(graphql_releases_page: false)
end
- it 'renders release info except for Git-related data' do
- visit project_releases_path(project)
-
- within('.release-block') do
- expect(page).to have_content(release.description)
-
- # The following properties (sometimes) include Git info,
- # so they are not rendered for Guest users
- expect(page).not_to have_content(release.name)
- expect(page).not_to have_content(release.tag)
- expect(page).not_to have_content(release.commit.short_id)
- end
- end
+ it_behaves_like 'releases page'
end
end
diff --git a/spec/frontend/diffs/components/diff_file_spec.js b/spec/frontend/diffs/components/diff_file_spec.js
index 0b0a7f966c5..3c39dd2d385 100644
--- a/spec/frontend/diffs/components/diff_file_spec.js
+++ b/spec/frontend/diffs/components/diff_file_spec.js
@@ -90,8 +90,8 @@ describe('DiffFile', () => {
vm.isCollapsed = true;
vm.$nextTick(() => {
- expect(vm.$el.innerText).toContain('This diff is collapsed');
- expect(vm.$el.querySelectorAll('.js-click-to-expand').length).toEqual(1);
+ expect(vm.$el.innerText).toContain('This file is collapsed.');
+ expect(vm.$el.querySelector('[data-testid="expandButton"]')).not.toBeFalsy();
done();
});
@@ -102,8 +102,8 @@ describe('DiffFile', () => {
vm.isCollapsed = true;
vm.$nextTick(() => {
- expect(vm.$el.innerText).toContain('This diff is collapsed');
- expect(vm.$el.querySelectorAll('.js-click-to-expand').length).toEqual(1);
+ expect(vm.$el.innerText).toContain('This file is collapsed.');
+ expect(vm.$el.querySelector('[data-testid="expandButton"]')).not.toBeFalsy();
done();
});
@@ -121,8 +121,8 @@ describe('DiffFile', () => {
vm.isCollapsed = true;
vm.$nextTick(() => {
- expect(vm.$el.innerText).toContain('This diff is collapsed');
- expect(vm.$el.querySelectorAll('.js-click-to-expand').length).toEqual(1);
+ expect(vm.$el.innerText).toContain('This file is collapsed.');
+ expect(vm.$el.querySelector('[data-testid="expandButton"]')).not.toBeFalsy();
done();
});
@@ -135,7 +135,7 @@ describe('DiffFile', () => {
vm.file.viewer.name = diffViewerModes.renamed;
vm.$nextTick(() => {
- expect(vm.$el.innerText).not.toContain('This diff is collapsed');
+ expect(vm.$el.innerText).not.toContain('This file is collapsed.');
done();
});
@@ -148,7 +148,7 @@ describe('DiffFile', () => {
vm.file.viewer.name = diffViewerModes.mode_changed;
vm.$nextTick(() => {
- expect(vm.$el.innerText).not.toContain('This diff is collapsed');
+ expect(vm.$el.innerText).not.toContain('This file is collapsed.');
done();
});
diff --git a/spec/frontend/issue_show/components/app_spec.js b/spec/frontend/issue_show/components/app_spec.js
index 1eabd3083ab..f4095d4de96 100644
--- a/spec/frontend/issue_show/components/app_spec.js
+++ b/spec/frontend/issue_show/components/app_spec.js
@@ -78,6 +78,8 @@ describe('Issuable output', () => {
});
mountComponent();
+
+ jest.advanceTimersByTime(2);
});
afterEach(() => {
diff --git a/spec/frontend/notes/components/notes_app_spec.js b/spec/frontend/notes/components/notes_app_spec.js
index fbfba2efb1d..c6034639a4a 100644
--- a/spec/frontend/notes/components/notes_app_spec.js
+++ b/spec/frontend/notes/components/notes_app_spec.js
@@ -330,6 +330,8 @@ describe('note_app', () => {
wrapper.vm.$parent.$el.dispatchEvent(toggleAwardEvent);
+ jest.advanceTimersByTime(2);
+
expect(toggleAwardAction).toHaveBeenCalledTimes(1);
const [, payload] = toggleAwardAction.mock.calls[0];
diff --git a/spec/frontend/notes/stores/actions_spec.js b/spec/frontend/notes/stores/actions_spec.js
index 52eea99ce8c..4681f3aa429 100644
--- a/spec/frontend/notes/stores/actions_spec.js
+++ b/spec/frontend/notes/stores/actions_spec.js
@@ -335,6 +335,9 @@ describe('Actions Notes Store', () => {
it('calls service with last fetched state', done => {
store
.dispatch('poll')
+ .then(() => {
+ jest.advanceTimersByTime(2);
+ })
.then(() => new Promise(resolve => requestAnimationFrame(resolve)))
.then(() => {
expect(store.state.lastFetchedAt).toBe('123456');
diff --git a/spec/frontend/pipelines/time_ago_spec.js b/spec/frontend/pipelines/time_ago_spec.js
index 04934fb93b0..b7bc8d08a0f 100644
--- a/spec/frontend/pipelines/time_ago_spec.js
+++ b/spec/frontend/pipelines/time_ago_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { GlIcon } from '@gitlab/ui';
import TimeAgo from '~/pipelines/components/pipelines_list/time_ago.vue';
describe('Timeago component', () => {
@@ -22,14 +23,19 @@ describe('Timeago component', () => {
wrapper = null;
});
+ const duration = () => wrapper.find('.duration');
+ const finishedAt = () => wrapper.find('.finished-at');
+
describe('with duration', () => {
beforeEach(() => {
createComponent({ duration: 10, finishedTime: '' });
});
it('should render duration and timer svg', () => {
- expect(wrapper.find('.duration').exists()).toBe(true);
- expect(wrapper.find('.duration svg').exists()).toBe(true);
+ const icon = duration().find(GlIcon);
+
+ expect(duration().exists()).toBe(true);
+ expect(icon.props('name')).toBe('timer');
});
});
@@ -39,7 +45,7 @@ describe('Timeago component', () => {
});
it('should not render duration and timer svg', () => {
- expect(wrapper.find('.duration').exists()).toBe(false);
+ expect(duration().exists()).toBe(false);
});
});
@@ -49,9 +55,12 @@ describe('Timeago component', () => {
});
it('should render time and calendar icon', () => {
- expect(wrapper.find('.finished-at').exists()).toBe(true);
- expect(wrapper.find('.finished-at i.fa-calendar').exists()).toBe(true);
- expect(wrapper.find('.finished-at time').exists()).toBe(true);
+ const icon = finishedAt().find(GlIcon);
+ const time = finishedAt().find('time');
+
+ expect(finishedAt().exists()).toBe(true);
+ expect(icon.props('name')).toBe('calendar');
+ expect(time.exists()).toBe(true);
});
});
@@ -61,7 +70,7 @@ describe('Timeago component', () => {
});
it('should not render time and calendar icon', () => {
- expect(wrapper.find('.finished-at').exists()).toBe(false);
+ expect(finishedAt().exists()).toBe(false);
});
});
});
diff --git a/spec/frontend/releases/__snapshots__/util_spec.js.snap b/spec/frontend/releases/__snapshots__/util_spec.js.snap
new file mode 100644
index 00000000000..f56e296d106
--- /dev/null
+++ b/spec/frontend/releases/__snapshots__/util_spec.js.snap
@@ -0,0 +1,113 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`releases/util.js convertGraphQLResponse matches snapshot 1`] = `
+Object {
+ "data": Array [
+ Object {
+ "_links": Object {
+ "editUrl": "http://0.0.0.0:3000/root/release-test/-/releases/v5.10/edit",
+ "issuesUrl": null,
+ "mergeRequestsUrl": null,
+ "self": "http://0.0.0.0:3000/root/release-test/-/releases/v5.10",
+ "selfUrl": "http://0.0.0.0:3000/root/release-test/-/releases/v5.10",
+ },
+ "assets": Object {
+ "count": 7,
+ "links": Array [
+ Object {
+ "directAssetUrl": "http://0.0.0.0:3000/root/release-test/-/releases/v5.32/permanent/path/to/runbook",
+ "external": true,
+ "id": "gid://gitlab/Releases::Link/69",
+ "linkType": "other",
+ "name": "An example link",
+ "url": "https://example.com/link",
+ },
+ Object {
+ "directAssetUrl": "https://example.com/package",
+ "external": true,
+ "id": "gid://gitlab/Releases::Link/68",
+ "linkType": "package",
+ "name": "An example package link",
+ "url": "https://example.com/package",
+ },
+ Object {
+ "directAssetUrl": "https://example.com/image",
+ "external": true,
+ "id": "gid://gitlab/Releases::Link/67",
+ "linkType": "image",
+ "name": "An example image",
+ "url": "https://example.com/image",
+ },
+ ],
+ "sources": Array [
+ Object {
+ "format": "zip",
+ "url": "http://0.0.0.0:3000/root/release-test/-/archive/v5.10/release-test-v5.10.zip",
+ },
+ Object {
+ "format": "tar.gz",
+ "url": "http://0.0.0.0:3000/root/release-test/-/archive/v5.10/release-test-v5.10.tar.gz",
+ },
+ Object {
+ "format": "tar.bz2",
+ "url": "http://0.0.0.0:3000/root/release-test/-/archive/v5.10/release-test-v5.10.tar.bz2",
+ },
+ Object {
+ "format": "tar",
+ "url": "http://0.0.0.0:3000/root/release-test/-/archive/v5.10/release-test-v5.10.tar",
+ },
+ ],
+ },
+ "author": Object {
+ "avatarUrl": "/uploads/-/system/user/avatar/1/avatar.png",
+ "username": "root",
+ "webUrl": "http://0.0.0.0:3000/root",
+ },
+ "commit": Object {
+ "shortId": "92e7ea2e",
+ "title": "Testing a change.",
+ },
+ "commitPath": "http://0.0.0.0:3000/root/release-test/-/commit/92e7ea2ee4496fe0d00ff69830ba0564d3d1e5a7",
+ "descriptionHtml": "<p data-sourcepos=\\"1:1-1:24\\" dir=\\"auto\\">This is version <strong>1.0</strong>!</p>",
+ "evidences": Array [
+ Object {
+ "collectedAt": "2020-08-21T20:15:19Z",
+ "filepath": "http://0.0.0.0:3000/root/release-test/-/releases/v5.10/evidences/34.json",
+ "sha": "22bde8e8b93d870a29ddc339287a1fbb598f45d1396d",
+ },
+ ],
+ "milestones": Array [
+ Object {
+ "description": "",
+ "id": "gid://gitlab/Milestone/60",
+ "issueStats": Object {
+ "closed": 0,
+ "total": 0,
+ },
+ "stats": undefined,
+ "title": "12.4",
+ "webPath": undefined,
+ "webUrl": "/root/release-test/-/milestones/2",
+ },
+ Object {
+ "description": "Milestone 12.3",
+ "id": "gid://gitlab/Milestone/59",
+ "issueStats": Object {
+ "closed": 1,
+ "total": 2,
+ },
+ "stats": undefined,
+ "title": "12.3",
+ "webPath": undefined,
+ "webUrl": "/root/release-test/-/milestones/1",
+ },
+ ],
+ "name": "Release 1.0",
+ "releasedAt": "2020-08-21T20:15:18Z",
+ "tagName": "v5.10",
+ "tagPath": "/root/release-test/-/tags/v5.10",
+ "upcomingRelease": false,
+ },
+ ],
+}
+`;
diff --git a/spec/frontend/releases/components/app_index_spec.js b/spec/frontend/releases/components/app_index_spec.js
index 0ffece36d90..1f485cb276e 100644
--- a/spec/frontend/releases/components/app_index_spec.js
+++ b/spec/frontend/releases/components/app_index_spec.js
@@ -20,6 +20,7 @@ localVue.use(Vuex);
describe('Releases App ', () => {
let wrapper;
+ let fetchReleaseSpy;
const releasesPagination = rge(21).map(index => ({
...convertObjectPropsToCamelCase(release, { deep: true }),
@@ -28,12 +29,22 @@ describe('Releases App ', () => {
const defaultProps = {
projectId: 'gitlab-ce',
+ projectPath: 'gitlab-org/gitlab-ce',
documentationPath: 'help/releases',
illustrationPath: 'illustration/path',
};
const createComponent = (propsData = defaultProps) => {
- const store = createStore({ modules: { list: listModule } });
+ fetchReleaseSpy = jest.spyOn(listModule.actions, 'fetchReleases');
+
+ const store = createStore({
+ modules: { list: listModule },
+ featureFlags: {
+ graphqlReleaseData: true,
+ graphqlReleasesPage: false,
+ graphqlMilestoneStats: true,
+ },
+ });
wrapper = shallowMount(ReleasesApp, {
store,
@@ -46,6 +57,25 @@ describe('Releases App ', () => {
wrapper.destroy();
});
+ describe('on startup', () => {
+ beforeEach(() => {
+ jest
+ .spyOn(api, 'releases')
+ .mockResolvedValue({ data: releases, headers: pageInfoHeadersWithoutPagination });
+
+ createComponent();
+ });
+
+ it('calls fetchRelease with the page, project ID, and project path', () => {
+ expect(fetchReleaseSpy).toHaveBeenCalledTimes(1);
+ expect(fetchReleaseSpy).toHaveBeenCalledWith(expect.anything(), {
+ page: null,
+ projectId: defaultProps.projectId,
+ projectPath: defaultProps.projectPath,
+ });
+ });
+ });
+
describe('while loading', () => {
beforeEach(() => {
jest
diff --git a/spec/frontend/releases/mock_data.js b/spec/frontend/releases/mock_data.js
index b97385154bd..58cd69a2f6a 100644
--- a/spec/frontend/releases/mock_data.js
+++ b/spec/frontend/releases/mock_data.js
@@ -222,3 +222,131 @@ export const release2 = {
};
export const releases = [release, release2];
+
+export const graphqlReleasesResponse = {
+ data: {
+ project: {
+ releases: {
+ count: 39,
+ nodes: [
+ {
+ name: 'Release 1.0',
+ tagName: 'v5.10',
+ tagPath: '/root/release-test/-/tags/v5.10',
+ descriptionHtml:
+ '<p data-sourcepos="1:1-1:24" dir="auto">This is version <strong>1.0</strong>!</p>',
+ releasedAt: '2020-08-21T20:15:18Z',
+ upcomingRelease: false,
+ assets: {
+ count: 7,
+ sources: {
+ nodes: [
+ {
+ format: 'zip',
+ url:
+ 'http://0.0.0.0:3000/root/release-test/-/archive/v5.10/release-test-v5.10.zip',
+ },
+ {
+ format: 'tar.gz',
+ url:
+ 'http://0.0.0.0:3000/root/release-test/-/archive/v5.10/release-test-v5.10.tar.gz',
+ },
+ {
+ format: 'tar.bz2',
+ url:
+ 'http://0.0.0.0:3000/root/release-test/-/archive/v5.10/release-test-v5.10.tar.bz2',
+ },
+ {
+ format: 'tar',
+ url:
+ 'http://0.0.0.0:3000/root/release-test/-/archive/v5.10/release-test-v5.10.tar',
+ },
+ ],
+ },
+ links: {
+ nodes: [
+ {
+ id: 'gid://gitlab/Releases::Link/69',
+ name: 'An example link',
+ url: 'https://example.com/link',
+ directAssetUrl:
+ 'http://0.0.0.0:3000/root/release-test/-/releases/v5.32/permanent/path/to/runbook',
+ linkType: 'OTHER',
+ external: true,
+ },
+ {
+ id: 'gid://gitlab/Releases::Link/68',
+ name: 'An example package link',
+ url: 'https://example.com/package',
+ directAssetUrl: 'https://example.com/package',
+ linkType: 'PACKAGE',
+ external: true,
+ },
+ {
+ id: 'gid://gitlab/Releases::Link/67',
+ name: 'An example image',
+ url: 'https://example.com/image',
+ directAssetUrl: 'https://example.com/image',
+ linkType: 'IMAGE',
+ external: true,
+ },
+ ],
+ },
+ },
+ evidences: {
+ nodes: [
+ {
+ filepath:
+ 'http://0.0.0.0:3000/root/release-test/-/releases/v5.10/evidences/34.json',
+ collectedAt: '2020-08-21T20:15:19Z',
+ sha: '22bde8e8b93d870a29ddc339287a1fbb598f45d1396d',
+ },
+ ],
+ },
+ links: {
+ editUrl: 'http://0.0.0.0:3000/root/release-test/-/releases/v5.10/edit',
+ issuesUrl: null,
+ mergeRequestsUrl: null,
+ selfUrl: 'http://0.0.0.0:3000/root/release-test/-/releases/v5.10',
+ },
+ commit: {
+ sha: '92e7ea2ee4496fe0d00ff69830ba0564d3d1e5a7',
+ webUrl:
+ 'http://0.0.0.0:3000/root/release-test/-/commit/92e7ea2ee4496fe0d00ff69830ba0564d3d1e5a7',
+ title: 'Testing a change.',
+ },
+ author: {
+ webUrl: 'http://0.0.0.0:3000/root',
+ avatarUrl: '/uploads/-/system/user/avatar/1/avatar.png',
+ username: 'root',
+ },
+ milestones: {
+ nodes: [
+ {
+ id: 'gid://gitlab/Milestone/60',
+ title: '12.4',
+ description: '',
+ webPath: '/root/release-test/-/milestones/2',
+ stats: {
+ totalIssuesCount: 0,
+ closedIssuesCount: 0,
+ },
+ },
+ {
+ id: 'gid://gitlab/Milestone/59',
+ title: '12.3',
+ description: 'Milestone 12.3',
+ webPath: '/root/release-test/-/milestones/1',
+ stats: {
+ totalIssuesCount: 2,
+ closedIssuesCount: 1,
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ },
+};
diff --git a/spec/frontend/releases/stores/modules/list/actions_spec.js b/spec/frontend/releases/stores/modules/list/actions_spec.js
index 4c3af157684..88653d3502f 100644
--- a/spec/frontend/releases/stores/modules/list/actions_spec.js
+++ b/spec/frontend/releases/stores/modules/list/actions_spec.js
@@ -1,3 +1,4 @@
+import { cloneDeep } from 'lodash';
import testAction from 'helpers/vuex_action_helper';
import {
requestReleases,
@@ -8,18 +9,36 @@ import {
import state from '~/releases/stores/modules/list/state';
import * as types from '~/releases/stores/modules/list/mutation_types';
import api from '~/api';
+import { gqClient, convertGraphQLResponse } from '~/releases/util';
import { parseIntPagination, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
-import { pageInfoHeadersWithoutPagination, releases as originalReleases } from '../../../mock_data';
+import {
+ pageInfoHeadersWithoutPagination,
+ releases as originalReleases,
+ graphqlReleasesResponse as originalGraphqlReleasesResponse,
+} from '../../../mock_data';
+import allReleasesQuery from '~/releases/queries/all_releases.query.graphql';
describe('Releases State actions', () => {
let mockedState;
let pageInfo;
let releases;
+ let graphqlReleasesResponse;
+ let projectPath;
beforeEach(() => {
- mockedState = state();
+ mockedState = {
+ ...state(),
+ featureFlags: {
+ graphqlReleaseData: true,
+ graphqlReleasesPage: true,
+ graphqlMilestoneStats: true,
+ },
+ };
+
pageInfo = parseIntPagination(pageInfoHeadersWithoutPagination);
releases = convertObjectPropsToCamelCase(originalReleases, { deep: true });
+ graphqlReleasesResponse = cloneDeep(originalGraphqlReleasesResponse);
+ projectPath = 'root/test-project';
});
describe('requestReleases', () => {
@@ -31,39 +50,17 @@ describe('Releases State actions', () => {
describe('fetchReleases', () => {
describe('success', () => {
it('dispatches requestReleases and receiveReleasesSuccess', done => {
- jest.spyOn(api, 'releases').mockImplementation((id, options) => {
- expect(id).toEqual(1);
- expect(options.page).toEqual('1');
- return Promise.resolve({ data: releases, headers: pageInfoHeadersWithoutPagination });
- });
-
- testAction(
- fetchReleases,
- { projectId: 1 },
- mockedState,
- [],
- [
- {
- type: 'requestReleases',
- },
- {
- payload: { data: releases, headers: pageInfoHeadersWithoutPagination },
- type: 'receiveReleasesSuccess',
- },
- ],
- done,
- );
- });
-
- it('dispatches requestReleases and receiveReleasesSuccess on page two', done => {
- jest.spyOn(api, 'releases').mockImplementation((_, options) => {
- expect(options.page).toEqual('2');
- return Promise.resolve({ data: releases, headers: pageInfoHeadersWithoutPagination });
+ jest.spyOn(gqClient, 'query').mockImplementation(({ query, variables }) => {
+ expect(query).toEqual(allReleasesQuery);
+ expect(variables).toEqual({
+ fullPath: projectPath,
+ });
+ return Promise.resolve(graphqlReleasesResponse);
});
testAction(
fetchReleases,
- { page: '2', projectId: 1 },
+ { projectPath },
mockedState,
[],
[
@@ -71,7 +68,7 @@ describe('Releases State actions', () => {
type: 'requestReleases',
},
{
- payload: { data: releases, headers: pageInfoHeadersWithoutPagination },
+ payload: convertGraphQLResponse(graphqlReleasesResponse),
type: 'receiveReleasesSuccess',
},
],
@@ -82,11 +79,11 @@ describe('Releases State actions', () => {
describe('error', () => {
it('dispatches requestReleases and receiveReleasesError', done => {
- jest.spyOn(api, 'releases').mockReturnValue(Promise.reject());
+ jest.spyOn(gqClient, 'query').mockRejectedValue();
testAction(
fetchReleases,
- { projectId: null },
+ { projectPath },
mockedState,
[],
[
@@ -101,6 +98,85 @@ describe('Releases State actions', () => {
);
});
});
+
+ describe('when the graphqlReleaseData feature flag is disabled', () => {
+ beforeEach(() => {
+ mockedState.featureFlags.graphqlReleasesPage = false;
+ });
+
+ describe('success', () => {
+ it('dispatches requestReleases and receiveReleasesSuccess', done => {
+ jest.spyOn(api, 'releases').mockImplementation((id, options) => {
+ expect(id).toEqual(1);
+ expect(options.page).toEqual('1');
+ return Promise.resolve({ data: releases, headers: pageInfoHeadersWithoutPagination });
+ });
+
+ testAction(
+ fetchReleases,
+ { projectId: 1 },
+ mockedState,
+ [],
+ [
+ {
+ type: 'requestReleases',
+ },
+ {
+ payload: { data: releases, headers: pageInfoHeadersWithoutPagination },
+ type: 'receiveReleasesSuccess',
+ },
+ ],
+ done,
+ );
+ });
+
+ it('dispatches requestReleases and receiveReleasesSuccess on page two', done => {
+ jest.spyOn(api, 'releases').mockImplementation((_, options) => {
+ expect(options.page).toEqual('2');
+ return Promise.resolve({ data: releases, headers: pageInfoHeadersWithoutPagination });
+ });
+
+ testAction(
+ fetchReleases,
+ { page: '2', projectId: 1 },
+ mockedState,
+ [],
+ [
+ {
+ type: 'requestReleases',
+ },
+ {
+ payload: { data: releases, headers: pageInfoHeadersWithoutPagination },
+ type: 'receiveReleasesSuccess',
+ },
+ ],
+ done,
+ );
+ });
+ });
+
+ describe('error', () => {
+ it('dispatches requestReleases and receiveReleasesError', done => {
+ jest.spyOn(api, 'releases').mockReturnValue(Promise.reject());
+
+ testAction(
+ fetchReleases,
+ { projectId: null },
+ mockedState,
+ [],
+ [
+ {
+ type: 'requestReleases',
+ },
+ {
+ type: 'receiveReleasesError',
+ },
+ ],
+ done,
+ );
+ });
+ });
+ });
});
describe('receiveReleasesSuccess', () => {
diff --git a/spec/frontend/releases/util_spec.js b/spec/frontend/releases/util_spec.js
index 90aa9c4c7d8..f40e5729188 100644
--- a/spec/frontend/releases/util_spec.js
+++ b/spec/frontend/releases/util_spec.js
@@ -1,4 +1,6 @@
-import { releaseToApiJson, apiJsonToRelease } from '~/releases/util';
+import { cloneDeep } from 'lodash';
+import { releaseToApiJson, apiJsonToRelease, convertGraphQLResponse } from '~/releases/util';
+import { graphqlReleasesResponse as originalGraphqlReleasesResponse } from './mock_data';
describe('releases/util.js', () => {
describe('releaseToApiJson', () => {
@@ -100,4 +102,55 @@ describe('releases/util.js', () => {
expect(apiJsonToRelease(json)).toEqual(expectedRelease);
});
});
+
+ describe('convertGraphQLResponse', () => {
+ let graphqlReleasesResponse;
+ let converted;
+
+ beforeEach(() => {
+ graphqlReleasesResponse = cloneDeep(originalGraphqlReleasesResponse);
+ converted = convertGraphQLResponse(graphqlReleasesResponse);
+ });
+
+ it('matches snapshot', () => {
+ expect(converted).toMatchSnapshot();
+ });
+
+ describe('assets', () => {
+ it("handles asset links that don't have a linkType", () => {
+ expect(converted.data[0].assets.links[0].linkType).not.toBeUndefined();
+
+ delete graphqlReleasesResponse.data.project.releases.nodes[0].assets.links.nodes[0]
+ .linkType;
+
+ converted = convertGraphQLResponse(graphqlReleasesResponse);
+
+ expect(converted.data[0].assets.links[0].linkType).toBeUndefined();
+ });
+ });
+
+ describe('_links', () => {
+ it("handles releases that don't have any links", () => {
+ expect(converted.data[0]._links.selfUrl).not.toBeUndefined();
+
+ delete graphqlReleasesResponse.data.project.releases.nodes[0].links;
+
+ converted = convertGraphQLResponse(graphqlReleasesResponse);
+
+ expect(converted.data[0]._links.selfUrl).toBeUndefined();
+ });
+ });
+
+ describe('commit', () => {
+ it("handles releases that don't have any commit info", () => {
+ expect(converted.data[0].commit).not.toBeUndefined();
+
+ delete graphqlReleasesResponse.data.project.releases.nodes[0].commit;
+
+ converted = convertGraphQLResponse(graphqlReleasesResponse);
+
+ expect(converted.data[0].commit).toBeUndefined();
+ });
+ });
+ });
});
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js b/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js
index 0eb90f5529d..e0a3208cac9 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js
@@ -1,6 +1,7 @@
import { mockLabels } from 'jest/vue_shared/components/sidebar/labels_select_vue/mock_data';
import Api from '~/api';
import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue';
+import BranchToken from '~/vue_shared/components/filtered_search_bar/tokens/branch_token.vue';
import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue';
import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue';
@@ -33,6 +34,8 @@ export const mockAuthor3 = {
export const mockAuthors = [mockAuthor1, mockAuthor2, mockAuthor3];
+export const mockBranches = [{ name: 'Master' }, { name: 'v1.x' }, { name: 'my-Branch' }];
+
export const mockRegularMilestone = {
id: 1,
name: '4.0',
@@ -55,6 +58,16 @@ export const mockMilestones = [
mockEscapedMilestone,
];
+export const mockBranchToken = {
+ type: 'source_branch',
+ icon: 'branch',
+ title: 'Source Branch',
+ unique: true,
+ token: BranchToken,
+ operators: [{ value: '=', description: 'is', default: 'true' }],
+ fetchBranches: Api.branches.bind(Api),
+};
+
export const mockAuthorToken = {
type: 'author_username',
icon: 'user',
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/branch_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/branch_token_spec.js
new file mode 100644
index 00000000000..12b7fd58670
--- /dev/null
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/branch_token_spec.js
@@ -0,0 +1,207 @@
+import { mount } from '@vue/test-utils';
+import {
+ GlFilteredSearchToken,
+ GlFilteredSearchSuggestion,
+ GlFilteredSearchTokenSegment,
+ GlDropdownDivider,
+} from '@gitlab/ui';
+import MockAdapter from 'axios-mock-adapter';
+import waitForPromises from 'helpers/wait_for_promises';
+
+import axios from '~/lib/utils/axios_utils';
+import createFlash from '~/flash';
+import {
+ DEFAULT_LABEL_NONE,
+ DEFAULT_LABEL_ANY,
+} from '~/vue_shared/components/filtered_search_bar/constants';
+import BranchToken from '~/vue_shared/components/filtered_search_bar/tokens/branch_token.vue';
+
+import { mockBranches, mockBranchToken } from '../mock_data';
+
+jest.mock('~/flash');
+const defaultStubs = {
+ Portal: true,
+ GlFilteredSearchSuggestionList: {
+ template: '<div></div>',
+ methods: {
+ getValue: () => '=',
+ },
+ },
+};
+
+function createComponent(options = {}) {
+ const {
+ config = mockBranchToken,
+ value = { data: '' },
+ active = false,
+ stubs = defaultStubs,
+ } = options;
+ return mount(BranchToken, {
+ propsData: {
+ config,
+ value,
+ active,
+ },
+ provide: {
+ portalName: 'fake target',
+ alignSuggestions: function fakeAlignSuggestions() {},
+ },
+ stubs,
+ });
+}
+
+describe('BranchToken', () => {
+ let mock;
+ let wrapper;
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ wrapper.destroy();
+ });
+
+ describe('computed', () => {
+ beforeEach(async () => {
+ wrapper = createComponent({ value: { data: mockBranches[0].name } });
+
+ wrapper.setData({
+ branches: mockBranches,
+ });
+
+ await wrapper.vm.$nextTick();
+ });
+
+ describe('currentValue', () => {
+ it('returns lowercase string for `value.data`', () => {
+ expect(wrapper.vm.currentValue).toBe('master');
+ });
+ });
+
+ describe('activeBranch', () => {
+ it('returns object for currently present `value.data`', () => {
+ expect(wrapper.vm.activeBranch).toEqual(mockBranches[0]);
+ });
+ });
+ });
+
+ describe('methods', () => {
+ beforeEach(() => {
+ wrapper = createComponent();
+ });
+
+ describe('fetchBranchBySearchTerm', () => {
+ it('calls `config.fetchBranches` with provided searchTerm param', () => {
+ jest.spyOn(wrapper.vm.config, 'fetchBranches');
+
+ wrapper.vm.fetchBranchBySearchTerm('foo');
+
+ expect(wrapper.vm.config.fetchBranches).toHaveBeenCalledWith('foo');
+ });
+
+ it('sets response to `branches` when request is succesful', () => {
+ jest.spyOn(wrapper.vm.config, 'fetchBranches').mockResolvedValue({ data: mockBranches });
+
+ wrapper.vm.fetchBranchBySearchTerm('foo');
+
+ return waitForPromises().then(() => {
+ expect(wrapper.vm.branches).toEqual(mockBranches);
+ });
+ });
+
+ it('calls `createFlash` with flash error message when request fails', () => {
+ jest.spyOn(wrapper.vm.config, 'fetchBranches').mockRejectedValue({});
+
+ wrapper.vm.fetchBranchBySearchTerm('foo');
+
+ return waitForPromises().then(() => {
+ expect(createFlash).toHaveBeenCalledWith({
+ message: 'There was a problem fetching branches.',
+ });
+ });
+ });
+
+ it('sets `loading` to false when request completes', () => {
+ jest.spyOn(wrapper.vm.config, 'fetchBranches').mockRejectedValue({});
+
+ wrapper.vm.fetchBranchBySearchTerm('foo');
+
+ return waitForPromises().then(() => {
+ expect(wrapper.vm.loading).toBe(false);
+ });
+ });
+ });
+ });
+
+ describe('template', () => {
+ const defaultBranches = [DEFAULT_LABEL_NONE, DEFAULT_LABEL_ANY];
+ async function showSuggestions() {
+ const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
+ const suggestionsSegment = tokenSegments.at(2);
+ suggestionsSegment.vm.$emit('activate');
+ await wrapper.vm.$nextTick();
+ }
+
+ beforeEach(async () => {
+ wrapper = createComponent({ value: { data: mockBranches[0].name } });
+
+ wrapper.setData({
+ branches: mockBranches,
+ });
+
+ await wrapper.vm.$nextTick();
+ });
+
+ it('renders gl-filtered-search-token component', () => {
+ expect(wrapper.find(GlFilteredSearchToken).exists()).toBe(true);
+ });
+
+ it('renders token item when value is selected', () => {
+ const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
+
+ expect(tokenSegments).toHaveLength(3);
+ expect(tokenSegments.at(2).text()).toBe(mockBranches[0].name);
+ });
+
+ it('renders provided defaultBranches as suggestions', async () => {
+ wrapper = createComponent({
+ active: true,
+ config: { ...mockBranchToken, defaultBranches },
+ stubs: { Portal: true },
+ });
+ await showSuggestions();
+ const suggestions = wrapper.findAll(GlFilteredSearchSuggestion);
+
+ expect(suggestions).toHaveLength(defaultBranches.length);
+ defaultBranches.forEach((branch, index) => {
+ expect(suggestions.at(index).text()).toBe(branch.text);
+ });
+ });
+
+ it('does not render divider when no defaultBranches', async () => {
+ wrapper = createComponent({
+ active: true,
+ config: { ...mockBranchToken, defaultBranches: [] },
+ stubs: { Portal: true },
+ });
+ await showSuggestions();
+
+ expect(wrapper.contains(GlFilteredSearchSuggestion)).toBe(false);
+ expect(wrapper.contains(GlDropdownDivider)).toBe(false);
+ });
+
+ it('renders no suggestions as default', async () => {
+ wrapper = createComponent({
+ active: true,
+ config: { ...mockBranchToken },
+ stubs: { Portal: true },
+ });
+ await showSuggestions();
+ const suggestions = wrapper.findAll(GlFilteredSearchSuggestion);
+
+ expect(suggestions).toHaveLength(0);
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/markdown/suggestion_diff_row_spec.js b/spec/frontend/vue_shared/components/markdown/suggestion_diff_row_spec.js
index 57d4ff93eea..b67f4cf12bf 100644
--- a/spec/frontend/vue_shared/components/markdown/suggestion_diff_row_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/suggestion_diff_row_spec.js
@@ -34,12 +34,25 @@ describe('SuggestionDiffRow', () => {
const findOldLineWrapper = () => wrapper.find('.old_line');
const findNewLineWrapper = () => wrapper.find('.new_line');
+ const findSuggestionContent = () => wrapper.find('[data-testid="suggestion-diff-content"]');
afterEach(() => {
wrapper.destroy();
});
describe('renders correctly', () => {
+ it('renders the correct base suggestion markup', () => {
+ factory({
+ propsData: {
+ line: oldLine,
+ },
+ });
+
+ expect(findSuggestionContent().html()).toBe(
+ '<td data-testid="suggestion-diff-content" class="line_content old"><span class="line">oldrichtext</span></td>',
+ );
+ });
+
it('has the right classes on the wrapper', () => {
factory({
propsData: {
@@ -48,6 +61,11 @@ describe('SuggestionDiffRow', () => {
});
expect(wrapper.classes()).toContain('line_holder');
+ expect(
+ findSuggestionContent()
+ .find('span')
+ .classes(),
+ ).toContain('line');
});
it('renders the rich text when it is available', () => {
diff --git a/spec/helpers/releases_helper_spec.rb b/spec/helpers/releases_helper_spec.rb
index 6ae99648ff3..f10a2ed8e60 100644
--- a/spec/helpers/releases_helper_spec.rb
+++ b/spec/helpers/releases_helper_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe ReleasesHelper do
let(:release) { create(:release, project: project) }
let(:user) { create(:user) }
let(:can_user_create_release) { false }
- let(:common_keys) { [:project_id, :illustration_path, :documentation_path] }
+ let(:common_keys) { [:project_id, :project_path, :illustration_path, :documentation_path] }
# rubocop: disable CodeReuse/ActiveRecord
before do
diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/none_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/none_spec.rb
new file mode 100644
index 00000000000..3250c7cfa31
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/none_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies::None do
+ let(:fake_duplicate_job) do
+ instance_double(Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob)
+ end
+
+ subject(:strategy) { described_class.new(fake_duplicate_job) }
+
+ describe '#schedule' do
+ it 'yields without checking for duplicates', :aggregate_failures do
+ expect(fake_duplicate_job).not_to receive(:scheduled?)
+ expect(fake_duplicate_job).not_to receive(:duplicate?)
+ expect(fake_duplicate_job).not_to receive(:check!)
+
+ expect { |b| strategy.schedule({}, &b) }.to yield_control
+ end
+ end
+
+ describe '#perform' do
+ it 'does not delete any locks before executing', :aggregate_failures do
+ expect(fake_duplicate_job).not_to receive(:delete!)
+
+ expect { |b| strategy.perform({}, &b) }.to yield_control
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies_spec.rb
index 5d37e3cb1ae..84856238aab 100644
--- a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies_spec.rb
@@ -8,6 +8,10 @@ RSpec.describe Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies do
expect(described_class.for(:until_executing)).to eq(described_class::UntilExecuting)
end
+ it 'returns the right class for `none`' do
+ expect(described_class.for(:none)).to eq(described_class::None)
+ end
+
it 'raises an UnknownStrategyError when passing an unknown key' do
expect { described_class.for(:unknown) }.to raise_error(described_class::UnknownStrategyError)
end
diff --git a/spec/services/admin/propagate_integration_service_spec.rb b/spec/services/admin/propagate_integration_service_spec.rb
index 8b895cfb449..49d974b7154 100644
--- a/spec/services/admin/propagate_integration_service_spec.rb
+++ b/spec/services/admin/propagate_integration_service_spec.rb
@@ -10,8 +10,9 @@ RSpec.describe Admin::PropagateIntegrationService do
stub_jira_service_test
end
- let(:excluded_attributes) { %w[id project_id inherit_from_id instance created_at updated_at default] }
+ let(:excluded_attributes) { %w[id project_id group_id inherit_from_id instance created_at updated_at default] }
let!(:project) { create(:project) }
+ let!(:group) { create(:group) }
let!(:instance_integration) do
JiraService.create!(
instance: true,
@@ -109,6 +110,10 @@ RSpec.describe Admin::PropagateIntegrationService do
it_behaves_like 'inherits settings from integration' do
let(:integration) { project.jira_service }
end
+
+ it_behaves_like 'inherits settings from integration' do
+ let(:integration) { Service.find_by(group_id: group.id) }
+ end
end
it 'updates project#has_external_issue_tracker for issue tracker services' do