summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-07-09 09:09:27 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-07-09 09:09:27 +0000
commitc3b45354d720654215eb0e7b8e718ba6ea2d7a96 (patch)
tree359066e91bad08ae8e404bb43316705b7b53993e /spec
parent734708924b0f86ad3c23636bd6a8942d679daaf2 (diff)
downloadgitlab-ce-c3b45354d720654215eb0e7b8e718ba6ea2d7a96.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/factories/projects.rb8
-rw-r--r--spec/factories/service_desk_settings.rb7
-rw-r--r--spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap4
-rw-r--r--spec/frontend/monitoring/components/charts/time_series_spec.js85
-rw-r--r--spec/frontend/monitoring/components/dashboard_header_spec.js48
-rw-r--r--spec/frontend/monitoring/graph_data.js70
-rw-r--r--spec/graphql/types/project_type_spec.rb2
-rw-r--r--spec/helpers/environments_helper_spec.rb18
-rw-r--r--spec/helpers/todos_helper_spec.rb20
-rw-r--r--spec/lib/gitlab/service_desk_email_spec.rb59
-rw-r--r--spec/lib/gitlab/service_desk_spec.rb56
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb34
-rw-r--r--spec/models/issue_spec.rb26
-rw-r--r--spec/models/project_spec.rb56
-rw-r--r--spec/models/service_desk_setting_spec.rb37
-rw-r--r--spec/support/helpers/stub_configuration.rb4
-rw-r--r--spec/support/shared_examples/policies/project_policy_shared_examples.rb28
17 files changed, 498 insertions, 64 deletions
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index dca8d70a166..46ed50932b7 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -316,6 +316,14 @@ FactoryBot.define do
end
end
+ trait :service_desk_disabled do
+ service_desk_enabled { nil }
+ end
+
+ trait(:service_desk_enabled) do
+ service_desk_enabled { true }
+ end
+
# Project with empty repository
#
# This is a case when you just created a project
diff --git a/spec/factories/service_desk_settings.rb b/spec/factories/service_desk_settings.rb
new file mode 100644
index 00000000000..abdbc5f1ea0
--- /dev/null
+++ b/spec/factories/service_desk_settings.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :service_desk_setting do
+ project
+ end
+end
diff --git a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap
index cb3f74ca73c..0a85dbc32d5 100644
--- a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap
+++ b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap
@@ -129,6 +129,10 @@ exports[`Dashboard template matches the default snapshot 1`] = `
<!---->
<!---->
+
+ <!---->
+
+ <!---->
</div>
<duplicate-dashboard-modal-stub
diff --git a/spec/frontend/monitoring/components/charts/time_series_spec.js b/spec/frontend/monitoring/components/charts/time_series_spec.js
index 3cb27be30ac..c4d6c25e18d 100644
--- a/spec/frontend/monitoring/components/charts/time_series_spec.js
+++ b/spec/frontend/monitoring/components/charts/time_series_spec.js
@@ -9,18 +9,12 @@ import {
GlChartSeriesLabel,
GlChartLegend,
} from '@gitlab/ui/dist/charts';
-import { cloneDeep } from 'lodash';
import { shallowWrapperContainsSlotText } from 'helpers/vue_test_utils_helper';
-import { createStore } from '~/monitoring/stores';
import { panelTypes, chartHeight } from '~/monitoring/constants';
import TimeSeries from '~/monitoring/components/charts/time_series.vue';
-import * as types from '~/monitoring/stores/mutation_types';
-import { deploymentData, mockProjectDir, annotationsData, metricsResult } from '../../mock_data';
-import {
- metricsDashboardPayload,
- metricsDashboardViewModel,
- metricResultStatus,
-} from '../../fixture_data';
+import { deploymentData, mockProjectDir, annotationsData } from '../../mock_data';
+
+import { timeSeriesGraphData } from '../../graph_data';
jest.mock('lodash/throttle', () =>
// this throttle mock executes immediately
@@ -35,23 +29,21 @@ jest.mock('~/lib/utils/icon_utils', () => ({
}));
describe('Time series component', () => {
- let mockGraphData;
- let store;
+ const defaultGraphData = timeSeriesGraphData();
let wrapper;
const createWrapper = (
- { graphData = mockGraphData, ...props } = {},
+ { graphData = defaultGraphData, ...props } = {},
mountingMethod = shallowMount,
) => {
wrapper = mountingMethod(TimeSeries, {
propsData: {
graphData,
- deploymentData: store.state.monitoringDashboard.deploymentData,
- annotations: store.state.monitoringDashboard.annotations,
+ deploymentData,
+ annotations: annotationsData,
projectPath: `${TEST_HOST}${mockProjectDir}`,
...props,
},
- store,
stubs: {
GlPopover: true,
},
@@ -59,27 +51,15 @@ describe('Time series component', () => {
});
};
- describe('With a single time series', () => {
- beforeEach(() => {
- setTestTimeout(1000);
-
- store = createStore();
-
- store.commit(
- `monitoringDashboard/${types.RECEIVE_METRICS_DASHBOARD_SUCCESS}`,
- metricsDashboardPayload,
- );
-
- store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData);
+ beforeEach(() => {
+ setTestTimeout(1000);
+ });
- store.commit(
- `monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`,
- metricResultStatus,
- );
- // dashboard is a dynamically generated fixture and stored at environment_metrics_dashboard.json
- [mockGraphData] = store.state.monitoringDashboard.dashboard.panelGroups[1].panels;
- });
+ afterEach(() => {
+ wrapper.destroy();
+ });
+ describe('With a single time series', () => {
describe('general functions', () => {
const findChart = () => wrapper.find({ ref: 'chart' });
@@ -88,10 +68,6 @@ describe('Time series component', () => {
return wrapper.vm.$nextTick();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('allows user to override legend label texts using props', () => {
const legendRelatedProps = {
legendMinText: 'legendMinText',
@@ -231,19 +207,20 @@ describe('Time series component', () => {
});
it('formats tooltip content', () => {
- const name = 'Status Code';
+ const name = 'Metric 1';
const value = '5.556';
const dataIndex = 0;
const seriesLabel = wrapper.find(GlChartSeriesLabel);
expect(seriesLabel.vm.color).toBe('');
+
expect(shallowWrapperContainsSlotText(seriesLabel, 'default', name)).toBe(true);
expect(wrapper.vm.tooltip.content).toEqual([
{ name, value, dataIndex, color: undefined },
]);
expect(
- shallowWrapperContainsSlotText(wrapper.find(GlAreaChart), 'tooltipContent', value),
+ shallowWrapperContainsSlotText(wrapper.find(GlLineChart), 'tooltipContent', value),
).toBe(true);
});
@@ -385,10 +362,8 @@ describe('Time series component', () => {
});
it('utilizes all data points', () => {
- const { values } = mockGraphData.metrics[0].result[0];
-
expect(chartData.length).toBe(1);
- expect(seriesData().data.length).toBe(values.length);
+ expect(seriesData().data.length).toBe(3);
});
it('creates valid data', () => {
@@ -602,14 +577,10 @@ describe('Time series component', () => {
it('constructs a label for the chart y-axis', () => {
const { yAxis } = getChartOptions();
- expect(yAxis[0].name).toBe('Requests / Sec');
+ expect(yAxis[0].name).toBe('Y Axis');
});
});
});
-
- afterEach(() => {
- wrapper.destroy();
- });
});
describe('wrapped components', () => {
@@ -630,7 +601,7 @@ describe('Time series component', () => {
beforeEach(() => {
createWrapper(
- { graphData: { ...mockGraphData, type: dynamicComponent.chartType } },
+ { graphData: timeSeriesGraphData({ type: dynamicComponent.chartType }) },
mount,
);
return wrapper.vm.$nextTick();
@@ -700,18 +671,12 @@ describe('Time series component', () => {
describe('with multiple time series', () => {
describe('General functions', () => {
beforeEach(() => {
- store = createStore();
- const graphData = cloneDeep(metricsDashboardViewModel.panelGroups[0].panels[3]);
- graphData.metrics.forEach(metric => Object.assign(metric, { result: metricsResult }));
+ const graphData = timeSeriesGraphData({ type: panelTypes.AREA_CHART, multiMetric: true });
- createWrapper({ graphData: { ...graphData, type: 'area-chart' } }, mount);
+ createWrapper({ graphData }, mount);
return wrapper.vm.$nextTick();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('Color match', () => {
let lineColors;
@@ -752,14 +717,10 @@ describe('Time series component', () => {
const findLegend = () => wrapper.find(GlChartLegend);
beforeEach(() => {
- createWrapper(mockGraphData, mount);
+ createWrapper({}, mount);
return wrapper.vm.$nextTick();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should render a tabular legend layout by default', () => {
expect(findLegend().props('layout')).toBe('table');
});
diff --git a/spec/frontend/monitoring/components/dashboard_header_spec.js b/spec/frontend/monitoring/components/dashboard_header_spec.js
index 5af7b4049d1..7522fe247b7 100644
--- a/spec/frontend/monitoring/components/dashboard_header_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_header_spec.js
@@ -157,4 +157,52 @@ describe('Dashboard header', () => {
});
});
});
+
+ describe('metrics settings button', () => {
+ const findSettingsButton = () => wrapper.find('[data-testid="metrics-settings-button"]');
+ const url = 'https://path/to/project/settings';
+
+ beforeEach(() => {
+ createShallowWrapper();
+
+ store.state.monitoringDashboard.canAccessOperationsSettings = false;
+ store.state.monitoringDashboard.operationsSettingsPath = '';
+ });
+
+ it('is rendered when the user can access the project settings and path to settings is available', () => {
+ store.state.monitoringDashboard.canAccessOperationsSettings = true;
+ store.state.monitoringDashboard.operationsSettingsPath = url;
+
+ return wrapper.vm.$nextTick(() => {
+ expect(findSettingsButton().exists()).toBe(true);
+ });
+ });
+
+ it('is not rendered when the user can not access the project settings', () => {
+ store.state.monitoringDashboard.canAccessOperationsSettings = false;
+ store.state.monitoringDashboard.operationsSettingsPath = url;
+
+ return wrapper.vm.$nextTick(() => {
+ expect(findSettingsButton().exists()).toBe(false);
+ });
+ });
+
+ it('is not rendered when the path to settings is unavailable', () => {
+ store.state.monitoringDashboard.canAccessOperationsSettings = false;
+ store.state.monitoringDashboard.operationsSettingsPath = '';
+
+ return wrapper.vm.$nextTick(() => {
+ expect(findSettingsButton().exists()).toBe(false);
+ });
+ });
+
+ it('leads to the project settings page', () => {
+ store.state.monitoringDashboard.canAccessOperationsSettings = true;
+ store.state.monitoringDashboard.operationsSettingsPath = url;
+
+ return wrapper.vm.$nextTick(() => {
+ expect(findSettingsButton().attributes('href')).toBe(url);
+ });
+ });
+ });
});
diff --git a/spec/frontend/monitoring/graph_data.js b/spec/frontend/monitoring/graph_data.js
new file mode 100644
index 00000000000..4874e558d06
--- /dev/null
+++ b/spec/frontend/monitoring/graph_data.js
@@ -0,0 +1,70 @@
+import { mapPanelToViewModel, normalizeQueryResponseData } from '~/monitoring/stores/utils';
+import { panelTypes, metricStates } from '~/monitoring/constants';
+
+const initTime = 1435781451.781;
+
+const makeValues = vals => vals.map((val, i) => [initTime + 15 * i, val]);
+
+// Normalized Prometheus Responses
+
+const matrixSingleResult = ({ values = ['1', '2', '3'] } = {}) =>
+ normalizeQueryResponseData({
+ resultType: 'matrix',
+ result: [
+ {
+ metric: {},
+ values: makeValues(values),
+ },
+ ],
+ });
+
+const matrixMultiResult = ({ values1 = ['1', '2', '3'], values2 = ['4', '5', '6'] } = {}) =>
+ normalizeQueryResponseData({
+ resultType: 'matrix',
+ result: [
+ {
+ metric: {
+ __name__: 'up',
+ job: 'prometheus',
+ instance: 'localhost:9090',
+ },
+ values: makeValues(values1),
+ },
+ {
+ metric: {
+ __name__: 'up',
+ job: 'node',
+ instance: 'localhost:9091',
+ },
+ values: makeValues(values2),
+ },
+ ],
+ });
+
+// GraphData factory
+
+/**
+ * Generate mock graph data according to options
+ *
+ * @param {Object} panelOptions - Panel options as in YML.
+ * @param {Object} dataOptions
+ * @param {Object} dataOptions.metricCount
+ * @param {Object} dataOptions.isMultiSeries
+ */
+// eslint-disable-next-line import/prefer-default-export
+export const timeSeriesGraphData = (panelOptions = {}, dataOptions = {}) => {
+ const { metricCount = 1, isMultiSeries = false } = dataOptions;
+
+ return mapPanelToViewModel({
+ title: 'Time Series Panel',
+ type: panelTypes.LINE_CHART,
+ x_label: 'X Axis',
+ y_label: 'Y Axis',
+ metrics: Array.from(Array(metricCount), (_, i) => ({
+ label: `Metric ${i + 1}`,
+ state: metricStates.OK,
+ result: isMultiSeries ? matrixMultiResult() : matrixSingleResult(),
+ })),
+ ...panelOptions,
+ });
+};
diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb
index 49e2cfd45e5..ea88ed6a3f5 100644
--- a/spec/graphql/types/project_type_spec.rb
+++ b/spec/graphql/types/project_type_spec.rb
@@ -26,7 +26,7 @@ RSpec.describe GitlabSchema.types['Project'] do
grafanaIntegration autocloseReferencedIssues suggestion_commit_message environments
boards jira_import_status jira_imports services releases release
alert_management_alerts alert_management_alert alert_management_alert_status_counts
- container_expiration_policy sast_ci_configuration
+ container_expiration_policy sast_ci_configuration service_desk_enabled service_desk_address
]
expect(described_class).to include_graphql_fields(*expected_fields)
diff --git a/spec/helpers/environments_helper_spec.rb b/spec/helpers/environments_helper_spec.rb
index c083eb89cc0..9386ec2ac71 100644
--- a/spec/helpers/environments_helper_spec.rb
+++ b/spec/helpers/environments_helper_spec.rb
@@ -42,10 +42,26 @@ RSpec.describe EnvironmentsHelper do
'custom-metrics-available' => 'true',
'alerts-endpoint' => project_prometheus_alerts_path(project, environment_id: environment.id, format: :json),
'prometheus-alerts-available' => 'true',
- 'custom-dashboard-base-path' => Metrics::Dashboard::CustomDashboardService::DASHBOARD_ROOT
+ 'custom-dashboard-base-path' => Metrics::Dashboard::CustomDashboardService::DASHBOARD_ROOT,
+ 'operations-settings-path' => project_settings_operations_path(project),
+ 'can-access-operations-settings' => 'true'
)
end
+ context 'without admin_operations permission' do
+ before do
+ allow(helper).to receive(:can?)
+ .with(user, :admin_operations, project)
+ .and_return(false)
+ end
+
+ specify do
+ expect(metrics_data).to include(
+ 'can-access-operations-settings' => 'false'
+ )
+ end
+ end
+
context 'without read_prometheus_alerts permission' do
before do
allow(helper).to receive(:can?)
diff --git a/spec/helpers/todos_helper_spec.rb b/spec/helpers/todos_helper_spec.rb
index 5b9168fb655..6b658a475b1 100644
--- a/spec/helpers/todos_helper_spec.rb
+++ b/spec/helpers/todos_helper_spec.rb
@@ -222,4 +222,24 @@ RSpec.describe TodosHelper do
end
end
end
+
+ describe '#todo_author_display?' do
+ using RSpec::Parameterized::TableSyntax
+
+ subject { helper.todo_author_display?(alert_todo) }
+
+ where(:action, :result) do
+ Todo::BUILD_FAILED | false
+ Todo::UNMERGEABLE | false
+ Todo::ASSIGNED | true
+ end
+
+ with_them do
+ before do
+ alert_todo.action = action
+ end
+
+ it { is_expected.to eq(result) }
+ end
+ end
end
diff --git a/spec/lib/gitlab/service_desk_email_spec.rb b/spec/lib/gitlab/service_desk_email_spec.rb
new file mode 100644
index 00000000000..23e2b2ff3cf
--- /dev/null
+++ b/spec/lib/gitlab/service_desk_email_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::ServiceDeskEmail do
+ describe '.enabled?' do
+ context 'when service_desk_email is enabled and address is set' do
+ before do
+ stub_service_desk_email_setting(enabled: true, address: 'foo')
+ end
+
+ it 'returns true' do
+ expect(described_class.enabled?).to be_truthy
+ end
+ end
+
+ context 'when service_desk_email is disabled' do
+ before do
+ stub_service_desk_email_setting(enabled: false, address: 'foo')
+ end
+
+ it 'returns false' do
+ expect(described_class.enabled?).to be_falsey
+ end
+ end
+
+ context 'when service desk address is not set' do
+ before do
+ stub_service_desk_email_setting(enabled: true, address: nil)
+ end
+
+ it 'returns false' do
+ expect(described_class.enabled?).to be_falsey
+ end
+ end
+ end
+
+ describe '.key_from_address' do
+ context 'when service desk address is set' do
+ before do
+ stub_service_desk_email_setting(address: 'address+%{key}@example.com')
+ end
+
+ it 'returns key' do
+ expect(described_class.key_from_address('address+key@example.com')).to eq('key')
+ end
+ end
+
+ context 'when service desk address is not set' do
+ before do
+ stub_service_desk_email_setting(address: nil)
+ end
+
+ it 'returns nil' do
+ expect(described_class.key_from_address('address+key@example.com')).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/service_desk_spec.rb b/spec/lib/gitlab/service_desk_spec.rb
new file mode 100644
index 00000000000..f554840ec78
--- /dev/null
+++ b/spec/lib/gitlab/service_desk_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::ServiceDesk do
+ before do
+ allow(Gitlab::IncomingEmail).to receive(:enabled?).and_return(true)
+ allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?).and_return(true)
+ end
+
+ describe 'enabled?' do
+ let_it_be(:project) { create(:project) }
+
+ subject { described_class.enabled?(project: project) }
+
+ it { is_expected.to be_truthy }
+
+ context 'when service desk is not supported' do
+ before do
+ allow(described_class).to receive(:supported?).and_return(false)
+ end
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when service desk is disabled for project' do
+ before do
+ project.update!(service_desk_enabled: false)
+ end
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ describe 'supported?' do
+ subject { described_class.supported? }
+
+ it { is_expected.to be_truthy }
+
+ context 'when incoming emails are disabled' do
+ before do
+ allow(Gitlab::IncomingEmail).to receive(:enabled?).and_return(false)
+ end
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when email key is not supported' do
+ before do
+ allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?).and_return(false)
+ end
+
+ it { is_expected.to be_falsy }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 5156e6bcb0d..cdd10f89a58 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -174,6 +174,40 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
:merge_requests_users
)
end
+
+ it 'includes accurate usage_activity_by_stage data' do
+ for_defined_days_back do
+ user = create(:user)
+ project = create(:project, :repository_private,
+ :test_repo, :remote_mirror, creator: user)
+ create(:merge_request, source_project: project)
+ create(:deploy_key, user: user)
+ create(:key, user: user)
+ create(:project, creator: user, disable_overriding_approvers_per_merge_request: true)
+ create(:project, creator: user, disable_overriding_approvers_per_merge_request: false)
+ create(:remote_mirror, project: project)
+ create(:snippet, author: user)
+ end
+
+ expect(described_class.uncached_data[:usage_activity_by_stage][:create]).to include(
+ deploy_keys: 2,
+ keys: 2,
+ merge_requests: 2,
+ projects_with_disable_overriding_approvers_per_merge_request: 2,
+ projects_without_disable_overriding_approvers_per_merge_request: 4,
+ remote_mirrors: 2,
+ snippets: 2
+ )
+ expect(described_class.uncached_data[:usage_activity_by_stage_monthly][:create]).to include(
+ deploy_keys: 1,
+ keys: 1,
+ merge_requests: 1,
+ projects_with_disable_overriding_approvers_per_merge_request: 1,
+ projects_without_disable_overriding_approvers_per_merge_request: 2,
+ remote_mirrors: 1,
+ snippets: 1
+ )
+ end
end
context 'for monitor' do
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 4221b5e5f7a..fe6f2ac6f4d 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -406,6 +406,22 @@ RSpec.describe Issue do
end
end
+ describe '#from_service_desk?' do
+ subject { issue.from_service_desk? }
+
+ context 'when issue author is support bot' do
+ let(:issue) { create(:issue, author: ::User.support_bot) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when issue author is not support bot' do
+ let(:issue) { create(:issue) }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
describe '#suggested_branch_name' do
let(:repository) { double }
@@ -1002,6 +1018,16 @@ RSpec.describe Issue do
end
end
+ describe '.service_desk' do
+ it 'returns the service desk issue' do
+ service_desk_issue = create(:issue, author: ::User.support_bot)
+ regular_issue = create(:issue)
+
+ expect(described_class.service_desk).to include(service_desk_issue)
+ expect(described_class.service_desk).not_to include(regular_issue)
+ end
+ end
+
it_behaves_like 'throttled touch' do
subject { create(:issue, updated_at: 1.hour.ago) }
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index c1c7892e2da..75336446c7e 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -1378,6 +1378,62 @@ RSpec.describe Project do
end
end
+ describe '.service_desk_enabled' do
+ it 'returns the correct project' do
+ project_with_service_desk_enabled = create(:project)
+ project_with_service_desk_disabled = create(:project, :service_desk_disabled)
+
+ expect(described_class.service_desk_enabled).to include(project_with_service_desk_enabled)
+ expect(described_class.service_desk_enabled).not_to include(project_with_service_desk_disabled)
+ end
+ end
+
+ describe '#service_desk_enabled?' do
+ let_it_be(:namespace) { create(:namespace) }
+
+ subject(:project) { build(:project, :private, namespace: namespace, service_desk_enabled: true) }
+
+ before do
+ allow(Gitlab::IncomingEmail).to receive(:enabled?).and_return(true)
+ allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?).and_return(true)
+ end
+
+ it 'is enabled' do
+ expect(project.service_desk_enabled?).to be_truthy
+ expect(project.service_desk_enabled).to be_truthy
+ end
+ end
+
+ describe '#service_desk_address' do
+ let_it_be(:project) { create(:project, service_desk_enabled: true) }
+
+ before do
+ allow(Gitlab::ServiceDesk).to receive(:enabled?).and_return(true)
+ allow(Gitlab.config.incoming_email).to receive(:enabled).and_return(true)
+ allow(Gitlab.config.incoming_email).to receive(:address).and_return("test+%{key}@mail.com")
+ end
+
+ it 'uses project full path as service desk address key' do
+ expect(project.service_desk_address).to eq("test+#{project.full_path_slug}-#{project.project_id}-issue-@mail.com")
+ end
+ end
+
+ describe '.find_by_service_desk_project_key' do
+ it 'returns the correct project' do
+ project1 = create(:project)
+ project2 = create(:project)
+ create(:service_desk_setting, project: project1, project_key: 'key1')
+ create(:service_desk_setting, project: project2, project_key: 'key2')
+
+ expect(Project.find_by_service_desk_project_key('key1')).to eq(project1)
+ expect(Project.find_by_service_desk_project_key('key2')).to eq(project2)
+ end
+
+ it 'returns nil if there is no project with the key' do
+ expect(Project.find_by_service_desk_project_key('some_key')).to be_nil
+ end
+ end
+
context 'repository storage by default' do
let(:project) { build(:project) }
diff --git a/spec/models/service_desk_setting_spec.rb b/spec/models/service_desk_setting_spec.rb
new file mode 100644
index 00000000000..ca57a5d4087
--- /dev/null
+++ b/spec/models/service_desk_setting_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ServiceDeskSetting do
+ describe 'validations' do
+ subject(:service_desk_setting) { create(:service_desk_setting) }
+
+ it { is_expected.to validate_presence_of(:project_id) }
+ it { is_expected.to validate_length_of(:outgoing_name).is_at_most(255) }
+ it { is_expected.to validate_length_of(:project_key).is_at_most(255) }
+ it { is_expected.to allow_value('abc123_').for(:project_key) }
+ it { is_expected.not_to allow_value('abc 12').for(:project_key) }
+ it { is_expected.not_to allow_value('Big val').for(:project_key) }
+
+ describe '.valid_issue_template' do
+ let_it_be(:project) { create(:project, :custom_repo, files: { '.gitlab/issue_templates/service_desk.md' => 'template' }) }
+
+ it 'is not valid if template does not exist' do
+ settings = build(:service_desk_setting, project: project, issue_template_key: 'invalid key')
+
+ expect(settings).not_to be_valid
+ expect(settings.errors[:issue_template_key].first).to eq('is empty or does not exist')
+ end
+
+ it 'is valid if template exists' do
+ settings = build(:service_desk_setting, project: project, issue_template_key: 'service_desk')
+
+ expect(settings).to be_valid
+ end
+ end
+ end
+
+ describe 'associations' do
+ it { is_expected.to belong_to(:project) }
+ end
+end
diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb
index 6a832ca97d1..83d4fa1aa2d 100644
--- a/spec/support/helpers/stub_configuration.rb
+++ b/spec/support/helpers/stub_configuration.rb
@@ -113,6 +113,10 @@ module StubConfiguration
allow(Gitlab.config.rack_attack.git_basic_auth).to receive_messages(to_settings(messages))
end
+ def stub_service_desk_email_setting(messages)
+ allow(::Gitlab.config.service_desk_email).to receive_messages(to_settings(messages))
+ end
+
private
# Modifies stubbed messages to also stub possible predicate versions
diff --git a/spec/support/shared_examples/policies/project_policy_shared_examples.rb b/spec/support/shared_examples/policies/project_policy_shared_examples.rb
index 4dd0152e3d1..f8526ec68dc 100644
--- a/spec/support/shared_examples/policies/project_policy_shared_examples.rb
+++ b/spec/support/shared_examples/policies/project_policy_shared_examples.rb
@@ -41,6 +41,28 @@ RSpec.shared_examples 'archived project policies' do
end
end
+RSpec.shared_examples 'project private features with read_all_resources ability' do
+ subject { described_class.new(user, project) }
+
+ before do
+ project.project_feature.update!(
+ repository_access_level: ProjectFeature::PRIVATE,
+ merge_requests_access_level: ProjectFeature::PRIVATE,
+ builds_access_level: ProjectFeature::PRIVATE
+ )
+ end
+
+ [:public, :internal, :private].each do |visibility|
+ context "for #{visibility} projects" do
+ let(:project) { create(:project, visibility, namespace: owner.namespace) }
+
+ it 'allows the download_code ability' do
+ expect_allowed(:download_code)
+ end
+ end
+ end
+end
+
RSpec.shared_examples 'project policies as anonymous' do
context 'abilities for public projects' do
context 'when a project has pending invites' do
@@ -231,6 +253,12 @@ RSpec.shared_examples 'project policies as admin with admin mode' do
let(:regular_abilities) { owner_permissions }
end
end
+
+ context 'abilities for all project visibility', :enable_admin_mode do
+ it_behaves_like 'project private features with read_all_resources ability' do
+ let(:user) { admin }
+ end
+ end
end
RSpec.shared_examples 'project policies as admin without admin mode' do