diff options
Diffstat (limited to 'spec')
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 |