summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/features/issues/user_creates_issue_spec.rb13
-rw-r--r--spec/features/projects/graph_spec.rb6
-rw-r--r--spec/fixtures/lib/gitlab/import_export/project.json11
-rw-r--r--spec/frontend/contributors/component/__snapshots__/contributors_spec.js.snap47
-rw-r--r--spec/frontend/contributors/component/contributors_spec.js69
-rw-r--r--spec/frontend/contributors/store/actions_spec.js60
-rw-r--r--spec/frontend/contributors/store/getters_spec.js73
-rw-r--r--spec/frontend/contributors/store/mutations_spec.js40
-rw-r--r--spec/frontend/contributors/utils_spec.js21
-rw-r--r--spec/frontend/lib/utils/datetime_utility_spec.js31
-rw-r--r--spec/helpers/issuables_helper_spec.rb55
-rw-r--r--spec/helpers/search_helper_spec.rb46
-rw-r--r--spec/javascripts/graphs/stat_graph_contributors_graph_spec.js152
-rw-r--r--spec/javascripts/graphs/stat_graph_contributors_spec.js28
-rw-r--r--spec/javascripts/graphs/stat_graph_contributors_util_spec.js298
-rw-r--r--spec/lib/gitlab/ci/config/entry/default_spec.rb14
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb12
-rw-r--r--spec/lib/gitlab/ci/config/entry/root_spec.rb5
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml3
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb7
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml8
-rw-r--r--spec/services/issues/update_service_spec.rb14
-rw-r--r--spec/services/issues/zoom_link_service_spec.rb162
-rw-r--r--spec/services/zoom_notes_service_spec.rb81
-rw-r--r--spec/support/shared_examples/lib/gitlab/config/inheritable_shared_examples.rb105
-rw-r--r--spec/support/shared_examples/quick_actions/issue/zoom_quick_actions_shared_examples.rb43
26 files changed, 687 insertions, 717 deletions
diff --git a/spec/features/issues/user_creates_issue_spec.rb b/spec/features/issues/user_creates_issue_spec.rb
index a71395c0e47..39ce3415727 100644
--- a/spec/features/issues/user_creates_issue_spec.rb
+++ b/spec/features/issues/user_creates_issue_spec.rb
@@ -92,19 +92,6 @@ describe "User creates issue" do
.and have_content(label_titles.first)
end
end
-
- context "with Zoom link" do
- it "adds Zoom button" do
- issue_title = "Issue containing Zoom meeting link"
- zoom_url = "https://gitlab.zoom.us/j/123456789"
-
- fill_in("Title", with: issue_title)
- fill_in("Description", with: zoom_url)
- click_button("Submit issue")
-
- expect(page).to have_link('Join Zoom meeting', href: zoom_url)
- end
- end
end
context "when signed in as user with special characters in their name" do
diff --git a/spec/features/projects/graph_spec.rb b/spec/features/projects/graph_spec.rb
index 6082eb03374..5dabaf20952 100644
--- a/spec/features/projects/graph_spec.rb
+++ b/spec/features/projects/graph_spec.rb
@@ -29,12 +29,6 @@ describe 'Project Graph', :js do
end
end
- it 'renders graphs' do
- visit project_graph_path(project, 'master')
-
- expect(page).to have_selector('.stat-graph', visible: false)
- end
-
context 'commits graph' do
before do
visit commits_project_graph_path(project, 'master')
diff --git a/spec/fixtures/lib/gitlab/import_export/project.json b/spec/fixtures/lib/gitlab/import_export/project.json
index fbd752b7403..864933ca1b4 100644
--- a/spec/fixtures/lib/gitlab/import_export/project.json
+++ b/spec/fixtures/lib/gitlab/import_export/project.json
@@ -80,6 +80,17 @@
"issue_id": 40
}
],
+ "zoom_meetings": [
+ {
+ "id": 1,
+ "project_id": 5,
+ "issue_id": 40,
+ "url": "https://zoom.us/j/123456789",
+ "issue_status": 1,
+ "created_at": "2016-06-14T15:02:04.418Z",
+ "updated_at": "2016-06-14T15:02:04.418Z"
+ }
+ ],
"milestone": {
"id": 1,
"title": "test milestone",
diff --git a/spec/frontend/contributors/component/__snapshots__/contributors_spec.js.snap b/spec/frontend/contributors/component/__snapshots__/contributors_spec.js.snap
new file mode 100644
index 00000000000..b87afdd7eb4
--- /dev/null
+++ b/spec/frontend/contributors/component/__snapshots__/contributors_spec.js.snap
@@ -0,0 +1,47 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Contributors charts should render charts when loading completed and there is chart data 1`] = `
+<div>
+ <div
+ class="contributors-charts"
+ >
+ <h4>
+ Commits to master
+ </h4>
+
+ <span>
+ Excluding merge commits. Limited to 6,000 commits.
+ </span>
+
+ <div>
+ <glareachart-stub
+ data="[object Object]"
+ height="264"
+ option="[object Object]"
+ />
+ </div>
+
+ <div
+ class="row"
+ >
+ <div
+ class="col-6"
+ >
+ <h4>
+ John
+ </h4>
+
+ <p>
+ 2 commits (jawnnypoo@gmail.com)
+ </p>
+
+ <glareachart-stub
+ data="[object Object]"
+ height="216"
+ option="[object Object]"
+ />
+ </div>
+ </div>
+ </div>
+</div>
+`;
diff --git a/spec/frontend/contributors/component/contributors_spec.js b/spec/frontend/contributors/component/contributors_spec.js
new file mode 100644
index 00000000000..fdba09ed26c
--- /dev/null
+++ b/spec/frontend/contributors/component/contributors_spec.js
@@ -0,0 +1,69 @@
+import Vue from 'vue';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { createStore } from '~/contributors/stores';
+import axios from '~/lib/utils/axios_utils';
+import MockAdapter from 'axios-mock-adapter';
+import ContributorsCharts from '~/contributors/components/contributors.vue';
+
+const localVue = createLocalVue();
+let wrapper;
+let mock;
+let store;
+const Component = Vue.extend(ContributorsCharts);
+const endpoint = 'contributors';
+const branch = 'master';
+const chartData = [
+ { author_name: 'John', author_email: 'jawnnypoo@gmail.com', date: '2019-05-05' },
+ { author_name: 'John', author_email: 'jawnnypoo@gmail.com', date: '2019-03-03' },
+];
+
+function factory() {
+ mock = new MockAdapter(axios);
+ jest.spyOn(axios, 'get');
+ mock.onGet().reply(200, chartData);
+ store = createStore();
+
+ wrapper = shallowMount(Component, {
+ propsData: {
+ endpoint,
+ branch,
+ },
+ stubs: {
+ GlLoadingIcon: true,
+ GlAreaChart: true,
+ },
+ store,
+ });
+}
+
+describe('Contributors charts', () => {
+ beforeEach(() => {
+ factory();
+ });
+
+ afterEach(() => {
+ mock.restore();
+ wrapper.destroy();
+ });
+
+ it('should fetch chart data when mounted', () => {
+ expect(axios.get).toHaveBeenCalledWith(endpoint);
+ });
+
+ it('should display loader whiled loading data', () => {
+ wrapper.vm.$store.state.loading = true;
+ return localVue.nextTick(() => {
+ expect(wrapper.find('.contributors-loader').exists()).toBe(true);
+ });
+ });
+
+ it('should render charts when loading completed and there is chart data', () => {
+ wrapper.vm.$store.state.loading = false;
+ wrapper.vm.$store.state.chartData = chartData;
+ return localVue.nextTick(() => {
+ expect(wrapper.find('.contributors-loader').exists()).toBe(false);
+ expect(wrapper.find('.contributors-charts').exists()).toBe(true);
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+});
diff --git a/spec/frontend/contributors/store/actions_spec.js b/spec/frontend/contributors/store/actions_spec.js
new file mode 100644
index 00000000000..bb017e0ac0f
--- /dev/null
+++ b/spec/frontend/contributors/store/actions_spec.js
@@ -0,0 +1,60 @@
+import axios from '~/lib/utils/axios_utils';
+import MockAdapter from 'axios-mock-adapter';
+import testAction from 'helpers/vuex_action_helper';
+import flashError from '~/flash';
+import * as actions from '~/contributors/stores/actions';
+import * as types from '~/contributors/stores/mutation_types';
+
+jest.mock('~/flash.js');
+
+describe('Contributors store actions', () => {
+ describe('fetchChartData', () => {
+ let mock;
+ const endpoint = '/contributors';
+ const chartData = { '2017-11': 0, '2017-12': 2 };
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ it('should commit SET_CHART_DATA with received response', done => {
+ mock.onGet().reply(200, chartData);
+
+ testAction(
+ actions.fetchChartData,
+ { endpoint },
+ {},
+ [
+ { type: types.SET_LOADING_STATE, payload: true },
+ { type: types.SET_CHART_DATA, payload: chartData },
+ { type: types.SET_LOADING_STATE, payload: false },
+ ],
+ [],
+ () => {
+ mock.restore();
+ done();
+ },
+ );
+ });
+
+ it('should show flash on API error', done => {
+ mock.onGet().reply(400, 'Not Found');
+
+ testAction(
+ actions.fetchChartData,
+ { endpoint },
+ {},
+ [{ type: types.SET_LOADING_STATE, payload: true }],
+ [],
+ () => {
+ expect(flashError).toHaveBeenCalledWith(expect.stringMatching('error'));
+ mock.restore();
+ done();
+ },
+ );
+ });
+ });
+});
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/spec/frontend/contributors/store/getters_spec.js b/spec/frontend/contributors/store/getters_spec.js
new file mode 100644
index 00000000000..62ae9b36f87
--- /dev/null
+++ b/spec/frontend/contributors/store/getters_spec.js
@@ -0,0 +1,73 @@
+import * as getters from '~/contributors/stores/getters';
+
+describe('Contributors Store Getters', () => {
+ const state = {};
+
+ describe('showChart', () => {
+ it('should NOT show chart if loading', () => {
+ state.loading = true;
+
+ expect(getters.showChart(state)).toEqual(false);
+ });
+
+ it('should NOT show chart there is not data', () => {
+ state.loading = false;
+ state.chartData = null;
+
+ expect(getters.showChart(state)).toEqual(false);
+ });
+
+ it('should show the chart in case loading complated and there is data', () => {
+ state.loading = false;
+ state.chartData = true;
+
+ expect(getters.showChart(state)).toEqual(true);
+ });
+
+ describe('parsedData', () => {
+ let parsed;
+
+ beforeAll(() => {
+ state.chartData = [
+ { author_name: 'John', author_email: 'jawnnypoo@gmail.com', date: '2019-05-05' },
+ { author_name: 'John', author_email: 'jawnnypoo@gmail.com', date: '2019-05-05' },
+ { author_name: 'Carlson', author_email: 'jawnnypoo@gmail.com', date: '2019-03-03' },
+ { author_name: 'Carlson', author_email: 'jawnnypoo@gmail.com', date: '2019-05-05' },
+ { author_name: 'John', author_email: 'jawnnypoo@gmail.com', date: '2019-04-04' },
+ { author_name: 'John', author_email: 'jawnnypoo@gmail.com', date: '2019-04-04' },
+ { author_name: 'John', author_email: 'jawnnypoo@gmail.com', date: '2019-03-03' },
+ ];
+ parsed = getters.parsedData(state);
+ });
+
+ it('should group contributions by date ', () => {
+ expect(parsed.total).toMatchObject({ '2019-05-05': 3, '2019-03-03': 2, '2019-04-04': 2 });
+ });
+
+ it('should group contributions by author ', () => {
+ expect(parsed.byAuthor).toMatchObject({
+ Carlson: {
+ email: 'jawnnypoo@gmail.com',
+ commits: 2,
+ dates: {
+ '2019-03-03': 1,
+ '2019-05-05': 1,
+ },
+ },
+ John: {
+ email: 'jawnnypoo@gmail.com',
+ commits: 5,
+ dates: {
+ '2019-03-03': 1,
+ '2019-04-04': 2,
+ '2019-05-05': 2,
+ },
+ },
+ });
+ });
+ });
+ });
+});
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/spec/frontend/contributors/store/mutations_spec.js b/spec/frontend/contributors/store/mutations_spec.js
new file mode 100644
index 00000000000..e9e756d4a65
--- /dev/null
+++ b/spec/frontend/contributors/store/mutations_spec.js
@@ -0,0 +1,40 @@
+import state from '~/contributors/stores/state';
+import mutations from '~/contributors/stores/mutations';
+import * as types from '~/contributors/stores/mutation_types';
+
+describe('Contributors mutations', () => {
+ let stateCopy;
+
+ beforeEach(() => {
+ stateCopy = state();
+ });
+
+ describe('SET_LOADING_STATE', () => {
+ it('should set loading flag', () => {
+ const loading = true;
+ mutations[types.SET_LOADING_STATE](stateCopy, loading);
+
+ expect(stateCopy.loading).toEqual(loading);
+ });
+ });
+
+ describe('SET_CHART_DATA', () => {
+ const chartData = { '2017-11': 0, '2017-12': 2 };
+
+ it('should set chart data', () => {
+ mutations[types.SET_CHART_DATA](stateCopy, chartData);
+
+ expect(stateCopy.chartData).toEqual(chartData);
+ });
+ });
+
+ describe('SET_ACTIVE_BRANCH', () => {
+ it('should set search query', () => {
+ const branch = 'feature-branch';
+
+ mutations[types.SET_ACTIVE_BRANCH](stateCopy, branch);
+
+ expect(stateCopy.branch).toEqual(branch);
+ });
+ });
+});
diff --git a/spec/frontend/contributors/utils_spec.js b/spec/frontend/contributors/utils_spec.js
new file mode 100644
index 00000000000..a2b9154329b
--- /dev/null
+++ b/spec/frontend/contributors/utils_spec.js
@@ -0,0 +1,21 @@
+import * as utils from '~/contributors/utils';
+
+describe('Contributors Util Functions', () => {
+ describe('xAxisLabelFormatter', () => {
+ it('should return year if the date is in January', () => {
+ expect(utils.xAxisLabelFormatter(new Date('01-12-2019'))).toEqual('2019');
+ });
+
+ it('should return month name otherwise', () => {
+ expect(utils.xAxisLabelFormatter(new Date('12-02-2019'))).toEqual('Dec');
+ expect(utils.xAxisLabelFormatter(new Date('07-12-2019'))).toEqual('Jul');
+ });
+ });
+
+ describe('dateFormatter', () => {
+ it('should format provided date to YYYY-MM-DD format', () => {
+ expect(utils.dateFormatter(new Date('December 17, 1995 03:24:00'))).toEqual('1995-12-17');
+ expect(utils.dateFormatter(new Date(1565308800000))).toEqual('2019-08-09');
+ });
+ });
+});
diff --git a/spec/frontend/lib/utils/datetime_utility_spec.js b/spec/frontend/lib/utils/datetime_utility_spec.js
index e2e71229320..149ce331ae5 100644
--- a/spec/frontend/lib/utils/datetime_utility_spec.js
+++ b/spec/frontend/lib/utils/datetime_utility_spec.js
@@ -441,3 +441,34 @@ describe('getDateInPast', () => {
expect(date).toStrictEqual(new Date(1563235200000));
});
});
+
+describe('getDatesInRange', () => {
+ it('returns an empty array if 1st or 2nd argument is not a Date object', () => {
+ const d1 = new Date('2019-01-01');
+ const d2 = 90;
+ const range = datetimeUtility.getDatesInRange(d1, d2);
+
+ expect(range).toEqual([]);
+ });
+
+ it('returns a range of dates between two given dates', () => {
+ const d1 = new Date('2019-01-01');
+ const d2 = new Date('2019-01-31');
+
+ const range = datetimeUtility.getDatesInRange(d1, d2);
+
+ expect(range.length).toEqual(31);
+ });
+
+ it('applies mapper function if provided fro each item in range', () => {
+ const d1 = new Date('2019-01-01');
+ const d2 = new Date('2019-01-31');
+ const formatter = date => date.getDate();
+
+ const range = datetimeUtility.getDatesInRange(d1, d2, formatter);
+
+ range.forEach((formattedItem, index) => {
+ expect(formattedItem).toEqual(index + 1);
+ });
+ });
+});
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index 2f67ea457a0..1af8b7390bb 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -203,42 +203,53 @@ describe IssuablesHelper do
end
describe '#zoomMeetingUrl in issue' do
- let(:issue) { create(:issue, author: user, description: description) }
+ let(:issue) { create(:issue, author: user) }
before do
assign(:project, issue.project)
end
- context 'no zoom links in the issue description' do
- let(:description) { 'issue text' }
-
- it 'does not set zoomMeetingUrl' do
- expect(helper.issuable_initial_data(issue))
- .not_to include(:zoomMeetingUrl)
+ shared_examples 'sets zoomMeetingUrl to nil' do
+ specify do
+ expect(helper.issuable_initial_data(issue)[:zoomMeetingUrl])
+ .to be_nil
end
end
- context 'no zoom links in the issue description if it has link but not a zoom link' do
- let(:description) { 'issue text https://stackoverflow.com/questions/22' }
+ context 'with no "added" zoom mettings' do
+ it_behaves_like 'sets zoomMeetingUrl to nil'
+
+ context 'with multiple removed meetings' do
+ before do
+ create(:zoom_meeting, issue: issue, issue_status: :removed)
+ create(:zoom_meeting, issue: issue, issue_status: :removed)
+ end
- it 'does not set zoomMeetingUrl' do
- expect(helper.issuable_initial_data(issue))
- .not_to include(:zoomMeetingUrl)
+ it_behaves_like 'sets zoomMeetingUrl to nil'
end
end
- context 'with two zoom links in description' do
- let(:description) do
- <<~TEXT
- issue text and
- zoom call on https://zoom.us/j/123456789 this url
- and new zoom url https://zoom.us/s/lastone and some more text
- TEXT
+ context 'with "added" zoom meeting' do
+ before do
+ create(:zoom_meeting, issue: issue)
end
- it 'sets zoomMeetingUrl value to the last url' do
- expect(helper.issuable_initial_data(issue))
- .to include(zoomMeetingUrl: 'https://zoom.us/s/lastone')
+ shared_examples 'sets zoomMeetingUrl to canonical meeting url' do
+ specify do
+ expect(helper.issuable_initial_data(issue))
+ .to include(zoomMeetingUrl: 'https://zoom.us/j/123456789')
+ end
+ end
+
+ it_behaves_like 'sets zoomMeetingUrl to canonical meeting url'
+
+ context 'with muliple "removed" zoom meetings' do
+ before do
+ create(:zoom_meeting, issue: issue, issue_status: :removed)
+ create(:zoom_meeting, issue: issue, issue_status: :removed)
+ end
+
+ it_behaves_like 'sets zoomMeetingUrl to canonical meeting url'
end
end
end
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index 9e9f87b3407..561cd085ad1 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -271,4 +271,50 @@ describe SearchHelper do
expect(link).to have_css('li[data-foo="bar"]')
end
end
+
+ describe '#show_user_search_tab?' do
+ subject { show_user_search_tab? }
+
+ context 'when users_search feature is disabled' do
+ before do
+ stub_feature_flags(users_search: false)
+ end
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when project search' do
+ before do
+ @project = :some_project
+
+ expect(self).to receive(:project_search_tabs?)
+ .with(:members)
+ .and_return(:value)
+ end
+
+ it 'delegates to project_search_tabs?' do
+ expect(subject).to eq(:value)
+ end
+ end
+
+ context 'when not project search' do
+ context 'when current_user can read_users_list' do
+ before do
+ allow(self).to receive(:current_user).and_return(:the_current_user)
+ allow(self).to receive(:can?).with(:the_current_user, :read_users_list).and_return(true)
+ end
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when current_user cannot read_users_list' do
+ before do
+ allow(self).to receive(:current_user).and_return(:the_current_user)
+ allow(self).to receive(:can?).with(:the_current_user, :read_users_list).and_return(false)
+ end
+
+ it { is_expected.to eq(false) }
+ end
+ end
+ end
end
diff --git a/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js b/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js
deleted file mode 100644
index 563d134ca81..00000000000
--- a/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js
+++ /dev/null
@@ -1,152 +0,0 @@
-/* eslint-disable jasmine/no-suite-dupes, vars-on-top, no-var */
-
-import { scaleLinear, scaleTime } from 'd3-scale';
-import { timeParse } from 'd3-time-format';
-import {
- ContributorsGraph,
- ContributorsMasterGraph,
-} from '~/pages/projects/graphs/show/stat_graph_contributors_graph';
-
-const d3 = { scaleLinear, scaleTime, timeParse };
-
-describe('ContributorsGraph', function() {
- describe('#set_x_domain', function() {
- it('set the x_domain', function() {
- ContributorsGraph.set_x_domain(20);
-
- expect(ContributorsGraph.prototype.x_domain).toEqual(20);
- });
- });
-
- describe('#set_y_domain', function() {
- it('sets the y_domain', function() {
- ContributorsGraph.set_y_domain([{ commits: 30 }]);
-
- expect(ContributorsGraph.prototype.y_domain).toEqual([0, 30]);
- });
- });
-
- describe('#init_x_domain', function() {
- it('sets the initial x_domain', function() {
- ContributorsGraph.init_x_domain([{ date: '2013-01-31' }, { date: '2012-01-31' }]);
-
- expect(ContributorsGraph.prototype.x_domain).toEqual(['2012-01-31', '2013-01-31']);
- });
- });
-
- describe('#init_y_domain', function() {
- it('sets the initial y_domain', function() {
- ContributorsGraph.init_y_domain([{ commits: 30 }]);
-
- expect(ContributorsGraph.prototype.y_domain).toEqual([0, 30]);
- });
- });
-
- describe('#init_domain', function() {
- it('calls init_x_domain and init_y_domain', function() {
- spyOn(ContributorsGraph, 'init_x_domain');
- spyOn(ContributorsGraph, 'init_y_domain');
- ContributorsGraph.init_domain();
-
- expect(ContributorsGraph.init_x_domain).toHaveBeenCalled();
- expect(ContributorsGraph.init_y_domain).toHaveBeenCalled();
- });
- });
-
- describe('#set_dates', function() {
- it('sets the dates', function() {
- ContributorsGraph.set_dates('2013-12-01');
-
- expect(ContributorsGraph.prototype.dates).toEqual('2013-12-01');
- });
- });
-
- describe('#set_x_domain', function() {
- it("sets the instance's x domain using the prototype's x_domain", function() {
- ContributorsGraph.prototype.x_domain = 20;
- var instance = new ContributorsGraph();
- instance.x = d3
- .scaleTime()
- .range([0, 100])
- .clamp(true);
- spyOn(instance.x, 'domain');
- instance.set_x_domain();
-
- expect(instance.x.domain).toHaveBeenCalledWith(20);
- });
- });
-
- describe('#set_y_domain', function() {
- it("sets the instance's y domain using the prototype's y_domain", function() {
- ContributorsGraph.prototype.y_domain = 30;
- var instance = new ContributorsGraph();
- instance.y = d3
- .scaleLinear()
- .range([100, 0])
- .nice();
- spyOn(instance.y, 'domain');
- instance.set_y_domain();
-
- expect(instance.y.domain).toHaveBeenCalledWith(30);
- });
- });
-
- describe('#set_domain', function() {
- it('calls set_x_domain and set_y_domain', function() {
- var instance = new ContributorsGraph();
- spyOn(instance, 'set_x_domain');
- spyOn(instance, 'set_y_domain');
- instance.set_domain();
-
- expect(instance.set_x_domain).toHaveBeenCalled();
- expect(instance.set_y_domain).toHaveBeenCalled();
- });
- });
-
- describe('#set_data', function() {
- it('sets the data', function() {
- var instance = new ContributorsGraph();
- instance.set_data('20');
-
- expect(instance.data).toEqual('20');
- });
- });
-});
-
-describe('ContributorsMasterGraph', function() {
- // TODO: fix or remove
- // describe("#process_dates", function () {
- // it("gets and parses dates", function () {
- // var graph = new ContributorsMasterGraph();
- // var data = 'random data here';
- // spyOn(graph, 'parse_dates');
- // spyOn(graph, 'get_dates').andReturn("get");
- // spyOn(ContributorsGraph,'set_dates').andCallThrough();
- // graph.process_dates(data);
- // expect(graph.parse_dates).toHaveBeenCalledWith(data);
- // expect(graph.get_dates).toHaveBeenCalledWith(data);
- // expect(ContributorsGraph.set_dates).toHaveBeenCalledWith("get");
- // });
- // });
-
- describe('#get_dates', function() {
- it('plucks the date field from data collection', function() {
- var graph = new ContributorsMasterGraph();
- var data = [{ date: '2013-01-01' }, { date: '2012-12-15' }];
-
- expect(graph.get_dates(data)).toEqual(['2013-01-01', '2012-12-15']);
- });
- });
-
- describe('#parse_dates', function() {
- it('parses the dates', function() {
- var graph = new ContributorsMasterGraph();
- var parseDate = d3.timeParse('%Y-%m-%d');
- var data = [{ date: '2013-01-01' }, { date: '2012-12-15' }];
- var correct = [{ date: parseDate(data[0].date) }, { date: parseDate(data[1].date) }];
- graph.parse_dates(data);
-
- expect(data).toEqual(correct);
- });
- });
-});
diff --git a/spec/javascripts/graphs/stat_graph_contributors_spec.js b/spec/javascripts/graphs/stat_graph_contributors_spec.js
deleted file mode 100644
index 2ebb6845a8b..00000000000
--- a/spec/javascripts/graphs/stat_graph_contributors_spec.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import ContributorsStatGraph from '~/pages/projects/graphs/show/stat_graph_contributors';
-import { ContributorsGraph } from '~/pages/projects/graphs/show/stat_graph_contributors_graph';
-
-import { setLanguage } from '../helpers/locale_helper';
-
-describe('ContributorsStatGraph', () => {
- describe('change_date_header', () => {
- beforeAll(() => {
- setLanguage('de');
- });
-
- afterAll(() => {
- setLanguage(null);
- });
-
- it('uses the locale to display date ranges', () => {
- ContributorsGraph.init_x_domain([{ date: '2013-01-31' }, { date: '2012-01-31' }]);
- setFixtures('<div id="date_header"></div>');
- const graph = new ContributorsStatGraph();
-
- graph.change_date_header();
-
- expect(document.getElementById('date_header').innerText).toBe(
- '31. Januar 2012 – 31. Januar 2013',
- );
- });
- });
-});
diff --git a/spec/javascripts/graphs/stat_graph_contributors_util_spec.js b/spec/javascripts/graphs/stat_graph_contributors_util_spec.js
deleted file mode 100644
index 511b660c671..00000000000
--- a/spec/javascripts/graphs/stat_graph_contributors_util_spec.js
+++ /dev/null
@@ -1,298 +0,0 @@
-/* eslint-disable no-var, camelcase, vars-on-top */
-
-import ContributorsStatGraphUtil from '~/pages/projects/graphs/show/stat_graph_contributors_util';
-
-describe('ContributorsStatGraphUtil', function() {
- describe('#parse_log', function() {
- it('returns a correctly parsed log', function() {
- var fake_log = [
- {
- author_email: 'karlo@email.com',
- author_name: 'Karlo Soriano',
- date: '2013-05-09',
- additions: 471,
- },
- {
- author_email: 'dzaporozhets@email.com',
- author_name: 'Dmitriy Zaporozhets',
- date: '2013-05-08',
- additions: 6,
- deletions: 1,
- },
- {
- author_email: 'dzaporozhets@email.com',
- author_name: 'Dmitriy Zaporozhets',
- date: '2013-05-08',
- additions: 19,
- deletions: 3,
- },
- {
- author_email: 'dzaporozhets@email.com',
- author_name: 'Dmitriy Zaporozhets',
- date: '2013-05-08',
- additions: 29,
- deletions: 3,
- },
- ];
-
- var correct_parsed_log = {
- total: [
- { date: '2013-05-09', additions: 471, deletions: 0, commits: 1 },
- { date: '2013-05-08', additions: 54, deletions: 7, commits: 3 },
- ],
- by_author: [
- {
- author_name: 'Karlo Soriano',
- author_email: 'karlo@email.com',
- '2013-05-09': { date: '2013-05-09', additions: 471, deletions: 0, commits: 1 },
- },
- {
- author_name: 'Dmitriy Zaporozhets',
- author_email: 'dzaporozhets@email.com',
- '2013-05-08': { date: '2013-05-08', additions: 54, deletions: 7, commits: 3 },
- },
- ],
- };
-
- expect(ContributorsStatGraphUtil.parse_log(fake_log)).toEqual(correct_parsed_log);
- });
- });
-
- describe('#store_data', function() {
- var fake_entry = { author: 'Karlo Soriano', date: '2013-05-09', additions: 471 };
- var fake_total = {};
- var fake_by_author = {};
-
- it('calls #store_commits', function() {
- spyOn(ContributorsStatGraphUtil, 'store_commits');
- ContributorsStatGraphUtil.store_data(fake_entry, fake_total, fake_by_author);
-
- expect(ContributorsStatGraphUtil.store_commits).toHaveBeenCalled();
- });
-
- it('calls #store_additions', function() {
- spyOn(ContributorsStatGraphUtil, 'store_additions');
- ContributorsStatGraphUtil.store_data(fake_entry, fake_total, fake_by_author);
-
- expect(ContributorsStatGraphUtil.store_additions).toHaveBeenCalled();
- });
-
- it('calls #store_deletions', function() {
- spyOn(ContributorsStatGraphUtil, 'store_deletions');
- ContributorsStatGraphUtil.store_data(fake_entry, fake_total, fake_by_author);
-
- expect(ContributorsStatGraphUtil.store_deletions).toHaveBeenCalled();
- });
- });
-
- // TODO: fix or remove
- // describe("#store_commits", function () {
- // var fake_total = "fake_total";
- // var fake_by_author = "fake_by_author";
- //
- // it("calls #add twice with arguments fake_total and fake_by_author respectively", function () {
- // spyOn(ContributorsStatGraphUtil, 'add');
- // ContributorsStatGraphUtil.store_commits(fake_total, fake_by_author);
- // expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "commits", 1], ["fake_by_author", "commits", 1]]);
- // });
- // });
-
- describe('#add', function() {
- it('adds 1 to current test_field in collection', function() {
- var fake_collection = { test_field: 10 };
- ContributorsStatGraphUtil.add(fake_collection, 'test_field', 1);
-
- expect(fake_collection.test_field).toEqual(11);
- });
-
- it('inits and adds 1 if test_field in collection is not defined', function() {
- var fake_collection = {};
- ContributorsStatGraphUtil.add(fake_collection, 'test_field', 1);
-
- expect(fake_collection.test_field).toEqual(1);
- });
- });
-
- // TODO: fix or remove
- // describe("#store_additions", function () {
- // var fake_entry = {additions: 10};
- // var fake_total= "fake_total";
- // var fake_by_author = "fake_by_author";
- // it("calls #add twice with arguments fake_total and fake_by_author respectively", function () {
- // spyOn(ContributorsStatGraphUtil, 'add');
- // ContributorsStatGraphUtil.store_additions(fake_entry, fake_total, fake_by_author);
- // expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "additions", 10], ["fake_by_author", "additions", 10]]);
- // });
- // });
-
- // TODO: fix or remove
- // describe("#store_deletions", function () {
- // var fake_entry = {deletions: 10};
- // var fake_total= "fake_total";
- // var fake_by_author = "fake_by_author";
- // it("calls #add twice with arguments fake_total and fake_by_author respectively", function () {
- // spyOn(ContributorsStatGraphUtil, 'add');
- // ContributorsStatGraphUtil.store_deletions(fake_entry, fake_total, fake_by_author);
- // expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "deletions", 10], ["fake_by_author", "deletions", 10]]);
- // });
- // });
-
- describe('#add_date', function() {
- it('adds a date field to the collection', function() {
- var fake_date = '2013-10-02';
- var fake_collection = {};
- ContributorsStatGraphUtil.add_date(fake_date, fake_collection);
-
- expect(fake_collection[fake_date].date).toEqual('2013-10-02');
- });
- });
-
- describe('#add_author', function() {
- it('adds an author field to the collection', function() {
- var fake_author = { author_name: 'Author', author_email: 'fake@email.com' };
- var fake_author_collection = {};
- var fake_email_collection = {};
- ContributorsStatGraphUtil.add_author(
- fake_author,
- fake_author_collection,
- fake_email_collection,
- );
-
- expect(fake_author_collection[fake_author.author_name].author_name).toEqual('Author');
- expect(fake_email_collection[fake_author.author_email].author_name).toEqual('Author');
- });
- });
-
- describe('#get_total_data', function() {
- it('returns the collection sorted via specified field', function() {
- var fake_parsed_log = {
- total: [
- { date: '2013-05-09', additions: 471, deletions: 0, commits: 1 },
- { date: '2013-05-08', additions: 54, deletions: 7, commits: 3 },
- ],
- by_author: [
- {
- author: 'Karlo Soriano',
- '2013-05-09': { date: '2013-05-09', additions: 471, deletions: 0, commits: 1 },
- },
- {
- author: 'Dmitriy Zaporozhets',
- '2013-05-08': { date: '2013-05-08', additions: 54, deletions: 7, commits: 3 },
- },
- ],
- };
- var correct_total_data = [
- { date: '2013-05-08', commits: 3 },
- { date: '2013-05-09', commits: 1 },
- ];
-
- expect(ContributorsStatGraphUtil.get_total_data(fake_parsed_log, 'commits')).toEqual(
- correct_total_data,
- );
- });
- });
-
- describe('#pick_field', function() {
- it('returns the collection with only the specified field and date', function() {
- var fake_parsed_log_total = [
- { date: '2013-05-09', additions: 471, deletions: 0, commits: 1 },
- { date: '2013-05-08', additions: 54, deletions: 7, commits: 3 },
- ];
- ContributorsStatGraphUtil.pick_field(fake_parsed_log_total, 'commits');
- var correct_pick_field_data = [
- { date: '2013-05-09', commits: 1 },
- { date: '2013-05-08', commits: 3 },
- ];
-
- expect(ContributorsStatGraphUtil.pick_field(fake_parsed_log_total, 'commits')).toEqual(
- correct_pick_field_data,
- );
- });
- });
-
- describe('#get_author_data', function() {
- it('returns the log by author sorted by specified field', function() {
- var fake_parsed_log = {
- total: [
- { date: '2013-05-09', additions: 471, deletions: 0, commits: 1 },
- { date: '2013-05-08', additions: 54, deletions: 7, commits: 3 },
- ],
- by_author: [
- {
- author_name: 'Karlo Soriano',
- author_email: 'karlo@email.com',
- '2013-05-09': { date: '2013-05-09', additions: 471, deletions: 0, commits: 1 },
- },
- {
- author_name: 'Dmitriy Zaporozhets',
- author_email: 'dzaporozhets@email.com',
- '2013-05-08': { date: '2013-05-08', additions: 54, deletions: 7, commits: 3 },
- },
- ],
- };
- var correct_author_data = [
- {
- author_name: 'Dmitriy Zaporozhets',
- author_email: 'dzaporozhets@email.com',
- dates: { '2013-05-08': 3 },
- deletions: 7,
- additions: 54,
- commits: 3,
- },
- {
- author_name: 'Karlo Soriano',
- author_email: 'karlo@email.com',
- dates: { '2013-05-09': 1 },
- deletions: 0,
- additions: 471,
- commits: 1,
- },
- ];
-
- expect(ContributorsStatGraphUtil.get_author_data(fake_parsed_log, 'commits')).toEqual(
- correct_author_data,
- );
- });
- });
-
- describe('#parse_log_entry', function() {
- it('adds the corresponding info from the log entry to the author', function() {
- var fake_log_entry = {
- author_name: 'Karlo Soriano',
- author_email: 'karlo@email.com',
- '2013-05-09': { date: '2013-05-09', additions: 471, deletions: 0, commits: 1 },
- };
- var correct_parsed_log = {
- author_name: 'Karlo Soriano',
- author_email: 'karlo@email.com',
- dates: { '2013-05-09': 1 },
- deletions: 0,
- additions: 471,
- commits: 1,
- };
-
- expect(ContributorsStatGraphUtil.parse_log_entry(fake_log_entry, 'commits', null)).toEqual(
- correct_parsed_log,
- );
- });
- });
-
- describe('#in_range', function() {
- var date = '2013-05-09';
- it('returns true if date_range is null', function() {
- expect(ContributorsStatGraphUtil.in_range(date, null)).toEqual(true);
- });
-
- it('returns true if date is in range', function() {
- var date_range = [new Date('2013-01-01'), new Date('2013-12-12')];
-
- expect(ContributorsStatGraphUtil.in_range(date, date_range)).toEqual(true);
- });
-
- it('returns false if date is not in range', function() {
- var date_range = [new Date('1999-12-01'), new Date('2000-12-01')];
-
- expect(ContributorsStatGraphUtil.in_range(date, date_range)).toEqual(false);
- });
- });
-});
diff --git a/spec/lib/gitlab/ci/config/entry/default_spec.rb b/spec/lib/gitlab/ci/config/entry/default_spec.rb
index 27d63dbd407..f09df698f68 100644
--- a/spec/lib/gitlab/ci/config/entry/default_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/default_spec.rb
@@ -5,6 +5,18 @@ require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Default do
let(:entry) { described_class.new(config) }
+ it_behaves_like 'with inheritable CI config' do
+ let(:inheritable_key) { nil }
+ let(:inheritable_class) { Gitlab::Ci::Config::Entry::Root }
+
+ # These are entries defined in Root
+ # that we know that we don't want to inherit
+ # as they do not have sense in context of Default
+ let(:ignored_inheritable_columns) do
+ %i[default include variables stages types]
+ end
+ end
+
describe '.nodes' do
it 'returns a hash' do
expect(described_class.nodes).to be_a(Hash)
@@ -87,7 +99,7 @@ describe Gitlab::Ci::Config::Entry::Default do
it 'raises error' do
expect { entry.compose!(deps) }.to raise_error(
- Gitlab::Ci::Config::Entry::Default::DuplicateError)
+ Gitlab::Ci::Config::Entry::Default::InheritError)
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index 1c4887e87c4..d3eb5a9663f 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -5,6 +5,18 @@ require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Job do
let(:entry) { described_class.new(config, name: :rspec) }
+ it_behaves_like 'with inheritable CI config' do
+ let(:inheritable_key) { 'default' }
+ let(:inheritable_class) { Gitlab::Ci::Config::Entry::Default }
+
+ # These are entries defined in Default
+ # that we know that we don't want to inherit
+ # as they do not have sense in context of Job
+ let(:ignored_inheritable_columns) do
+ %i[]
+ end
+ end
+
describe '.nodes' do
context 'when filtering all the entry/node names' do
subject { described_class.nodes.keys }
diff --git a/spec/lib/gitlab/ci/config/entry/root_spec.rb b/spec/lib/gitlab/ci/config/entry/root_spec.rb
index 7e1a80414d4..8243fcfd162 100644
--- a/spec/lib/gitlab/ci/config/entry/root_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb
@@ -12,6 +12,11 @@ describe Gitlab::Ci::Config::Entry::Root do
context 'when filtering all the entry/node names' do
it 'contains the expected node names' do
+ # No inheritable fields should be added to the `Root`
+ #
+ # Inheritable configuration can only be added to `default:`
+ #
+ # The purpose of `Root` is have only globally defined configuration.
expect(described_class.nodes.keys)
.to match_array(%i[before_script image services
after_script variables cache
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 2e3bc4606b9..3b665d95004 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -29,6 +29,7 @@ issues:
- prometheus_alerts
- prometheus_alert_events
- self_managed_prometheus_alert_events
+- zoom_meetings
events:
- author
- project
@@ -529,4 +530,6 @@ versions: &version
- issue
- designs
- actions
+zoom_meetings:
+- issue
design_versions: *version
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index ebd2c6089ce..88a7fbea1d1 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -211,6 +211,13 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
expect(CustomIssueTrackerService.first).not_to be_nil
end
+ it 'restores zoom meetings' do
+ meetings = @project.issues.first.zoom_meetings
+
+ expect(meetings.count).to eq(1)
+ expect(meetings.first.url).to eq('https://zoom.us/j/123456789')
+ end
+
context 'Merge requests' do
it 'always has the new project as a target' do
expect(MergeRequest.find_by_title('MR1').target_project).to eq(@project)
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index ede8eb4b2bd..3835b23123f 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -753,3 +753,11 @@ DesignManagement::Version:
- sha
- issue_id
- author_id
+ZoomMeeting:
+- id
+- issue_id
+- project_id
+- issue_status
+- url
+- created_at
+- updated_at
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 1eec3b97ea1..21e308e6636 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -187,7 +187,6 @@ describe Issues::UpdateService, :mailer do
it 'creates system note about issue reassign' do
note = find_note('assigned to')
- expect(note).not_to be_nil
expect(note.note).to include "assigned to #{user2.to_reference}"
end
@@ -202,14 +201,12 @@ describe Issues::UpdateService, :mailer do
it 'creates system note about title change' do
note = find_note('changed title')
- expect(note).not_to be_nil
expect(note.note).to eq 'changed title from **{-Old-} title** to **{+New+} title**'
end
it 'creates system note about discussion lock' do
note = find_note('locked this issue')
- expect(note).not_to be_nil
expect(note.note).to eq 'locked this issue'
end
end
@@ -221,20 +218,10 @@ describe Issues::UpdateService, :mailer do
note = find_note('changed the description')
- expect(note).not_to be_nil
expect(note.note).to eq('changed the description')
end
end
- it 'creates zoom_link_added system note when a zoom link is added to the description' do
- update_issue(description: 'Changed description https://zoom.us/j/5873603787')
-
- note = find_note('added a Zoom call')
-
- expect(note).not_to be_nil
- expect(note.note).to eq('added a Zoom call to this issue')
- end
-
context 'when issue turns confidential' do
let(:opts) do
{
@@ -252,7 +239,6 @@ describe Issues::UpdateService, :mailer do
note = find_note('made the issue confidential')
- expect(note).not_to be_nil
expect(note.note).to eq 'made the issue confidential'
end
diff --git a/spec/services/issues/zoom_link_service_spec.rb b/spec/services/issues/zoom_link_service_spec.rb
index ba3f007c917..ecca9467965 100644
--- a/spec/services/issues/zoom_link_service_spec.rb
+++ b/spec/services/issues/zoom_link_service_spec.rb
@@ -14,27 +14,16 @@ describe Issues::ZoomLinkService do
project.add_reporter(user)
end
- shared_context 'with Zoom link' do
+ shared_context '"added" Zoom meeting' do
before do
- issue.update!(description: "Description\n\n#{zoom_link}")
+ create(:zoom_meeting, issue: issue)
end
end
- shared_context 'with Zoom link not at the end' do
+ shared_context '"removed" zoom meetings' do
before do
- issue.update!(description: "Description with #{zoom_link} some where")
- end
- end
-
- shared_context 'without Zoom link' do
- before do
- issue.update!(description: "Description\n\nhttp://example.com")
- end
- end
-
- shared_context 'without issue description' do
- before do
- issue.update!(description: nil)
+ create(:zoom_meeting, issue: issue, issue_status: :removed)
+ create(:zoom_meeting, issue: issue, issue_status: :removed)
end
end
@@ -45,11 +34,10 @@ describe Issues::ZoomLinkService do
end
describe '#add_link' do
- shared_examples 'can add link' do
- it 'appends the link to issue description' do
+ shared_examples 'can add meeting' do
+ it 'appends the new meeting to zoom_meetings' do
expect(result).to be_success
- expect(result.payload[:description])
- .to eq("#{issue.description}\n\n#{zoom_link}")
+ expect(ZoomMeeting.canonical_meeting_url(issue)).to eq(zoom_link)
end
it 'tracks the add event' do
@@ -57,55 +45,63 @@ describe Issues::ZoomLinkService do
.with('IncidentManagement::ZoomIntegration', 'add_zoom_meeting', label: 'Issue ID', value: issue.id)
result
end
+
+ it 'creates a zoom_link_added notification' do
+ expect(SystemNoteService).to receive(:zoom_link_added).with(issue, project, user)
+ expect(SystemNoteService).not_to receive(:zoom_link_removed)
+ result
+ end
end
- shared_examples 'cannot add link' do
- it 'cannot add the link' do
+ shared_examples 'cannot add meeting' do
+ it 'cannot add the meeting' do
expect(result).to be_error
expect(result.message).to eq('Failed to add a Zoom meeting')
end
+
+ it 'creates no notification' do
+ expect(SystemNoteService).not_to receive(:zoom_link_added)
+ expect(SystemNoteService).not_to receive(:zoom_link_removed)
+ result
+ end
end
subject(:result) { service.add_link(zoom_link) }
- context 'without Zoom link in the issue description' do
- include_context 'without Zoom link'
- include_examples 'can add link'
+ context 'without existing Zoom meeting' do
+ include_examples 'can add meeting'
- context 'with invalid Zoom link' do
+ context 'with invalid Zoom url' do
let(:zoom_link) { 'https://not-zoom.link' }
- include_examples 'cannot add link'
+ include_examples 'cannot add meeting'
end
context 'with insufficient permissions' do
include_context 'insufficient permissions'
- include_examples 'cannot add link'
+ include_examples 'cannot add meeting'
end
end
- context 'with Zoom link in the issue description' do
- include_context 'with Zoom link'
- include_examples 'cannot add link'
+ context 'with "added" Zoom meeting' do
+ include_context '"added" Zoom meeting'
+ include_examples 'cannot add meeting'
+ end
- context 'but not at the end' do
- include_context 'with Zoom link not at the end'
- include_examples 'can add link'
+ context 'with "added" Zoom meeting and race condition' do
+ include_context '"added" Zoom meeting'
+ before do
+ allow(service).to receive(:can_add_link?).and_return(true)
end
- end
- context 'without issue description' do
- include_context 'without issue description'
- include_examples 'can add link'
+ include_examples 'cannot add meeting'
end
end
describe '#can_add_link?' do
subject { service.can_add_link? }
- context 'without Zoom link in the issue description' do
- include_context 'without Zoom link'
-
+ context 'without "added" zoom meeting' do
it { is_expected.to eq(true) }
context 'with insufficient permissions' do
@@ -115,81 +111,93 @@ describe Issues::ZoomLinkService do
end
end
- context 'with Zoom link in the issue description' do
- include_context 'with Zoom link'
+ context 'with Zoom meeting in the issue description' do
+ include_context '"added" Zoom meeting'
it { is_expected.to eq(false) }
end
end
describe '#remove_link' do
- shared_examples 'cannot remove link' do
- it 'cannot remove the link' do
+ shared_examples 'cannot remove meeting' do
+ it 'cannot remove the meeting' do
expect(result).to be_error
expect(result.message).to eq('Failed to remove a Zoom meeting')
end
- end
- subject(:result) { service.remove_link }
+ it 'creates no notification' do
+ expect(SystemNoteService).not_to receive(:zoom_link_added)
+ expect(SystemNoteService).not_to receive(:zoom_link_removed)
+ result
+ end
+ end
- context 'with Zoom link in the issue description' do
- include_context 'with Zoom link'
+ shared_examples 'can remove meeting' do
+ it 'creates no notification' do
+ expect(SystemNoteService).not_to receive(:zoom_link_added).with(issue, project, user)
+ expect(SystemNoteService).to receive(:zoom_link_removed)
+ result
+ end
- it 'removes the link from the issue description' do
+ it 'can remove the meeting' do
expect(result).to be_success
- expect(result.payload[:description])
- .to eq(issue.description.delete_suffix("\n\n#{zoom_link}"))
+ expect(ZoomMeeting.canonical_meeting_url(issue)).to eq(nil)
end
it 'tracks the remove event' do
expect(Gitlab::Tracking).to receive(:event)
- .with('IncidentManagement::ZoomIntegration', 'remove_zoom_meeting', label: 'Issue ID', value: issue.id)
-
+ .with('IncidentManagement::ZoomIntegration', 'remove_zoom_meeting', label: 'Issue ID', value: issue.id)
result
end
+ end
- context 'with insufficient permissions' do
- include_context 'insufficient permissions'
- include_examples 'cannot remove link'
- end
+ subject(:result) { service.remove_link }
- context 'but not at the end' do
- include_context 'with Zoom link not at the end'
- include_examples 'cannot remove link'
+ context 'with Zoom meeting' do
+ include_context '"added" Zoom meeting'
+
+ context 'removes the link' do
+ include_examples 'can remove meeting'
end
- end
- context 'without Zoom link in the issue description' do
- include_context 'without Zoom link'
- include_examples 'cannot remove link'
+ context 'with insufficient permissions' do
+ include_context 'insufficient permissions'
+ include_examples 'cannot remove meeting'
+ end
end
- context 'without issue description' do
- include_context 'without issue description'
- include_examples 'cannot remove link'
+ context 'without "added" Zoom meeting' do
+ include_context '"removed" zoom meetings'
+ include_examples 'cannot remove meeting'
end
end
describe '#can_remove_link?' do
subject { service.can_remove_link? }
- context 'with Zoom link in the issue description' do
- include_context 'with Zoom link'
+ context 'without Zoom meeting' do
+ it { is_expected.to eq(false) }
+ end
+
+ context 'with only "removed" zoom meetings' do
+ include_context '"removed" zoom meetings'
+ it { is_expected.to eq(false) }
+ end
+ context 'with "added" Zoom meeting' do
+ include_context '"added" Zoom meeting'
it { is_expected.to eq(true) }
+ context 'with "removed" zoom meetings' do
+ include_context '"removed" zoom meetings'
+ it { is_expected.to eq(true) }
+ end
+
context 'with insufficient permissions' do
include_context 'insufficient permissions'
-
it { is_expected.to eq(false) }
end
end
-
- context 'without Zoom link in the issue description' do
- include_context 'without Zoom link'
-
- it { is_expected.to eq(false) }
- end
end
describe '#parse_link' do
diff --git a/spec/services/zoom_notes_service_spec.rb b/spec/services/zoom_notes_service_spec.rb
deleted file mode 100644
index 419ecf3f374..00000000000
--- a/spec/services/zoom_notes_service_spec.rb
+++ /dev/null
@@ -1,81 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe ZoomNotesService do
- describe '#execute' do
- let(:issue) { OpenStruct.new(description: description) }
- let(:project) { Object.new }
- let(:user) { Object.new }
- let(:description) { 'an issue description' }
- let(:old_description) { nil }
-
- subject { described_class.new(issue, project, user, old_description: old_description) }
-
- shared_examples 'no notifications' do
- it "doesn't create notifications" do
- expect(SystemNoteService).not_to receive(:zoom_link_added)
- expect(SystemNoteService).not_to receive(:zoom_link_removed)
-
- subject.execute
- end
- end
-
- it_behaves_like 'no notifications'
-
- context 'when the zoom link exists in both description and old_description' do
- let(:description) { 'a changed issue description https://zoom.us/j/123' }
- let(:old_description) { 'an issue description https://zoom.us/j/123' }
-
- it_behaves_like 'no notifications'
- end
-
- context "when the zoom link doesn't exist in both description and old_description" do
- let(:description) { 'a changed issue description' }
- let(:old_description) { 'an issue description' }
-
- it_behaves_like 'no notifications'
- end
-
- context 'when description == old_description' do
- let(:old_description) { 'an issue description' }
-
- it_behaves_like 'no notifications'
- end
-
- context 'when the description contains a zoom link and old_description is nil' do
- let(:description) { 'a changed issue description https://zoom.us/j/123' }
-
- it 'creates a zoom_link_added notification' do
- expect(SystemNoteService).to receive(:zoom_link_added).with(issue, project, user)
- expect(SystemNoteService).not_to receive(:zoom_link_removed)
-
- subject.execute
- end
- end
-
- context 'when the zoom link has been added to the description' do
- let(:description) { 'a changed issue description https://zoom.us/j/123' }
- let(:old_description) { 'an issue description' }
-
- it 'creates a zoom_link_added notification' do
- expect(SystemNoteService).to receive(:zoom_link_added).with(issue, project, user)
- expect(SystemNoteService).not_to receive(:zoom_link_removed)
-
- subject.execute
- end
- end
-
- context 'when the zoom link has been removed from the description' do
- let(:description) { 'a changed issue description' }
- let(:old_description) { 'an issue description https://zoom.us/j/123' }
-
- it 'creates a zoom_link_removed notification' do
- expect(SystemNoteService).not_to receive(:zoom_link_added).with(issue, project, user)
- expect(SystemNoteService).to receive(:zoom_link_removed)
-
- subject.execute
- end
- end
- end
-end
diff --git a/spec/support/shared_examples/lib/gitlab/config/inheritable_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/config/inheritable_shared_examples.rb
new file mode 100644
index 00000000000..556d81133bc
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/config/inheritable_shared_examples.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'with inheritable CI config' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:ignored_inheritable_columns) { [] }
+
+ it 'does prepend an Inheritable mixin' do
+ expect(described_class).to include_module(Gitlab::Config::Entry::Inheritable)
+ end
+
+ it 'all inheritable entries are covered' do
+ inheritable_entries = inheritable_class.nodes.keys
+ entries = described_class.nodes.keys
+
+ expect(entries + ignored_inheritable_columns).to include(
+ *inheritable_entries)
+ end
+
+ it 'all entries do have inherit flag' do
+ without_inherit_flag = described_class.nodes.map do |key, factory|
+ key if factory.inherit.nil?
+ end.compact
+
+ expect(without_inherit_flag).to be_empty
+ end
+
+ context 'for non-inheritable entries' do
+ where(:entry_key) do
+ described_class.nodes.map do |key, factory|
+ [key] unless factory.inherit
+ end.compact
+ end
+
+ with_them do
+ it 'inheritable_class does not define entry' do
+ expect(inheritable_class.nodes).not_to include(entry_key)
+ end
+ end
+ end
+
+ context 'for inheritable entries' do
+ where(:entry_key, :entry_class) do
+ described_class.nodes.map do |key, factory|
+ [key, factory.entry_class] if factory.inherit
+ end.compact
+ end
+
+ with_them do
+ let(:specified) { double('deps_specified', 'specified?' => true, value: 'specified') }
+ let(:unspecified) { double('unspecified', 'specified?' => false) }
+ let(:inheritable) { double(inheritable_key, '[]' => unspecified) }
+
+ let(:deps) do
+ if inheritable_key
+ double('deps', inheritable_key => inheritable, '[]' => unspecified)
+ else
+ inheritable
+ end
+ end
+
+ it 'inheritable_class does define entry' do
+ expect(inheritable_class.nodes).to include(entry_key)
+ expect(inheritable_class.nodes[entry_key].entry_class).to eq(entry_class)
+ end
+
+ context 'when is specified' do
+ it 'does inherit value' do
+ expect(inheritable).to receive('[]').with(entry_key).and_return(specified)
+
+ entry.compose!(deps)
+
+ expect(entry[entry_key]).to eq(specified)
+ end
+
+ context 'when entry is specified' do
+ let(:entry_specified) do
+ double('entry_specified', 'specified?' => true, value: 'specified', errors: [])
+ end
+
+ it 'does not inherit value' do
+ entry.send(:entries)[entry_key] = entry_specified
+
+ allow(inheritable).to receive('[]').with(entry_key).and_return(specified)
+
+ expect do
+ # we ignore exceptions as `#overwrite_entry`
+ # can raise exception on duplicates
+ entry.compose!(deps) rescue described_class::InheritError
+ end.not_to change { entry[entry_key] }
+ end
+ end
+ end
+
+ context 'when inheritable does not specify' do
+ it 'does not inherit value' do
+ entry.compose!(deps)
+
+ expect(entry[entry_key]).to be_a(
+ Gitlab::Config::Entry::Undefined)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issue/zoom_quick_actions_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/zoom_quick_actions_shared_examples.rb
index b4a8e3fca4d..92bbc4abe77 100644
--- a/spec/support/shared_examples/quick_actions/issue/zoom_quick_actions_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/issue/zoom_quick_actions_shared_examples.rb
@@ -2,22 +2,19 @@
shared_examples 'zoom quick actions' do
let(:zoom_link) { 'https://zoom.us/j/123456789' }
+ let(:existing_zoom_link) { 'https://zoom.us/j/123456780' }
let(:invalid_zoom_link) { 'https://invalid-zoom' }
- before do
- issue.update!(description: description)
- end
-
describe '/zoom' do
shared_examples 'skip silently' do
- it 'skip addition silently' do
+ it 'skips addition silently' do
add_note("/zoom #{zoom_link}")
wait_for_requests
expect(page).not_to have_content('Zoom meeting added')
expect(page).not_to have_content('Failed to add a Zoom meeting')
- expect(issue.reload.description).to eq(description)
+ expect(ZoomMeeting.canonical_meeting_url(issue.reload)).not_to eq(zoom_link)
end
end
@@ -28,13 +25,11 @@ shared_examples 'zoom quick actions' do
wait_for_requests
expect(page).to have_content('Zoom meeting added')
- expect(issue.reload.description).to end_with(zoom_link)
+ expect(ZoomMeeting.canonical_meeting_url(issue.reload)).to eq(zoom_link)
end
end
- context 'without issue description' do
- let(:description) { nil }
-
+ context 'without zoom_meetings' do
include_examples 'success'
it 'cannot add invalid zoom link' do
@@ -47,14 +42,18 @@ shared_examples 'zoom quick actions' do
end
end
- context 'with Zoom link not at the end of the issue description' do
- let(:description) { "A link #{zoom_link} not at the end" }
+ context 'with "removed" zoom meeting' do
+ before do
+ create(:zoom_meeting, issue_status: :removed, url: existing_zoom_link, issue: issue)
+ end
include_examples 'success'
end
- context 'with Zoom link at end of the issue description' do
- let(:description) { "Text\n#{zoom_link}" }
+ context 'with "added" zoom meeting' do
+ before do
+ create(:zoom_meeting, issue_status: :added, url: existing_zoom_link, issue: issue)
+ end
include_examples 'skip silently'
end
@@ -62,19 +61,19 @@ shared_examples 'zoom quick actions' do
describe '/remove_zoom' do
shared_examples 'skip silently' do
- it 'skip removal silently' do
+ it 'skips removal silently' do
add_note('/remove_zoom')
wait_for_requests
expect(page).not_to have_content('Zoom meeting removed')
expect(page).not_to have_content('Failed to remove a Zoom meeting')
- expect(issue.reload.description).to eq(description)
+ expect(ZoomMeeting.canonical_meeting_url(issue.reload)).to be_nil
end
end
- context 'with Zoom link in the description' do
- let(:description) { "Text with #{zoom_link}\n\n\n#{zoom_link}" }
+ context 'with added zoom meeting' do
+ let!(:added_zoom_meeting) { create(:zoom_meeting, url: zoom_link, issue: issue, issue_status: :added) }
it 'removes last Zoom link' do
add_note('/remove_zoom')
@@ -82,14 +81,8 @@ shared_examples 'zoom quick actions' do
wait_for_requests
expect(page).to have_content('Zoom meeting removed')
- expect(issue.reload.description).to eq("Text with #{zoom_link}")
+ expect(ZoomMeeting.canonical_meeting_url(issue.reload)).to be_nil
end
end
-
- context 'with a Zoom link not at the end of the description' do
- let(:description) { "A link #{zoom_link} not at the end" }
-
- include_examples 'skip silently'
- end
end
end