summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2019-11-28 15:06:57 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2019-11-28 15:06:57 +0000
commit7cdd70dcec27402e89e65451b4b1feb75b5eb267 (patch)
tree1691c8e1afd469fa426ecf5bc127de8df16d4855 /spec
parent79348faced5e7e62103ad27f6a6594dfdca463e2 (diff)
downloadgitlab-ce-7cdd70dcec27402e89e65451b4b1feb75b5eb267.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/db/schema_spec.rb1
-rw-r--r--spec/finders/snippets_finder_spec.rb87
-rw-r--r--spec/fixtures/lib/gitlab/import_export/with_invalid_records/project.json38
-rw-r--r--spec/frontend/monitoring/charts/time_series_spec.js4
-rw-r--r--spec/frontend/monitoring/components/charts/anomaly_spec.js18
-rw-r--r--spec/frontend/monitoring/embed/embed_spec.js2
-rw-r--r--spec/frontend/monitoring/embed/mock_data.js26
-rw-r--r--spec/frontend/monitoring/mock_data.js3
-rw-r--r--spec/frontend/monitoring/panel_type_spec.js4
-rw-r--r--spec/frontend/monitoring/store/mutations_spec.js17
-rw-r--r--spec/frontend/monitoring/store/utils_spec.js44
-rw-r--r--spec/frontend/vue_shared/components/clipboard_button_spec.js60
-rw-r--r--spec/frontend/vue_shared/components/user_avatar/user_avatar_link_spec.js113
-rw-r--r--spec/helpers/award_emoji_helper_spec.rb22
-rw-r--r--spec/helpers/gitlab_routing_helper_spec.rb94
-rw-r--r--spec/helpers/snippets_helper_spec.rb101
-rw-r--r--spec/javascripts/boards/board_list_common_spec.js3
-rw-r--r--spec/javascripts/boards/board_new_issue_spec.js3
-rw-r--r--spec/javascripts/monitoring/charts/column_spec.js2
-rw-r--r--spec/javascripts/monitoring/mock_data.js30
-rw-r--r--spec/javascripts/monitoring/utils_spec.js26
-rw-r--r--spec/javascripts/vue_shared/components/clipboard_button_spec.js51
-rw-r--r--spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js109
-rw-r--r--spec/lib/gitlab/etag_caching/router_spec.rb9
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml2
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb48
-rw-r--r--spec/lib/gitlab/url_builder_spec.rb20
-rw-r--r--spec/lib/gitlab/visibility_level_spec.rb24
-rw-r--r--spec/models/merge_request_spec.rb15
-rw-r--r--spec/models/note_spec.rb62
-rw-r--r--spec/services/metrics/dashboard/grafana_metric_embed_service_spec.rb61
-rw-r--r--spec/services/projects/destroy_service_spec.rb5
-rw-r--r--spec/services/projects/unlink_fork_service_spec.rb169
-rw-r--r--spec/services/projects/update_service_spec.rb105
-rw-r--r--spec/support/shared_examples/lib/gitlab/import_export/project_tree_restorer_shared_examples.rb6
35 files changed, 941 insertions, 443 deletions
diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb
index c8a697cfed4..d4fab48b426 100644
--- a/spec/db/schema_spec.rb
+++ b/spec/db/schema_spec.rb
@@ -43,6 +43,7 @@ describe 'Database schema' do
geo_nodes: %w[oauth_application_id],
geo_repository_deleted_events: %w[project_id],
geo_upload_deleted_events: %w[upload_id model_id],
+ import_failures: %w[project_id],
identities: %w[user_id],
issues: %w[last_edited_by_id state_id],
jira_tracker_data: %w[jira_issue_transition_id],
diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb
index bcb762664f7..8f83cb77709 100644
--- a/spec/finders/snippets_finder_spec.rb
+++ b/spec/finders/snippets_finder_spec.rb
@@ -60,10 +60,20 @@ describe SnippetsFinder do
end
context 'filter by author' do
- it 'returns all public and internal snippets' do
- snippets = described_class.new(create(:user), author: user).execute
+ context 'when the author is a User object' do
+ it 'returns all public and internal snippets' do
+ snippets = described_class.new(create(:user), author: user).execute
- expect(snippets).to contain_exactly(internal_personal_snippet, public_personal_snippet)
+ expect(snippets).to contain_exactly(internal_personal_snippet, public_personal_snippet)
+ end
+ end
+
+ context 'when the author is the User id' do
+ it 'returns all public and internal snippets' do
+ snippets = described_class.new(create(:user), author: user.id).execute
+
+ expect(snippets).to contain_exactly(internal_personal_snippet, public_personal_snippet)
+ end
end
it 'returns internal snippets' do
@@ -101,13 +111,33 @@ describe SnippetsFinder do
expect(snippets).to contain_exactly(private_personal_snippet, internal_personal_snippet, public_personal_snippet)
end
+
+ context 'when author is not valid' do
+ it 'returns quickly' do
+ finder = described_class.new(admin, author: 1234)
+
+ expect(finder).not_to receive(:init_collection)
+ expect(Snippet).to receive(:none).and_call_original
+ expect(finder.execute).to be_empty
+ end
+ end
end
- context 'project snippets' do
- it 'returns public personal and project snippets for unauthorized user' do
- snippets = described_class.new(nil, project: project).execute
+ context 'filter by project' do
+ context 'when project is a Project object' do
+ it 'returns public personal and project snippets for unauthorized user' do
+ snippets = described_class.new(nil, project: project).execute
- expect(snippets).to contain_exactly(public_project_snippet)
+ expect(snippets).to contain_exactly(public_project_snippet)
+ end
+ end
+
+ context 'when project is a Project id' do
+ it 'returns public personal and project snippets for unauthorized user' do
+ snippets = described_class.new(nil, project: project.id).execute
+
+ expect(snippets).to contain_exactly(public_project_snippet)
+ end
end
it 'returns public and internal snippets for non project members' do
@@ -175,6 +205,49 @@ describe SnippetsFinder do
)
end
end
+
+ context 'when project is not valid' do
+ it 'returns quickly' do
+ finder = described_class.new(admin, project: 1234)
+
+ expect(finder).not_to receive(:init_collection)
+ expect(Snippet).to receive(:none).and_call_original
+ expect(finder.execute).to be_empty
+ end
+ end
+ end
+
+ context 'filter by snippet type' do
+ context 'when filtering by only_personal snippet' do
+ it 'returns only personal snippet' do
+ snippets = described_class.new(admin, only_personal: true).execute
+
+ expect(snippets).to contain_exactly(private_personal_snippet,
+ internal_personal_snippet,
+ public_personal_snippet)
+ end
+ end
+
+ context 'when filtering by only_project snippet' do
+ it 'returns only project snippet' do
+ snippets = described_class.new(admin, only_project: true).execute
+
+ expect(snippets).to contain_exactly(private_project_snippet,
+ internal_project_snippet,
+ public_project_snippet)
+ end
+ end
+ end
+
+ context 'filtering by ids' do
+ it 'returns only personal snippet' do
+ snippets = described_class.new(
+ admin, ids: [private_personal_snippet.id,
+ internal_personal_snippet.id]
+ ).execute
+
+ expect(snippets).to contain_exactly(private_personal_snippet, internal_personal_snippet)
+ end
end
context 'explore snippets' do
diff --git a/spec/fixtures/lib/gitlab/import_export/with_invalid_records/project.json b/spec/fixtures/lib/gitlab/import_export/with_invalid_records/project.json
new file mode 100644
index 00000000000..52b5649ae59
--- /dev/null
+++ b/spec/fixtures/lib/gitlab/import_export/with_invalid_records/project.json
@@ -0,0 +1,38 @@
+{
+ "description": "Nisi et repellendus ut enim quo accusamus vel magnam.",
+ "import_type": "gitlab_project",
+ "creator_id": 123,
+ "visibility_level": 10,
+ "archived": false,
+ "milestones": [
+ {
+ "id": 1,
+ "title": null,
+ "project_id": 8,
+ "description": 123,
+ "due_date": null,
+ "created_at": "NOT A DATE",
+ "updated_at": "NOT A DATE",
+ "state": "active",
+ "iid": 1,
+ "group_id": null
+ },
+ {
+ "id": 42,
+ "title": "A valid milestone",
+ "project_id": 8,
+ "description": "Project-level milestone",
+ "due_date": null,
+ "created_at": "2016-06-14T15:02:04.415Z",
+ "updated_at": "2016-06-14T15:02:04.415Z",
+ "state": "active",
+ "iid": 1,
+ "group_id": null
+ }
+ ],
+ "labels": [],
+ "issues": [],
+ "services": [],
+ "snippets": [],
+ "hooks": []
+}
diff --git a/spec/frontend/monitoring/charts/time_series_spec.js b/spec/frontend/monitoring/charts/time_series_spec.js
index c561c5edf3c..6fa2d15ccbf 100644
--- a/spec/frontend/monitoring/charts/time_series_spec.js
+++ b/spec/frontend/monitoring/charts/time_series_spec.js
@@ -48,7 +48,7 @@ describe('Time series component', () => {
// Mock data contains 2 panels, pick the first one
store.commit(`monitoringDashboard/${types.SET_QUERY_RESULT}`, mockedQueryResultPayload);
- [mockGraphData] = store.state.monitoringDashboard.dashboard.panel_groups[0].metrics;
+ [mockGraphData] = store.state.monitoringDashboard.dashboard.panel_groups[0].panels;
makeTimeSeriesChart = (graphData, type) =>
shallowMount(TimeSeries, {
@@ -235,7 +235,7 @@ describe('Time series component', () => {
});
it('utilizes all data points', () => {
- const { values } = mockGraphData.queries[0].result[0];
+ const { values } = mockGraphData.metrics[0].result[0];
expect(chartData.length).toBe(1);
expect(seriesData().data.length).toBe(values.length);
diff --git a/spec/frontend/monitoring/components/charts/anomaly_spec.js b/spec/frontend/monitoring/components/charts/anomaly_spec.js
index 6707d0b1fe8..38aef6e6052 100644
--- a/spec/frontend/monitoring/components/charts/anomaly_spec.js
+++ b/spec/frontend/monitoring/components/charts/anomaly_spec.js
@@ -17,8 +17,8 @@ const mockProjectPath = `${TEST_HOST}${mockProjectDir}`;
jest.mock('~/lib/utils/icon_utils'); // mock getSvgIconPathContent
const makeAnomalyGraphData = (datasetName, template = anomalyMockGraphData) => {
- const queries = anomalyMockResultValues[datasetName].map((values, index) => ({
- ...template.queries[index],
+ const metrics = anomalyMockResultValues[datasetName].map((values, index) => ({
+ ...template.metrics[index],
result: [
{
metrics: {},
@@ -26,7 +26,7 @@ const makeAnomalyGraphData = (datasetName, template = anomalyMockGraphData) => {
},
],
}));
- return { ...template, queries };
+ return { ...template, metrics };
};
describe('Anomaly chart component', () => {
@@ -67,19 +67,19 @@ describe('Anomaly chart component', () => {
describe('graph-data', () => {
it('receives a single "metric" series', () => {
const { graphData } = getTimeSeriesProps();
- expect(graphData.queries.length).toBe(1);
+ expect(graphData.metrics.length).toBe(1);
});
it('receives "metric" with all data', () => {
const { graphData } = getTimeSeriesProps();
- const query = graphData.queries[0];
- const expectedQuery = makeAnomalyGraphData(dataSetName).queries[0];
+ const query = graphData.metrics[0];
+ const expectedQuery = makeAnomalyGraphData(dataSetName).metrics[0];
expect(query).toEqual(expectedQuery);
});
it('receives the "metric" results', () => {
const { graphData } = getTimeSeriesProps();
- const { result } = graphData.queries[0];
+ const { result } = graphData.metrics[0];
const { values } = result[0];
const [metricDataset] = dataSet;
expect(values).toEqual(expect.any(Array));
@@ -266,12 +266,12 @@ describe('Anomaly chart component', () => {
describe('graph-data', () => {
it('receives a single "metric" series', () => {
const { graphData } = getTimeSeriesProps();
- expect(graphData.queries.length).toBe(1);
+ expect(graphData.metrics.length).toBe(1);
});
it('receives "metric" results and applies the offset to them', () => {
const { graphData } = getTimeSeriesProps();
- const { result } = graphData.queries[0];
+ const { result } = graphData.metrics[0];
const { values } = result[0];
const [metricDataset] = dataSet;
expect(values).toEqual(expect.any(Array));
diff --git a/spec/frontend/monitoring/embed/embed_spec.js b/spec/frontend/monitoring/embed/embed_spec.js
index 3e22b0858e6..c5219f6130e 100644
--- a/spec/frontend/monitoring/embed/embed_spec.js
+++ b/spec/frontend/monitoring/embed/embed_spec.js
@@ -62,7 +62,7 @@ describe('Embed', () => {
describe('metrics are available', () => {
beforeEach(() => {
store.state.monitoringDashboard.dashboard.panel_groups = groups;
- store.state.monitoringDashboard.dashboard.panel_groups[0].metrics = metricsData;
+ store.state.monitoringDashboard.dashboard.panel_groups[0].panels = metricsData;
store.state.monitoringDashboard.metricsWithData = metricsWithData;
mountComponent();
diff --git a/spec/frontend/monitoring/embed/mock_data.js b/spec/frontend/monitoring/embed/mock_data.js
index 1685021fd4b..8941c183807 100644
--- a/spec/frontend/monitoring/embed/mock_data.js
+++ b/spec/frontend/monitoring/embed/mock_data.js
@@ -42,38 +42,34 @@ export const metrics = [
},
];
-const queries = [
+const result = [
{
- result: [
- {
- values: [
- ['Mon', 1220],
- ['Tue', 932],
- ['Wed', 901],
- ['Thu', 934],
- ['Fri', 1290],
- ['Sat', 1330],
- ['Sun', 1320],
- ],
- },
+ values: [
+ ['Mon', 1220],
+ ['Tue', 932],
+ ['Wed', 901],
+ ['Thu', 934],
+ ['Fri', 1290],
+ ['Sat', 1330],
+ ['Sun', 1320],
],
},
];
export const metricsData = [
{
- queries,
metrics: [
{
metric_id: 15,
+ result,
},
],
},
{
- queries,
metrics: [
{
metric_id: 16,
+ result,
},
],
},
diff --git a/spec/frontend/monitoring/mock_data.js b/spec/frontend/monitoring/mock_data.js
index c42366ab484..758e86235be 100644
--- a/spec/frontend/monitoring/mock_data.js
+++ b/spec/frontend/monitoring/mock_data.js
@@ -110,9 +110,6 @@ export const anomalyMockGraphData = {
type: 'anomaly-chart',
weight: 3,
metrics: [
- // Not used
- ],
- queries: [
{
metricId: '90',
id: 'metric',
diff --git a/spec/frontend/monitoring/panel_type_spec.js b/spec/frontend/monitoring/panel_type_spec.js
index 54a63e7f61f..b30ad747a12 100644
--- a/spec/frontend/monitoring/panel_type_spec.js
+++ b/spec/frontend/monitoring/panel_type_spec.js
@@ -33,7 +33,7 @@ describe('Panel Type component', () => {
let glEmptyChart;
// Deep clone object before modifying
const graphDataNoResult = JSON.parse(JSON.stringify(graphDataPrometheusQueryRange));
- graphDataNoResult.queries[0].result = [];
+ graphDataNoResult.metrics[0].result = [];
beforeEach(() => {
panelType = shallowMount(PanelType, {
@@ -143,7 +143,7 @@ describe('Panel Type component', () => {
describe('csvText', () => {
it('converts metrics data from json to csv', () => {
const header = `timestamp,${graphDataPrometheusQueryRange.y_label}`;
- const data = graphDataPrometheusQueryRange.queries[0].result[0].values;
+ const data = graphDataPrometheusQueryRange.metrics[0].result[0].values;
const firstRow = `${data[0][0]},${data[0][1]}`;
const secondRow = `${data[1][0]},${data[1][1]}`;
diff --git a/spec/frontend/monitoring/store/mutations_spec.js b/spec/frontend/monitoring/store/mutations_spec.js
index fdad290a8d6..42031e01878 100644
--- a/spec/frontend/monitoring/store/mutations_spec.js
+++ b/spec/frontend/monitoring/store/mutations_spec.js
@@ -27,7 +27,7 @@ describe('Monitoring mutations', () => {
it('normalizes values', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups);
const expectedLabel = 'Pod average';
- const { label, query_range } = stateCopy.dashboard.panel_groups[0].metrics[0].metrics[0];
+ const { label, query_range } = stateCopy.dashboard.panel_groups[0].panels[0].metrics[0];
expect(label).toEqual(expectedLabel);
expect(query_range.length).toBeGreaterThan(0);
});
@@ -39,23 +39,12 @@ describe('Monitoring mutations', () => {
expect(stateCopy.dashboard.panel_groups[0].panels[0].metrics.length).toEqual(1);
expect(stateCopy.dashboard.panel_groups[0].panels[1].metrics.length).toEqual(1);
});
- it('assigns queries a metric id', () => {
+ it('assigns metrics a metric id', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups);
- expect(stateCopy.dashboard.panel_groups[0].metrics[0].queries[0].metricId).toEqual(
+ expect(stateCopy.dashboard.panel_groups[0].panels[0].metrics[0].metricId).toEqual(
'17_system_metrics_kubernetes_container_memory_average',
);
});
- describe('dashboard endpoint', () => {
- const dashboardGroups = metricsDashboardResponse.dashboard.panel_groups;
- it('aliases group panels to metrics for backwards compatibility', () => {
- mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, dashboardGroups);
- expect(stateCopy.dashboard.panel_groups[0].metrics[0]).toBeDefined();
- });
- it('aliases panel metrics to queries for backwards compatibility', () => {
- mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, dashboardGroups);
- expect(stateCopy.dashboard.panel_groups[0].metrics[0].queries).toBeDefined();
- });
- });
});
describe('RECEIVE_DEPLOYMENTS_DATA_SUCCESS', () => {
diff --git a/spec/frontend/monitoring/store/utils_spec.js b/spec/frontend/monitoring/store/utils_spec.js
index 98388ac19f8..d562aaaefe9 100644
--- a/spec/frontend/monitoring/store/utils_spec.js
+++ b/spec/frontend/monitoring/store/utils_spec.js
@@ -1,44 +1,4 @@
-import { groupQueriesByChartInfo, normalizeMetric, uniqMetricsId } from '~/monitoring/stores/utils';
-
-describe('groupQueriesByChartInfo', () => {
- let input;
- let output;
-
- it('groups metrics with the same chart title and y_axis label', () => {
- input = [
- { title: 'title', y_label: 'MB', queries: [{}] },
- { title: 'title', y_label: 'MB', queries: [{}] },
- { title: 'new title', y_label: 'MB', queries: [{}] },
- ];
-
- output = [
- {
- title: 'title',
- y_label: 'MB',
- queries: [{ metricId: null }, { metricId: null }],
- },
- { title: 'new title', y_label: 'MB', queries: [{ metricId: null }] },
- ];
-
- expect(groupQueriesByChartInfo(input)).toEqual(output);
- });
-
- // Functionality associated with the /additional_metrics endpoint
- it("associates a chart's stringified metric_id with the metric", () => {
- input = [{ id: 3, title: 'new title', y_label: 'MB', queries: [{}] }];
- output = [{ id: 3, title: 'new title', y_label: 'MB', queries: [{ metricId: '3' }] }];
-
- expect(groupQueriesByChartInfo(input)).toEqual(output);
- });
-
- // Functionality associated with the /metrics_dashboard endpoint
- it('aliases a stringified metrics_id on the metric to the metricId key', () => {
- input = [{ title: 'new title', y_label: 'MB', queries: [{ metric_id: 3 }] }];
- output = [{ title: 'new title', y_label: 'MB', queries: [{ metricId: '3', metric_id: 3 }] }];
-
- expect(groupQueriesByChartInfo(input)).toEqual(output);
- });
-});
+import { normalizeMetric, uniqMetricsId } from '~/monitoring/stores/utils';
describe('normalizeMetric', () => {
[
@@ -54,7 +14,7 @@ describe('normalizeMetric', () => {
},
].forEach(({ args, expected }) => {
it(`normalizes metric to "${expected}" with args=${JSON.stringify(args)}`, () => {
- expect(normalizeMetric(...args)).toEqual({ metric_id: expected });
+ expect(normalizeMetric(...args)).toEqual({ metric_id: expected, metricId: expected });
});
});
});
diff --git a/spec/frontend/vue_shared/components/clipboard_button_spec.js b/spec/frontend/vue_shared/components/clipboard_button_spec.js
new file mode 100644
index 00000000000..4fb6924daba
--- /dev/null
+++ b/spec/frontend/vue_shared/components/clipboard_button_spec.js
@@ -0,0 +1,60 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlButton } from '@gitlab/ui';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import Icon from '~/vue_shared/components/icon.vue';
+
+describe('clipboard button', () => {
+ let wrapper;
+
+ const createWrapper = propsData => {
+ wrapper = shallowMount(ClipboardButton, {
+ propsData,
+ sync: false,
+ attachToDocument: true,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('without gfm', () => {
+ beforeEach(() => {
+ createWrapper({
+ text: 'copy me',
+ title: 'Copy this value',
+ cssClass: 'btn-danger',
+ });
+ });
+
+ it('renders a button for clipboard', () => {
+ expect(wrapper.find(GlButton).exists()).toBe(true);
+ expect(wrapper.attributes('data-clipboard-text')).toBe('copy me');
+ expect(wrapper.find(Icon).props('name')).toBe('duplicate');
+ });
+
+ it('should have a tooltip with default values', () => {
+ expect(wrapper.attributes('data-original-title')).toBe('Copy this value');
+ });
+
+ it('should render provided classname', () => {
+ expect(wrapper.classes()).toContain('btn-danger');
+ });
+ });
+
+ describe('with gfm', () => {
+ it('sets data-clipboard-text with gfm', () => {
+ createWrapper({
+ text: 'copy me',
+ gfm: '`path/to/file`',
+ title: 'Copy this value',
+ cssClass: 'btn-danger',
+ });
+
+ expect(wrapper.attributes('data-clipboard-text')).toBe(
+ '{"text":"copy me","gfm":"`path/to/file`"}',
+ );
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_spec.js b/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_spec.js
new file mode 100644
index 00000000000..bdd18110629
--- /dev/null
+++ b/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_spec.js
@@ -0,0 +1,113 @@
+import _ from 'underscore';
+import { trimText } from 'helpers/text_helper';
+import { shallowMount } from '@vue/test-utils';
+import { GlLink } from '@gitlab/ui';
+import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
+import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
+import { TEST_HOST } from 'spec/test_constants';
+
+describe('User Avatar Link Component', () => {
+ let wrapper;
+
+ const defaultProps = {
+ linkHref: `${TEST_HOST}/myavatarurl.com`,
+ imgSize: 99,
+ imgSrc: `${TEST_HOST}/myavatarurl.com`,
+ imgAlt: 'mydisplayname',
+ imgCssClasses: 'myextraavatarclass',
+ tooltipText: 'tooltip text',
+ tooltipPlacement: 'bottom',
+ username: 'username',
+ };
+
+ const createWrapper = props => {
+ wrapper = shallowMount(UserAvatarLink, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ sync: false,
+ attachToDocument: true,
+ });
+ };
+
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('should return a defined Vue component', () => {
+ expect(wrapper.isVueInstance()).toBe(true);
+ });
+
+ it('should have user-avatar-image registered as child component', () => {
+ expect(wrapper.vm.$options.components.userAvatarImage).toBeDefined();
+ });
+
+ it('user-avatar-link should have user-avatar-image as child component', () => {
+ expect(wrapper.find(UserAvatarImage).exists()).toBe(true);
+ });
+
+ it('should render GlLink as a child element', () => {
+ const link = wrapper.find(GlLink);
+
+ expect(link.exists()).toBe(true);
+ expect(link.attributes('href')).toBe(defaultProps.linkHref);
+ });
+
+ it('should return necessary props as defined', () => {
+ _.each(defaultProps, (val, key) => {
+ expect(wrapper.vm[key]).toBeDefined();
+ });
+ });
+
+ describe('no username', () => {
+ beforeEach(() => {
+ createWrapper({
+ username: '',
+ });
+ });
+
+ it('should only render image tag in link', () => {
+ const childElements = wrapper.vm.$el.childNodes;
+
+ expect(wrapper.find('img')).not.toBe('null');
+
+ // Vue will render the hidden component as <!---->
+ expect(childElements[1].tagName).toBeUndefined();
+ });
+
+ it('should render avatar image tooltip', () => {
+ expect(wrapper.vm.shouldShowUsername).toBe(false);
+ expect(wrapper.vm.avatarTooltipText).toEqual(defaultProps.tooltipText);
+ });
+ });
+
+ describe('username', () => {
+ it('should not render avatar image tooltip', () => {
+ expect(wrapper.find('.js-user-avatar-image-toolip').exists()).toBe(false);
+ });
+
+ it('should render username prop in <span>', () => {
+ expect(trimText(wrapper.find('.js-user-avatar-link-username').text())).toEqual(
+ defaultProps.username,
+ );
+ });
+
+ it('should render text tooltip for <span>', () => {
+ expect(
+ wrapper.find('.js-user-avatar-link-username').attributes('data-original-title'),
+ ).toEqual(defaultProps.tooltipText);
+ });
+
+ it('should render text tooltip placement for <span>', () => {
+ expect(wrapper.find('.js-user-avatar-link-username').attributes('tooltip-placement')).toBe(
+ defaultProps.tooltipPlacement,
+ );
+ });
+ });
+});
diff --git a/spec/helpers/award_emoji_helper_spec.rb b/spec/helpers/award_emoji_helper_spec.rb
index 2ee27bc5427..2ad6b68a34c 100644
--- a/spec/helpers/award_emoji_helper_spec.rb
+++ b/spec/helpers/award_emoji_helper_spec.rb
@@ -4,59 +4,69 @@ require 'spec_helper'
describe AwardEmojiHelper do
describe '.toggle_award_url' do
+ subject { helper.toggle_award_url(awardable) }
+
context 'note on personal snippet' do
- let(:note) { create(:note_on_personal_snippet) }
+ let(:snippet) { create(:personal_snippet) }
+ let(:note) { create(:note_on_personal_snippet, noteable: snippet) }
+ let(:awardable) { note }
+
+ subject { helper.toggle_award_url(note) }
it 'returns correct url' do
expected_url = "/snippets/#{note.noteable.id}/notes/#{note.id}/toggle_award_emoji"
- expect(helper.toggle_award_url(note)).to eq(expected_url)
+ expect(subject).to eq(expected_url)
end
end
context 'note on project item' do
let(:note) { create(:note_on_project_snippet) }
+ let(:awardable) { note }
it 'returns correct url' do
@project = note.noteable.project
expected_url = "/#{@project.namespace.path}/#{@project.path}/notes/#{note.id}/toggle_award_emoji"
- expect(helper.toggle_award_url(note)).to eq(expected_url)
+ expect(subject).to eq(expected_url)
end
end
context 'personal snippet' do
let(:snippet) { create(:personal_snippet) }
+ let(:awardable) { snippet }
it 'returns correct url' do
expected_url = "/snippets/#{snippet.id}/toggle_award_emoji"
- expect(helper.toggle_award_url(snippet)).to eq(expected_url)
+ expect(subject).to eq(expected_url)
end
end
context 'merge request' do
let(:merge_request) { create(:merge_request) }
+ let(:awardable) { merge_request }
it 'returns correct url' do
@project = merge_request.project
expected_url = "/#{@project.namespace.path}/#{@project.path}/merge_requests/#{merge_request.iid}/toggle_award_emoji"
- expect(helper.toggle_award_url(merge_request)).to eq(expected_url)
+ expect(subject).to eq(expected_url)
end
end
context 'issue' do
let(:issue) { create(:issue) }
+ let(:awardable) { issue }
it 'returns correct url' do
@project = issue.project
expected_url = "/#{@project.namespace.path}/#{@project.path}/issues/#{issue.iid}/toggle_award_emoji"
- expect(helper.toggle_award_url(issue)).to eq(expected_url)
+ expect(subject).to eq(expected_url)
end
end
end
diff --git a/spec/helpers/gitlab_routing_helper_spec.rb b/spec/helpers/gitlab_routing_helper_spec.rb
index 38699108b06..9cc1a9dfd5b 100644
--- a/spec/helpers/gitlab_routing_helper_spec.rb
+++ b/spec/helpers/gitlab_routing_helper_spec.rb
@@ -112,4 +112,98 @@ describe GitlabRoutingHelper do
expect(edit_milestone_path(milestone)).to eq("/#{milestone.project.full_path}/-/milestones/#{milestone.iid}/edit")
end
end
+
+ context 'snippets' do
+ let_it_be(:personal_snippet) { create(:personal_snippet) }
+ let_it_be(:project_snippet) { create(:project_snippet) }
+ let_it_be(:note) { create(:note_on_personal_snippet, noteable: personal_snippet) }
+
+ describe '#snippet_path' do
+ it 'returns the personal snippet path' do
+ expect(snippet_path(personal_snippet)).to eq("/snippets/#{personal_snippet.id}")
+ end
+
+ it 'returns the project snippet path' do
+ expect(snippet_path(project_snippet)).to eq("/#{project_snippet.project.full_path}/snippets/#{project_snippet.id}")
+ end
+ end
+
+ describe '#snippet_url' do
+ it 'returns the personal snippet url' do
+ expect(snippet_url(personal_snippet)).to eq("#{Settings.gitlab['url']}/snippets/#{personal_snippet.id}")
+ end
+
+ it 'returns the project snippet url' do
+ expect(snippet_url(project_snippet)).to eq("#{Settings.gitlab['url']}/#{project_snippet.project.full_path}/snippets/#{project_snippet.id}")
+ end
+ end
+
+ describe '#raw_snippet_path' do
+ it 'returns the raw personal snippet path' do
+ expect(raw_snippet_path(personal_snippet)).to eq("/snippets/#{personal_snippet.id}/raw")
+ end
+
+ it 'returns the raw project snippet path' do
+ expect(raw_snippet_path(project_snippet)).to eq("/#{project_snippet.project.full_path}/snippets/#{project_snippet.id}/raw")
+ end
+ end
+
+ describe '#raw_snippet_url' do
+ it 'returns the raw personal snippet url' do
+ expect(raw_snippet_url(personal_snippet)).to eq("#{Settings.gitlab['url']}/snippets/#{personal_snippet.id}/raw")
+ end
+
+ it 'returns the raw project snippet url' do
+ expect(raw_snippet_url(project_snippet)).to eq("#{Settings.gitlab['url']}/#{project_snippet.project.full_path}/snippets/#{project_snippet.id}/raw")
+ end
+ end
+
+ describe '#snippet_notes_path' do
+ it 'returns the notes path for the personal snippet' do
+ expect(snippet_notes_path(personal_snippet)).to eq("/snippets/#{personal_snippet.id}/notes")
+ end
+ end
+
+ describe '#snippet_notes_url' do
+ it 'returns the notes url for the personal snippet' do
+ expect(snippet_notes_url(personal_snippet)).to eq("#{Settings.gitlab['url']}/snippets/#{personal_snippet.id}/notes")
+ end
+ end
+
+ describe '#snippet_note_path' do
+ it 'returns the note path for the personal snippet' do
+ expect(snippet_note_path(personal_snippet, note)).to eq("/snippets/#{personal_snippet.id}/notes/#{note.id}")
+ end
+ end
+
+ describe '#snippet_note_url' do
+ it 'returns the note url for the personal snippet' do
+ expect(snippet_note_url(personal_snippet, note)).to eq("#{Settings.gitlab['url']}/snippets/#{personal_snippet.id}/notes/#{note.id}")
+ end
+ end
+
+ describe '#toggle_award_emoji_snippet_note_path' do
+ it 'returns the note award emoji path for the personal snippet' do
+ expect(toggle_award_emoji_snippet_note_path(personal_snippet, note)).to eq("/snippets/#{personal_snippet.id}/notes/#{note.id}/toggle_award_emoji")
+ end
+ end
+
+ describe '#toggle_award_emoji_snippet_note_url' do
+ it 'returns the note award emoji url for the personal snippet' do
+ expect(toggle_award_emoji_snippet_note_url(personal_snippet, note)).to eq("#{Settings.gitlab['url']}/snippets/#{personal_snippet.id}/notes/#{note.id}/toggle_award_emoji")
+ end
+ end
+
+ describe '#toggle_award_emoji_snippet_path' do
+ it 'returns the award emoji path for the personal snippet' do
+ expect(toggle_award_emoji_snippet_path(personal_snippet)).to eq("/snippets/#{personal_snippet.id}/toggle_award_emoji")
+ end
+ end
+
+ describe '#toggle_award_emoji_snippet_url' do
+ it 'returns the award url for the personal snippet' do
+ expect(toggle_award_emoji_snippet_url(personal_snippet)).to eq("#{Settings.gitlab['url']}/snippets/#{personal_snippet.id}/toggle_award_emoji")
+ end
+ end
+ end
end
diff --git a/spec/helpers/snippets_helper_spec.rb b/spec/helpers/snippets_helper_spec.rb
index d88e151a11c..2a24950502b 100644
--- a/spec/helpers/snippets_helper_spec.rb
+++ b/spec/helpers/snippets_helper_spec.rb
@@ -9,107 +9,18 @@ describe SnippetsHelper do
let_it_be(:public_personal_snippet) { create(:personal_snippet, :public) }
let_it_be(:public_project_snippet) { create(:project_snippet, :public) }
- describe '#reliable_snippet_path' do
- subject { reliable_snippet_path(snippet) }
-
- context 'personal snippets' do
- let(:snippet) { public_personal_snippet }
-
- context 'public' do
- it 'returns a full path' do
- expect(subject).to eq("/snippets/#{snippet.id}")
- end
- end
- end
-
- context 'project snippets' do
- let(:snippet) { public_project_snippet }
-
- it 'returns a full path' do
- expect(subject).to eq("/#{snippet.project.full_path}/snippets/#{snippet.id}")
- end
- end
- end
-
- describe '#reliable_snippet_url' do
- subject { reliable_snippet_url(snippet) }
-
- context 'personal snippets' do
- let(:snippet) { public_personal_snippet }
-
- context 'public' do
- it 'returns a full url' do
- expect(subject).to eq("http://test.host/snippets/#{snippet.id}")
- end
- end
- end
-
- context 'project snippets' do
- let(:snippet) { public_project_snippet }
-
- it 'returns a full url' do
- expect(subject).to eq("http://test.host/#{snippet.project.full_path}/snippets/#{snippet.id}")
- end
- end
- end
-
- describe '#reliable_raw_snippet_path' do
- subject { reliable_raw_snippet_path(snippet) }
-
- context 'personal snippets' do
- let(:snippet) { public_personal_snippet }
-
- context 'public' do
- it 'returns a full path' do
- expect(subject).to eq("/snippets/#{snippet.id}/raw")
- end
- end
- end
-
- context 'project snippets' do
- let(:snippet) { public_project_snippet }
-
- it 'returns a full path' do
- expect(subject).to eq("/#{snippet.project.full_path}/snippets/#{snippet.id}/raw")
- end
- end
- end
-
- describe '#reliable_raw_snippet_url' do
- subject { reliable_raw_snippet_url(snippet) }
-
- context 'personal snippets' do
- let(:snippet) { public_personal_snippet }
-
- context 'public' do
- it 'returns a full url' do
- expect(subject).to eq("http://test.host/snippets/#{snippet.id}/raw")
- end
- end
- end
-
- context 'project snippets' do
- let(:snippet) { public_project_snippet }
-
- it 'returns a full url' do
- expect(subject).to eq("http://test.host/#{snippet.project.full_path}/snippets/#{snippet.id}/raw")
- end
- end
- end
-
describe '#embedded_raw_snippet_button' do
subject { embedded_raw_snippet_button.to_s }
it 'returns view raw button of embedded snippets for personal snippets' do
@snippet = create(:personal_snippet, :public)
-
- expect(subject).to eq(download_link("http://test.host/snippets/#{@snippet.id}/raw"))
+ expect(subject).to eq(download_link("#{Settings.gitlab['url']}/snippets/#{@snippet.id}/raw"))
end
it 'returns view raw button of embedded snippets for project snippets' do
@snippet = create(:project_snippet, :public)
- expect(subject).to eq(download_link("http://test.host/#{@snippet.project.path_with_namespace}/snippets/#{@snippet.id}/raw"))
+ expect(subject).to eq(download_link("#{Settings.gitlab['url']}/#{@snippet.project.path_with_namespace}/snippets/#{@snippet.id}/raw"))
end
def download_link(url)
@@ -123,13 +34,13 @@ describe SnippetsHelper do
it 'returns download button of embedded snippets for personal snippets' do
@snippet = create(:personal_snippet, :public)
- expect(subject).to eq(download_link("http://test.host/snippets/#{@snippet.id}/raw"))
+ expect(subject).to eq(download_link("#{Settings.gitlab['url']}/snippets/#{@snippet.id}/raw"))
end
it 'returns download button of embedded snippets for project snippets' do
@snippet = create(:project_snippet, :public)
- expect(subject).to eq(download_link("http://test.host/#{@snippet.project.path_with_namespace}/snippets/#{@snippet.id}/raw"))
+ expect(subject).to eq(download_link("#{Settings.gitlab['url']}/#{@snippet.project.path_with_namespace}/snippets/#{@snippet.id}/raw"))
end
def download_link(url)
@@ -145,7 +56,7 @@ describe SnippetsHelper do
context 'public' do
it 'returns a script tag with the snippet full url' do
- expect(subject).to eq(script_embed("http://test.host/snippets/#{snippet.id}"))
+ expect(subject).to eq(script_embed("#{Settings.gitlab['url']}/snippets/#{snippet.id}"))
end
end
end
@@ -154,7 +65,7 @@ describe SnippetsHelper do
let(:snippet) { public_project_snippet }
it 'returns a script tag with the snippet full url' do
- expect(subject).to eq(script_embed("http://test.host/#{snippet.project.path_with_namespace}/snippets/#{snippet.id}"))
+ expect(subject).to eq(script_embed("#{Settings.gitlab['url']}/#{snippet.project.path_with_namespace}/snippets/#{snippet.id}"))
end
end
diff --git a/spec/javascripts/boards/board_list_common_spec.js b/spec/javascripts/boards/board_list_common_spec.js
index ada7589b795..a92d885790d 100644
--- a/spec/javascripts/boards/board_list_common_spec.js
+++ b/spec/javascripts/boards/board_list_common_spec.js
@@ -9,7 +9,7 @@ import BoardList from '~/boards/components/board_list.vue';
import '~/boards/models/issue';
import '~/boards/models/list';
-import { listObj, boardsMockInterceptor, mockBoardService } from './mock_data';
+import { listObj, boardsMockInterceptor } from './mock_data';
import store from '~/boards/stores';
import boardsStore from '~/boards/stores/boards_store';
@@ -26,7 +26,6 @@ export default function createComponent({
document.body.appendChild(el);
const mock = new MockAdapter(axios);
mock.onAny().reply(boardsMockInterceptor);
- gl.boardService = mockBoardService();
boardsStore.create();
const BoardListComp = Vue.extend(BoardList);
diff --git a/spec/javascripts/boards/board_new_issue_spec.js b/spec/javascripts/boards/board_new_issue_spec.js
index 76675a78db2..8e4093cc25c 100644
--- a/spec/javascripts/boards/board_new_issue_spec.js
+++ b/spec/javascripts/boards/board_new_issue_spec.js
@@ -7,7 +7,7 @@ import boardNewIssue from '~/boards/components/board_new_issue.vue';
import boardsStore from '~/boards/stores/boards_store';
import '~/boards/models/list';
-import { listObj, boardsMockInterceptor, mockBoardService } from './mock_data';
+import { listObj, boardsMockInterceptor } from './mock_data';
describe('Issue boards new issue form', () => {
let vm;
@@ -36,7 +36,6 @@ describe('Issue boards new issue form', () => {
mock = new MockAdapter(axios);
mock.onAny().reply(boardsMockInterceptor);
- gl.boardService = mockBoardService();
boardsStore.create();
list = new List(listObj);
diff --git a/spec/javascripts/monitoring/charts/column_spec.js b/spec/javascripts/monitoring/charts/column_spec.js
index 27b3d435f08..9676617e8e1 100644
--- a/spec/javascripts/monitoring/charts/column_spec.js
+++ b/spec/javascripts/monitoring/charts/column_spec.js
@@ -11,7 +11,7 @@ describe('Column component', () => {
columnChart = shallowMount(localVue.extend(ColumnChart), {
propsData: {
graphData: {
- queries: [
+ metrics: [
{
x_label: 'Time',
y_label: 'Usage',
diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js
index f9cc839bde6..f59c4ee4264 100644
--- a/spec/javascripts/monitoring/mock_data.js
+++ b/spec/javascripts/monitoring/mock_data.js
@@ -105,22 +105,11 @@ export const graphDataPrometheusQuery = {
metrics: [
{
id: 'metric_a1',
- metric_id: 2,
+ metricId: '2',
query: 'max(go_memstats_alloc_bytes{job="prometheus"}) by (job) /1024/1024',
unit: 'MB',
label: 'Total Consumption',
- prometheus_endpoint_path:
- '/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024',
- },
- ],
- queries: [
- {
- metricId: null,
- id: 'metric_a1',
metric_id: 2,
- query: 'max(go_memstats_alloc_bytes{job="prometheus"}) by (job) /1024/1024',
- unit: 'MB',
- label: 'Total Consumption',
prometheus_endpoint_path:
'/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024',
result: [
@@ -140,24 +129,12 @@ export const graphDataPrometheusQueryRange = {
metrics: [
{
id: 'metric_a1',
- metric_id: 2,
+ metricId: '2',
query_range:
'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024',
unit: 'MB',
label: 'Total Consumption',
- prometheus_endpoint_path:
- '/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024',
- },
- ],
- queries: [
- {
- metricId: '10',
- id: 'metric_a1',
metric_id: 2,
- query_range:
- 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024',
- unit: 'MB',
- label: 'Total Consumption',
prometheus_endpoint_path:
'/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024',
result: [
@@ -176,8 +153,7 @@ export const graphDataPrometheusQueryRangeMultiTrack = {
weight: 3,
x_label: 'Status Code',
y_label: 'Time',
- metrics: [],
- queries: [
+ metrics: [
{
metricId: '1',
id: 'response_metrics_nginx_ingress_throughput_status_code',
diff --git a/spec/javascripts/monitoring/utils_spec.js b/spec/javascripts/monitoring/utils_spec.js
index 202b4ec8f2e..3459b44c7ec 100644
--- a/spec/javascripts/monitoring/utils_spec.js
+++ b/spec/javascripts/monitoring/utils_spec.js
@@ -314,32 +314,32 @@ describe('isDateTimePickerInputValid', () => {
});
describe('graphDataValidatorForAnomalyValues', () => {
- let oneQuery;
- let threeQueries;
- let fourQueries;
+ let oneMetric;
+ let threeMetrics;
+ let fourMetrics;
beforeEach(() => {
- oneQuery = graphDataPrometheusQuery;
- threeQueries = anomalyMockGraphData;
+ oneMetric = graphDataPrometheusQuery;
+ threeMetrics = anomalyMockGraphData;
- const queries = [...threeQueries.queries];
- queries.push(threeQueries.queries[0]);
- fourQueries = {
+ const metrics = [...threeMetrics.metrics];
+ metrics.push(threeMetrics.metrics[0]);
+ fourMetrics = {
...anomalyMockGraphData,
- queries,
+ metrics,
};
});
/*
- * Anomaly charts can accept results for exactly 3 queries,
+ * Anomaly charts can accept results for exactly 3 metrics,
*/
it('validates passes with the right query format', () => {
- expect(graphDataValidatorForAnomalyValues(threeQueries)).toBe(true);
+ expect(graphDataValidatorForAnomalyValues(threeMetrics)).toBe(true);
});
it('validation fails for wrong format, 1 metric', () => {
- expect(graphDataValidatorForAnomalyValues(oneQuery)).toBe(false);
+ expect(graphDataValidatorForAnomalyValues(oneMetric)).toBe(false);
});
it('validation fails for wrong format, more than 3 metrics', () => {
- expect(graphDataValidatorForAnomalyValues(fourQueries)).toBe(false);
+ expect(graphDataValidatorForAnomalyValues(fourMetrics)).toBe(false);
});
});
diff --git a/spec/javascripts/vue_shared/components/clipboard_button_spec.js b/spec/javascripts/vue_shared/components/clipboard_button_spec.js
deleted file mode 100644
index 29a76574b89..00000000000
--- a/spec/javascripts/vue_shared/components/clipboard_button_spec.js
+++ /dev/null
@@ -1,51 +0,0 @@
-import Vue from 'vue';
-import clipboardButton from '~/vue_shared/components/clipboard_button.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-describe('clipboard button', () => {
- const Component = Vue.extend(clipboardButton);
- let vm;
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('without gfm', () => {
- beforeEach(() => {
- vm = mountComponent(Component, {
- text: 'copy me',
- title: 'Copy this value',
- cssClass: 'btn-danger',
- });
- });
-
- it('renders a button for clipboard', () => {
- expect(vm.$el.tagName).toEqual('BUTTON');
- expect(vm.$el.getAttribute('data-clipboard-text')).toEqual('copy me');
- expect(vm.$el).toHaveSpriteIcon('duplicate');
- });
-
- it('should have a tooltip with default values', () => {
- expect(vm.$el.getAttribute('data-original-title')).toEqual('Copy this value');
- });
-
- it('should render provided classname', () => {
- expect(vm.$el.classList).toContain('btn-danger');
- });
- });
-
- describe('with gfm', () => {
- it('sets data-clipboard-text with gfm', () => {
- vm = mountComponent(Component, {
- text: 'copy me',
- gfm: '`path/to/file`',
- title: 'Copy this value',
- cssClass: 'btn-danger',
- });
-
- expect(vm.$el.getAttribute('data-clipboard-text')).toEqual(
- '{"text":"copy me","gfm":"`path/to/file`"}',
- );
- });
- });
-});
diff --git a/spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js
deleted file mode 100644
index 80aa75847ae..00000000000
--- a/spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js
+++ /dev/null
@@ -1,109 +0,0 @@
-import _ from 'underscore';
-import Vue from 'vue';
-import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
-import { TEST_HOST } from 'spec/test_constants';
-
-describe('User Avatar Link Component', function() {
- beforeEach(function() {
- this.propsData = {
- linkHref: `${TEST_HOST}/myavatarurl.com`,
- imgSize: 99,
- imgSrc: `${TEST_HOST}/myavatarurl.com`,
- imgAlt: 'mydisplayname',
- imgCssClasses: 'myextraavatarclass',
- tooltipText: 'tooltip text',
- tooltipPlacement: 'bottom',
- username: 'username',
- };
-
- const UserAvatarLinkComponent = Vue.extend(UserAvatarLink);
-
- this.userAvatarLink = new UserAvatarLinkComponent({
- propsData: this.propsData,
- }).$mount();
-
- [this.userAvatarImage] = this.userAvatarLink.$children;
- });
-
- it('should return a defined Vue component', function() {
- expect(this.userAvatarLink).toBeDefined();
- });
-
- it('should have user-avatar-image registered as child component', function() {
- expect(this.userAvatarLink.$options.components.userAvatarImage).toBeDefined();
- });
-
- it('user-avatar-link should have user-avatar-image as child component', function() {
- expect(this.userAvatarImage).toBeDefined();
- });
-
- it('should render <a> as a child element', function() {
- const link = this.userAvatarLink.$el;
-
- expect(link.tagName).toBe('A');
- expect(link.href).toBe(this.propsData.linkHref);
- });
-
- it('renders imgSrc with imgSize as image', function() {
- const { imgSrc, imgSize } = this.propsData;
- const image = this.userAvatarLink.$el.querySelector('img');
-
- expect(image).not.toBeNull();
- expect(image.src).toBe(`${imgSrc}?width=${imgSize}`);
- });
-
- it('should return necessary props as defined', function() {
- _.each(this.propsData, (val, key) => {
- expect(this.userAvatarLink[key]).toBeDefined();
- });
- });
-
- describe('no username', function() {
- beforeEach(function(done) {
- this.userAvatarLink.username = '';
-
- Vue.nextTick(done);
- });
-
- it('should only render image tag in link', function() {
- const childElements = this.userAvatarLink.$el.childNodes;
-
- expect(this.userAvatarLink.$el.querySelector('img')).not.toBe('null');
-
- // Vue will render the hidden component as <!---->
- expect(childElements[1].tagName).toBeUndefined();
- });
-
- it('should render avatar image tooltip', function() {
- expect(this.userAvatarLink.shouldShowUsername).toBe(false);
- expect(this.userAvatarLink.avatarTooltipText).toEqual(this.propsData.tooltipText);
- });
- });
-
- describe('username', function() {
- it('should not render avatar image tooltip', function() {
- expect(this.userAvatarLink.$el.querySelector('.js-user-avatar-image-toolip')).toBeNull();
- });
-
- it('should render username prop in <span>', function() {
- expect(
- this.userAvatarLink.$el.querySelector('.js-user-avatar-link-username').innerText.trim(),
- ).toEqual(this.propsData.username);
- });
-
- it('should render text tooltip for <span>', function() {
- expect(
- this.userAvatarLink.$el.querySelector('.js-user-avatar-link-username').dataset
- .originalTitle,
- ).toEqual(this.propsData.tooltipText);
- });
-
- it('should render text tooltip placement for <span>', function() {
- expect(
- this.userAvatarLink.$el
- .querySelector('.js-user-avatar-link-username')
- .getAttribute('tooltip-placement'),
- ).toEqual(this.propsData.tooltipPlacement);
- });
- });
-});
diff --git a/spec/lib/gitlab/etag_caching/router_spec.rb b/spec/lib/gitlab/etag_caching/router_spec.rb
index 8fcd4eb3c21..e25ce4df4aa 100644
--- a/spec/lib/gitlab/etag_caching/router_spec.rb
+++ b/spec/lib/gitlab/etag_caching/router_spec.rb
@@ -12,6 +12,15 @@ describe Gitlab::EtagCaching::Router do
expect(result.name).to eq 'issue_notes'
end
+ it 'matches MR notes endpoint' do
+ result = described_class.match(
+ '/my-group/and-subgroup/here-comes-the-project/noteable/merge_request/1/notes'
+ )
+
+ expect(result).to be_present
+ expect(result.name).to eq 'merge_request_notes'
+ end
+
it 'matches issue title endpoint' do
result = described_class.match(
'/my-group/my-project/issues/123/realtime_changes'
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 5612b0dc270..64d1a98ae71 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -365,6 +365,7 @@ project:
- root_of_fork_network
- fork_network_member
- fork_network
+- fork_network_projects
- custom_attributes
- lfs_file_locks
- project_badges
@@ -434,6 +435,7 @@ project:
- upstream_project_subscriptions
- downstream_project_subscriptions
- service_desk_setting
+- import_failures
award_emoji:
- awardable
- user
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 2d8a603172d..d0e5ca2dde3 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -362,7 +362,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
expect(restored_project_json).to eq(true)
end
- it_behaves_like 'restores project correctly',
+ it_behaves_like 'restores project successfully',
issues: 1,
labels: 2,
label_with_priorities: 'A project label',
@@ -375,7 +375,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
create(:ci_build, token: 'abcd')
end
- it_behaves_like 'restores project correctly',
+ it_behaves_like 'restores project successfully',
issues: 1,
labels: 2,
label_with_priorities: 'A project label',
@@ -452,7 +452,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
expect(restored_project_json).to eq(true)
end
- it_behaves_like 'restores project correctly',
+ it_behaves_like 'restores project successfully',
issues: 2,
labels: 2,
label_with_priorities: 'A project label',
@@ -633,4 +633,46 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end
end
end
+
+ context 'JSON with invalid records' do
+ let(:user) { create(:user) }
+ let!(:project) { create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') }
+ let(:project_tree_restorer) { described_class.new(user: user, shared: shared, project: project) }
+ let(:restored_project_json) { project_tree_restorer.restore }
+
+ context 'when some failures occur' do
+ context 'because a relation fails to be processed' do
+ let(:correlation_id) { 'my-correlation-id' }
+
+ before do
+ setup_import_export_config('with_invalid_records')
+
+ Labkit::Correlation::CorrelationId.use_id(correlation_id) do
+ expect(restored_project_json).to eq(true)
+ end
+ end
+
+ it_behaves_like 'restores project successfully',
+ issues: 0,
+ labels: 0,
+ label_with_priorities: nil,
+ milestones: 1,
+ first_issue_labels: 0,
+ services: 0,
+ import_failures: 1
+
+ it 'records the failures in the database' do
+ import_failure = ImportFailure.last
+
+ expect(import_failure.project_id).to eq(project.id)
+ expect(import_failure.relation_key).to eq('milestones')
+ expect(import_failure.relation_index).to be_present
+ expect(import_failure.exception_class).to eq('ActiveRecord::RecordInvalid')
+ expect(import_failure.exception_message).to be_present
+ expect(import_failure.correlation_id_value).to eq('my-correlation-id')
+ expect(import_failure.created_at).to be_present
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb
index 08d3c638f9e..0aab02b6c4c 100644
--- a/spec/lib/gitlab/url_builder_spec.rb
+++ b/spec/lib/gitlab/url_builder_spec.rb
@@ -59,6 +59,26 @@ describe Gitlab::UrlBuilder do
end
end
+ context 'when passing a ProjectSnippet' do
+ it 'returns a proper URL' do
+ project_snippet = create(:project_snippet)
+
+ url = described_class.build(project_snippet)
+
+ expect(url).to eq "#{Settings.gitlab['url']}/#{project_snippet.project.full_path}/snippets/#{project_snippet.id}"
+ end
+ end
+
+ context 'when passing a PersonalSnippet' do
+ it 'returns a proper URL' do
+ personal_snippet = create(:personal_snippet)
+
+ url = described_class.build(personal_snippet)
+
+ expect(url).to eq "#{Settings.gitlab['url']}/snippets/#{personal_snippet.id}"
+ end
+ end
+
context 'when passing a Note' do
context 'on a Commit' do
it 'returns a proper URL' do
diff --git a/spec/lib/gitlab/visibility_level_spec.rb b/spec/lib/gitlab/visibility_level_spec.rb
index 75dc7d8e6d1..16a05af2216 100644
--- a/spec/lib/gitlab/visibility_level_spec.rb
+++ b/spec/lib/gitlab/visibility_level_spec.rb
@@ -95,4 +95,28 @@ describe Gitlab::VisibilityLevel do
expect(described_class.valid_level?(described_class::PUBLIC)).to be_truthy
end
end
+
+ describe '#visibility_level_decreased?' do
+ let(:project) { create(:project, :internal) }
+
+ context 'when visibility level decreases' do
+ before do
+ project.update!(visibility_level: described_class::PRIVATE)
+ end
+
+ it 'returns true' do
+ expect(project.visibility_level_decreased?).to be(true)
+ end
+ end
+
+ context 'when visibility level does not decrease' do
+ before do
+ project.update!(visibility_level: described_class::PUBLIC)
+ end
+
+ it 'returns false' do
+ expect(project.visibility_level_decreased?).to be(false)
+ end
+ end
+ end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 79ea29c84a4..f3d270cdf7e 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -33,6 +33,21 @@ describe MergeRequest do
end
end
+ describe '.from_and_to_forks' do
+ it 'returns only MRs from and to forks (with no internal MRs)' do
+ project = create(:project)
+ fork = fork_project(project)
+ fork_2 = fork_project(project)
+ mr_from_fork = create(:merge_request, source_project: fork, target_project: project)
+ mr_to_fork = create(:merge_request, source_project: project, target_project: fork)
+
+ create(:merge_request, source_project: fork, target_project: fork_2)
+ create(:merge_request, source_project: project, target_project: project)
+
+ expect(described_class.from_and_to_forks(project)).to contain_exactly(mr_from_fork, mr_to_fork)
+ end
+ end
+
describe 'locking' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 74f2fc1bb61..a6d9ecaa7c5 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -1048,20 +1048,20 @@ describe Note do
describe 'expiring ETag cache' do
let(:note) { build(:note_on_issue) }
- def expect_expiration(note)
+ def expect_expiration(noteable)
expect_any_instance_of(Gitlab::EtagCaching::Store)
.to receive(:touch)
- .with("/#{note.project.namespace.to_param}/#{note.project.to_param}/noteable/issue/#{note.noteable.id}/notes")
+ .with("/#{noteable.project.namespace.to_param}/#{noteable.project.to_param}/noteable/#{noteable.class.name.underscore}/#{noteable.id}/notes")
end
it "expires cache for note's issue when note is saved" do
- expect_expiration(note)
+ expect_expiration(note.noteable)
note.save!
end
it "expires cache for note's issue when note is destroyed" do
- expect_expiration(note)
+ expect_expiration(note.noteable)
note.destroy!
end
@@ -1076,28 +1076,54 @@ describe Note do
end
end
- describe '#with_notes_filter' do
- let!(:comment) { create(:note) }
- let!(:system_note) { create(:note, system: true) }
+ context 'for merge requests' do
+ let_it_be(:merge_request) { create(:merge_request) }
- context 'when notes filter is nil' do
- subject { described_class.with_notes_filter(nil) }
+ context 'when adding a note to the MR' do
+ let(:note) { build(:note, noteable: merge_request, project: merge_request.project) }
- it { is_expected.to include(comment, system_note) }
+ it 'expires the MR note etag cache' do
+ expect_expiration(merge_request)
+
+ note.save!
+ end
end
- context 'when notes filter is set to all notes' do
- subject { described_class.with_notes_filter(UserPreference::NOTES_FILTERS[:all_notes]) }
+ context 'when adding a note to a commit on the MR' do
+ let(:note) { build(:note_on_commit, commit_id: merge_request.commits.first.id, project: merge_request.project) }
- it { is_expected.to include(comment, system_note) }
+ it 'expires the MR note etag cache' do
+ expect_expiration(merge_request)
+
+ note.save!
+ end
end
+ end
+ end
- context 'when notes filter is set to only comments' do
- subject { described_class.with_notes_filter(UserPreference::NOTES_FILTERS[:only_comments]) }
+ describe '#with_notes_filter' do
+ let!(:comment) { create(:note) }
+ let!(:system_note) { create(:note, system: true) }
- it { is_expected.to include(comment) }
- it { is_expected.not_to include(system_note) }
- end
+ subject { described_class.with_notes_filter(filter) }
+
+ context 'when notes filter is nil' do
+ let(:filter) { nil }
+
+ it { is_expected.to include(comment, system_note) }
+ end
+
+ context 'when notes filter is set to all notes' do
+ let(:filter) { UserPreference::NOTES_FILTERS[:all_notes] }
+
+ it { is_expected.to include(comment, system_note) }
+ end
+
+ context 'when notes filter is set to only comments' do
+ let(:filter) { UserPreference::NOTES_FILTERS[:only_comments] }
+
+ it { is_expected.to include(comment) }
+ it { is_expected.not_to include(system_note) }
end
end
diff --git a/spec/services/metrics/dashboard/grafana_metric_embed_service_spec.rb b/spec/services/metrics/dashboard/grafana_metric_embed_service_spec.rb
index f200c636aac..a772b911d8a 100644
--- a/spec/services/metrics/dashboard/grafana_metric_embed_service_spec.rb
+++ b/spec/services/metrics/dashboard/grafana_metric_embed_service_spec.rb
@@ -175,3 +175,64 @@ describe Metrics::Dashboard::GrafanaMetricEmbedService do
end
end
end
+
+describe Metrics::Dashboard::GrafanaUidParser do
+ let_it_be(:grafana_integration) { create(:grafana_integration) }
+ let_it_be(:project) { grafana_integration.project }
+
+ subject { described_class.new(grafana_url, project).parse }
+
+ context 'with a Grafana-defined uid' do
+ let(:grafana_url) { grafana_integration.grafana_url + '/d/XDaNK6amz/?panelId=1' }
+
+ it { is_expected.to eq 'XDaNK6amz' }
+ end
+
+ context 'with a user-defined uid' do
+ let(:grafana_url) { grafana_integration.grafana_url + '/d/pgbouncer-main/pgbouncer-overview?panelId=1' }
+
+ it { is_expected.to eq 'pgbouncer-main' }
+ end
+
+ context 'when a uid is not present' do
+ let(:grafana_url) { grafana_integration.grafana_url }
+
+ it { is_expected.to be nil }
+ end
+
+ context 'when the url starts with unrelated content' do
+ let(:grafana_url) { 'js:' + grafana_integration.grafana_url }
+
+ it { is_expected.to be nil }
+ end
+end
+
+describe Metrics::Dashboard::DatasourceNameParser do
+ include GrafanaApiHelpers
+
+ let(:grafana_url) { valid_grafana_dashboard_link('https://gitlab.grafana.net') }
+ let(:grafana_dashboard) { JSON.parse(fixture_file('grafana/dashboard_response.json'), symbolize_names: true) }
+
+ subject { described_class.new(grafana_url, grafana_dashboard).parse }
+
+ it { is_expected.to eq 'GitLab Omnibus' }
+
+ context 'when the panelId is missing from the url' do
+ let(:grafana_url) { 'https:/gitlab.grafana.net/d/jbdbks/' }
+
+ it { is_expected.to be nil }
+ end
+
+ context 'when the panel is not present' do
+ # We're looking for panelId of 8, but only 6 is present
+ let(:grafana_dashboard) { { dashboard: { panels: [{ id: 6 }] } } }
+
+ it { is_expected.to be nil }
+ end
+
+ context 'when the dashboard panel does not have a datasource' do
+ let(:grafana_dashboard) { { dashboard: { panels: [{ id: 8 }] } } }
+
+ it { is_expected.to be nil }
+ end
+end
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index 642986bb176..d8ba042af35 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -296,9 +296,12 @@ describe Projects::DestroyService do
end
context 'as the root of a fork network' do
- let!(:fork_network) { create(:fork_network, root_project: project) }
+ let!(:fork_1) { fork_project(project, user) }
+ let!(:fork_2) { fork_project(project, user) }
it 'updates the fork network with the project name' do
+ fork_network = project.fork_network
+
destroy_project(project, user)
fork_network.reload
diff --git a/spec/services/projects/unlink_fork_service_spec.rb b/spec/services/projects/unlink_fork_service_spec.rb
index a1175bf7123..a6bdc69cdca 100644
--- a/spec/services/projects/unlink_fork_service_spec.rb
+++ b/spec/services/projects/unlink_fork_service_spec.rb
@@ -2,13 +2,13 @@
require 'spec_helper'
-describe Projects::UnlinkForkService do
+describe Projects::UnlinkForkService, :use_clean_rails_memory_store_caching do
include ProjectForksHelper
subject { described_class.new(forked_project, user) }
let(:project) { create(:project, :public) }
- let(:forked_project) { fork_project(project, user) }
+ let!(:forked_project) { fork_project(project, user) }
let(:user) { create(:user) }
context 'with opened merge request on the source project' do
@@ -86,4 +86,169 @@ describe Projects::UnlinkForkService do
expect { subject.execute }.not_to raise_error
end
end
+
+ context 'when given project is a source of forks' do
+ let!(:forked_project_2) { fork_project(project, user) }
+ let!(:fork_of_fork) { fork_project(forked_project, user) }
+
+ subject { described_class.new(project, user) }
+
+ context 'with opened merge requests from fork back to root project' do
+ let!(:merge_request) { create(:merge_request, source_project: project, target_project: forked_project) }
+ let!(:merge_request2) { create(:merge_request, source_project: project, target_project: fork_project(project)) }
+ let!(:merge_request_in_fork) { create(:merge_request, source_project: forked_project, target_project: forked_project) }
+
+ let(:mr_close_service) { MergeRequests::CloseService.new(project, user) }
+
+ before do
+ allow(MergeRequests::CloseService).to receive(:new)
+ .with(project, user)
+ .and_return(mr_close_service)
+ end
+
+ it 'closes all pending merge requests' do
+ expect(mr_close_service).to receive(:execute).with(merge_request)
+ expect(mr_close_service).to receive(:execute).with(merge_request2)
+
+ subject.execute
+ end
+
+ it 'does not close merge requests that do not come from the project being unlinked' do
+ expect(mr_close_service).not_to receive(:execute).with(merge_request_in_fork)
+
+ subject.execute
+ end
+ end
+
+ it 'removes its link to the fork network and updates direct network members' do
+ expect(project.fork_network_member).to be_present
+ expect(project.fork_network).to be_present
+ expect(project.forked_to_members.count).to eq(2)
+ expect(forked_project.forked_to_members.count).to eq(1)
+ expect(fork_of_fork.forked_to_members.count).to eq(0)
+
+ subject.execute
+
+ project.reload
+ forked_project.reload
+ fork_of_fork.reload
+
+ expect(project.fork_network_member).to be_nil
+ expect(project.fork_network).to be_nil
+ expect(forked_project.fork_network).to have_attributes(root_project_id: nil,
+ deleted_root_project_name: project.full_name)
+ expect(project.forked_to_members.count).to eq(0)
+ expect(forked_project.forked_to_members.count).to eq(1)
+ expect(fork_of_fork.forked_to_members.count).to eq(0)
+ end
+
+ it 'refreshes the forks count cache of the given project' do
+ expect(project.forks_count).to eq(2)
+
+ subject.execute
+
+ expect(project.forks_count).to be_zero
+ end
+
+ context 'when given project is a fork of an unlinked parent' do
+ let!(:fork_of_fork) { fork_project(forked_project, user) }
+ let(:lfs_object) { create(:lfs_object) }
+
+ before do
+ lfs_object.projects << project
+ end
+
+ it 'saves lfs objects to the root project' do
+ # Remove parent from network
+ described_class.new(forked_project, user).execute
+
+ described_class.new(fork_of_fork, user).execute
+
+ expect(lfs_object.projects).to include(fork_of_fork)
+ end
+ end
+
+ context 'and is node with a parent' do
+ subject { described_class.new(forked_project, user) }
+
+ context 'with opened merge requests from and to given project' do
+ let!(:mr_from_parent) { create(:merge_request, source_project: project, target_project: forked_project) }
+ let!(:mr_to_parent) { create(:merge_request, source_project: forked_project, target_project: project) }
+ let!(:mr_to_child) { create(:merge_request, source_project: forked_project, target_project: fork_of_fork) }
+ let!(:mr_from_child) { create(:merge_request, source_project: fork_of_fork, target_project: forked_project) }
+ let!(:merge_request_in_fork) { create(:merge_request, source_project: forked_project, target_project: forked_project) }
+
+ let(:mr_close_service) { MergeRequests::CloseService.new(forked_project, user) }
+
+ before do
+ allow(MergeRequests::CloseService).to receive(:new)
+ .with(forked_project, user)
+ .and_return(mr_close_service)
+ end
+
+ it 'close all pending merge requests' do
+ merge_requests = [mr_from_parent, mr_to_parent, mr_from_child, mr_to_child]
+
+ merge_requests.each do |mr|
+ expect(mr_close_service).to receive(:execute).with(mr).and_call_original
+ end
+
+ subject.execute
+
+ merge_requests = MergeRequest.where(id: merge_requests)
+
+ expect(merge_requests).to all(have_attributes(state: 'closed'))
+ end
+
+ it 'does not close merge requests which do not come from the project being unlinked' do
+ expect(mr_close_service).not_to receive(:execute).with(merge_request_in_fork)
+
+ subject.execute
+ end
+ end
+
+ it 'refreshes the forks count cache of the parent and the given project' do
+ expect(project.forks_count).to eq(2)
+ expect(forked_project.forks_count).to eq(1)
+
+ subject.execute
+
+ expect(project.forks_count).to eq(1)
+ expect(forked_project.forks_count).to eq(0)
+ end
+
+ it 'removes its link to the fork network and updates direct network members' do
+ expect(project.fork_network).to be_present
+ expect(forked_project.fork_network).to be_present
+ expect(fork_of_fork.fork_network).to be_present
+
+ expect(project.forked_to_members.count).to eq(2)
+ expect(forked_project.forked_to_members.count).to eq(1)
+ expect(fork_of_fork.forked_to_members.count).to eq(0)
+
+ subject.execute
+ project.reload
+ forked_project.reload
+ fork_of_fork.reload
+
+ expect(project.fork_network).to be_present
+ expect(forked_project.fork_network).to be_nil
+ expect(fork_of_fork.fork_network).to be_present
+
+ expect(project.forked_to_members.count).to eq(1) # 1 child is gone
+ expect(forked_project.forked_to_members.count).to eq(0)
+ expect(fork_of_fork.forked_to_members.count).to eq(0)
+ end
+ end
+ end
+
+ context 'when given project is not part of a fork network' do
+ let!(:project_without_forks) { create(:project, :public) }
+
+ subject { described_class.new(project_without_forks, user) }
+
+ it 'does not raise errors' do
+ expect { subject.execute }.not_to raise_error
+ end
+ end
end
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index c848a5397e1..3092fb7116a 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -16,7 +16,14 @@ describe Projects::UpdateService do
let(:admin) { create(:admin) }
context 'when changing visibility level' do
- context 'when visibility_level is INTERNAL' do
+ def expect_to_call_unlink_fork_service
+ service = Projects::UnlinkForkService.new(project, user)
+
+ expect(Projects::UnlinkForkService).to receive(:new).with(project, user).and_return(service)
+ expect(service).to receive(:execute).and_call_original
+ end
+
+ context 'when visibility_level changes to INTERNAL' do
it 'updates the project to internal' do
expect(TodosDestroyer::ProjectPrivateWorker).not_to receive(:perform_in)
@@ -25,9 +32,21 @@ describe Projects::UpdateService do
expect(result).to eq({ status: :success })
expect(project).to be_internal
end
+
+ context 'and project is PUBLIC' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ end
+
+ it 'unlinks project from fork network' do
+ expect_to_call_unlink_fork_service
+
+ update_project(project, user, visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ end
+ end
end
- context 'when visibility_level is PUBLIC' do
+ context 'when visibility_level changes to PUBLIC' do
it 'updates the project to public' do
expect(TodosDestroyer::ProjectPrivateWorker).not_to receive(:perform_in)
@@ -36,9 +55,17 @@ describe Projects::UpdateService do
expect(result).to eq({ status: :success })
expect(project).to be_public
end
+
+ context 'and project is PRIVATE' do
+ it 'does not unlink project from fork network' do
+ expect(Projects::UnlinkForkService).not_to receive(:new)
+
+ update_project(project, user, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ end
+ end
end
- context 'when visibility_level is PRIVATE' do
+ context 'when visibility_level changes to PRIVATE' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
end
@@ -52,6 +79,30 @@ describe Projects::UpdateService do
expect(result).to eq({ status: :success })
expect(project).to be_private
end
+
+ context 'and project is PUBLIC' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ end
+
+ it 'unlinks project from fork network' do
+ expect_to_call_unlink_fork_service
+
+ update_project(project, user, visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+ end
+
+ context 'and project is INTERNAL' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ it 'unlinks project from fork network' do
+ expect_to_call_unlink_fork_service
+
+ update_project(project, user, visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+ end
end
context 'when visibility levels are restricted to PUBLIC only' do
@@ -107,28 +158,48 @@ describe Projects::UpdateService do
let(:project) { create(:project, :internal) }
let(:forked_project) { fork_project(project) }
- it 'updates forks visibility level when parent set to more restrictive' do
- opts = { visibility_level: Gitlab::VisibilityLevel::PRIVATE }
+ context 'and unlink forks feature flag is off' do
+ before do
+ stub_feature_flags(unlink_fork_network_upon_visibility_decrease: false)
+ end
+
+ it 'updates forks visibility level when parent set to more restrictive' do
+ opts = { visibility_level: Gitlab::VisibilityLevel::PRIVATE }
+
+ expect(project).to be_internal
+ expect(forked_project).to be_internal
+
+ expect(update_project(project, admin, opts)).to eq({ status: :success })
+
+ expect(project).to be_private
+ expect(forked_project.reload).to be_private
+ end
+
+ it 'does not update forks visibility level when parent set to less restrictive' do
+ opts = { visibility_level: Gitlab::VisibilityLevel::PUBLIC }
- expect(project).to be_internal
- expect(forked_project).to be_internal
+ expect(project).to be_internal
+ expect(forked_project).to be_internal
- expect(update_project(project, admin, opts)).to eq({ status: :success })
+ expect(update_project(project, admin, opts)).to eq({ status: :success })
- expect(project).to be_private
- expect(forked_project.reload).to be_private
+ expect(project).to be_public
+ expect(forked_project.reload).to be_internal
+ end
end
- it 'does not update forks visibility level when parent set to less restrictive' do
- opts = { visibility_level: Gitlab::VisibilityLevel::PUBLIC }
+ context 'and unlink forks feature flag is on' do
+ it 'does not change visibility of forks' do
+ opts = { visibility_level: Gitlab::VisibilityLevel::PRIVATE }
- expect(project).to be_internal
- expect(forked_project).to be_internal
+ expect(project).to be_internal
+ expect(forked_project).to be_internal
- expect(update_project(project, admin, opts)).to eq({ status: :success })
+ expect(update_project(project, admin, opts)).to eq({ status: :success })
- expect(project).to be_public
- expect(forked_project.reload).to be_internal
+ expect(project).to be_private
+ expect(forked_project.reload).to be_internal
+ end
end
end
diff --git a/spec/support/shared_examples/lib/gitlab/import_export/project_tree_restorer_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/import_export/project_tree_restorer_shared_examples.rb
index f26a8554055..2fee58a9f30 100644
--- a/spec/support/shared_examples/lib/gitlab/import_export/project_tree_restorer_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/import_export/project_tree_restorer_shared_examples.rb
@@ -2,7 +2,7 @@
# Shared examples for ProjectTreeRestorer (shared to allow the testing
# of EE-specific features)
-RSpec.shared_examples 'restores project correctly' do |**results|
+RSpec.shared_examples 'restores project successfully' do |**results|
it 'restores the project' do
expect(shared.errors).to be_empty
expect(restored_project_json).to be_truthy
@@ -34,4 +34,8 @@ RSpec.shared_examples 'restores project correctly' do |**results|
expect(project.import_type).to be_nil
expect(project.creator_id).not_to eq 123
end
+
+ it 'records exact number of import failures' do
+ expect(project.import_failures.size).to eq(results.fetch(:import_failures, 0))
+ end
end