Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-12-20 18:12:25 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-12-20 18:12:25 +0300
commit068b3a417794ab8506b2e149301b3a60c01df078 (patch)
tree26ce51b45ae535a6fc47fb04cad8da42ec408a2f /spec
parent62c78157be8fe8888787162293f13945a5fa5d3e (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/concerns/check_rate_limit_spec.rb85
-rw-r--r--spec/finders/group_members_finder_spec.rb116
-rw-r--r--spec/frontend/vue_mr_widget/components/terraform/mock_data.js6
-rw-r--r--spec/frontend/vue_mr_widget/extentions/terraform/index_spec.js178
-rw-r--r--spec/graphql/types/group_member_relation_enum_spec.rb2
-rw-r--r--spec/lib/api/helpers/rate_limiter_spec.rb73
-rw-r--r--spec/lib/gitlab/metrics/samplers/action_cable_sampler_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb2
-rw-r--r--spec/lib/gitlab_edition_spec.rb160
-rw-r--r--spec/lib/gitlab_spec.rb131
-rw-r--r--spec/lib/sidebars/projects/panel_spec.rb3
-rw-r--r--spec/metrics_server/metrics_server_spec.rb25
-rw-r--r--spec/models/integrations/jira_spec.rb12
-rw-r--r--spec/requests/api/graphql/group/group_members_spec.rb11
-rw-r--r--spec/requests/api/users_spec.rb53
-rw-r--r--spec/rubocop/code_reuse_helpers_spec.rb73
-rw-r--r--spec/support/shared_examples/metrics/sampler_shared_examples.rb84
-rw-r--r--spec/views/shared/nav/_sidebar.html.haml_spec.rb3
18 files changed, 748 insertions, 271 deletions
diff --git a/spec/controllers/concerns/check_rate_limit_spec.rb b/spec/controllers/concerns/check_rate_limit_spec.rb
new file mode 100644
index 00000000000..34ececfe639
--- /dev/null
+++ b/spec/controllers/concerns/check_rate_limit_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe CheckRateLimit do
+ let(:key) { :some_key }
+ let(:scope) { [:some, :scope] }
+ let(:request) { instance_double('Rack::Request') }
+ let(:user) { build_stubbed(:user) }
+
+ let(:controller_class) do
+ Class.new do
+ include CheckRateLimit
+
+ attr_reader :request, :current_user
+
+ def initialize(request, current_user)
+ @request = request
+ @current_user = current_user
+ end
+
+ def redirect_back_or_default(**args)
+ end
+
+ def render(**args)
+ end
+ end
+ end
+
+ subject { controller_class.new(request, user) }
+
+ before do
+ allow(::Gitlab::ApplicationRateLimiter).to receive(:throttled?)
+ allow(::Gitlab::ApplicationRateLimiter).to receive(:log_request)
+ end
+
+ describe '#check_rate_limit!' do
+ it 'calls ApplicationRateLimiter#throttled? with the right arguments' do
+ expect(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).with(key, scope: scope).and_return(false)
+ expect(subject).not_to receive(:render)
+
+ subject.check_rate_limit!(key, scope: scope)
+ end
+
+ it 'renders error and logs request if throttled' do
+ expect(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).with(key, scope: scope).and_return(true)
+ expect(::Gitlab::ApplicationRateLimiter).to receive(:log_request).with(request, "#{key}_request_limit".to_sym, user)
+ expect(subject).to receive(:render).with({ plain: _('This endpoint has been requested too many times. Try again later.'), status: :too_many_requests })
+
+ subject.check_rate_limit!(key, scope: scope)
+ end
+
+ it 'redirects back if throttled and redirect_back option is set to true' do
+ expect(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).with(key, scope: scope).and_return(true)
+ expect(::Gitlab::ApplicationRateLimiter).to receive(:log_request).with(request, "#{key}_request_limit".to_sym, user)
+ expect(subject).not_to receive(:render)
+ expect(subject).to receive(:redirect_back_or_default).with(options: { alert: _('This endpoint has been requested too many times. Try again later.') })
+
+ subject.check_rate_limit!(key, scope: scope, redirect_back: true)
+ end
+
+ context 'when the bypass header is set' do
+ before do
+ allow(Gitlab::Throttle).to receive(:bypass_header).and_return('SOME_HEADER')
+ end
+
+ it 'skips rate limit if set to "1"' do
+ allow(request).to receive(:get_header).with(Gitlab::Throttle.bypass_header).and_return('1')
+
+ expect(::Gitlab::ApplicationRateLimiter).not_to receive(:throttled?)
+ expect(subject).not_to receive(:render)
+
+ subject.check_rate_limit!(key, scope: scope)
+ end
+
+ it 'does not skip rate limit if set to something else than "1"' do
+ allow(request).to receive(:get_header).with(Gitlab::Throttle.bypass_header).and_return('0')
+
+ expect(::Gitlab::ApplicationRateLimiter).to receive(:throttled?)
+
+ subject.check_rate_limit!(key, scope: scope)
+ end
+ end
+ end
+end
diff --git a/spec/finders/group_members_finder_spec.rb b/spec/finders/group_members_finder_spec.rb
index 0d797b7923c..a9a8e9d19b8 100644
--- a/spec/finders/group_members_finder_spec.rb
+++ b/spec/finders/group_members_finder_spec.rb
@@ -3,83 +3,93 @@
require 'spec_helper'
RSpec.describe GroupMembersFinder, '#execute' do
- let(:group) { create(:group) }
- let(:sub_group) { create(:group, parent: group) }
- let(:sub_sub_group) { create(:group, parent: sub_group) }
- let(:user1) { create(:user) }
- let(:user2) { create(:user) }
- let(:user3) { create(:user) }
- let(:user4) { create(:user) }
- let(:user5) { create(:user, :two_factor_via_otp) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:sub_group) { create(:group, parent: group) }
+ let_it_be(:sub_sub_group) { create(:group, parent: sub_group) }
+ let_it_be(:public_shared_group) { create(:group, :public) }
+ let_it_be(:private_shared_group) { create(:group, :private) }
+ let_it_be(:user1) { create(:user) }
+ let_it_be(:user2) { create(:user) }
+ let_it_be(:user3) { create(:user) }
+ let_it_be(:user4) { create(:user) }
+ let_it_be(:user5) { create(:user, :two_factor_via_otp) }
+
+ let!(:link) do
+ create(:group_group_link, shared_group: group, shared_with_group: public_shared_group)
+ create(:group_group_link, shared_group: sub_group, shared_with_group: private_shared_group)
+ end
let(:groups) do
{
- group: group,
- sub_group: sub_group,
- sub_sub_group: sub_sub_group
+ group: group,
+ sub_group: sub_group,
+ sub_sub_group: sub_sub_group,
+ public_shared_group: public_shared_group,
+ private_shared_group: private_shared_group
}
end
context 'relations' do
let!(:members) do
{
- user1_sub_sub_group: create(:group_member, :maintainer, group: sub_sub_group, user: user1),
- user1_sub_group: create(:group_member, :developer, group: sub_group, user: user1),
- user1_group: create(:group_member, :reporter, group: group, user: user1),
- user2_sub_sub_group: create(:group_member, :reporter, group: sub_sub_group, user: user2),
- user2_sub_group: create(:group_member, :developer, group: sub_group, user: user2),
- user2_group: create(:group_member, :maintainer, group: group, user: user2),
- user3_sub_sub_group: create(:group_member, :developer, group: sub_sub_group, user: user3, expires_at: 1.day.from_now),
- user3_sub_group: create(:group_member, :developer, group: sub_group, user: user3, expires_at: 2.days.from_now),
- user3_group: create(:group_member, :reporter, group: group, user: user3),
- user4_sub_sub_group: create(:group_member, :reporter, group: sub_sub_group, user: user4),
- user4_sub_group: create(:group_member, :developer, group: sub_group, user: user4, expires_at: 1.day.from_now),
- user4_group: create(:group_member, :developer, group: group, user: user4, expires_at: 2.days.from_now)
+ user1_sub_sub_group: create(:group_member, :maintainer, group: sub_sub_group, user: user1),
+ user1_sub_group: create(:group_member, :developer, group: sub_group, user: user1),
+ user1_group: create(:group_member, :reporter, group: group, user: user1),
+ user1_public_shared_group: create(:group_member, :maintainer, group: public_shared_group, user: user1),
+ user1_private_shared_group: create(:group_member, :maintainer, group: private_shared_group, user: user1),
+ user2_sub_sub_group: create(:group_member, :reporter, group: sub_sub_group, user: user2),
+ user2_sub_group: create(:group_member, :developer, group: sub_group, user: user2),
+ user2_group: create(:group_member, :maintainer, group: group, user: user2),
+ user2_public_shared_group: create(:group_member, :developer, group: public_shared_group, user: user2),
+ user2_private_shared_group: create(:group_member, :developer, group: private_shared_group, user: user2),
+ user3_sub_sub_group: create(:group_member, :developer, group: sub_sub_group, user: user3, expires_at: 1.day.from_now),
+ user3_sub_group: create(:group_member, :developer, group: sub_group, user: user3, expires_at: 2.days.from_now),
+ user3_group: create(:group_member, :reporter, group: group, user: user3),
+ user3_public_shared_group: create(:group_member, :reporter, group: public_shared_group, user: user3),
+ user3_private_shared_group: create(:group_member, :reporter, group: private_shared_group, user: user3),
+ user4_sub_sub_group: create(:group_member, :reporter, group: sub_sub_group, user: user4),
+ user4_sub_group: create(:group_member, :developer, group: sub_group, user: user4, expires_at: 1.day.from_now),
+ user4_group: create(:group_member, :developer, group: group, user: user4, expires_at: 2.days.from_now),
+ user4_public_shared_group: create(:group_member, :developer, group: public_shared_group, user: user4),
+ user4_private_shared_group: create(:group_member, :developer, group: private_shared_group, user: user4)
}
end
it 'raises an error if a non-supported relation type is used' do
expect do
described_class.new(group).execute(include_relations: [:direct, :invalid_relation_type])
- end.to raise_error(ArgumentError, "invalid_relation_type is not a valid relation type. Valid relation types are direct, inherited, descendants.")
+ end.to raise_error(ArgumentError, "invalid_relation_type is not a valid relation type. Valid relation types are direct, inherited, descendants, shared_from_groups.")
end
using RSpec::Parameterized::TableSyntax
where(:subject_relations, :subject_group, :expected_members) do
- nil | :group | [:user1_group, :user2_group, :user3_group, :user4_group]
- [:direct] | :group | [:user1_group, :user2_group, :user3_group, :user4_group]
- [:inherited] | :group | []
- [:descendants] | :group | [:user1_sub_sub_group, :user2_sub_group, :user3_sub_group, :user4_sub_group]
- [:direct, :inherited] | :group | [:user1_group, :user2_group, :user3_group, :user4_group]
- [:direct, :descendants] | :group | [:user1_sub_sub_group, :user2_group, :user3_sub_group, :user4_group]
- [:descendants, :inherited] | :group | [:user1_sub_sub_group, :user2_sub_group, :user3_sub_group, :user4_sub_group]
- [:direct, :descendants, :inherited] | :group | [:user1_sub_sub_group, :user2_group, :user3_sub_group, :user4_group]
- nil | :sub_group | [:user1_sub_group, :user2_group, :user3_sub_group, :user4_group]
- [:direct] | :sub_group | [:user1_sub_group, :user2_sub_group, :user3_sub_group, :user4_sub_group]
- [:inherited] | :sub_group | [:user1_group, :user2_group, :user3_group, :user4_group]
- [:descendants] | :sub_group | [:user1_sub_sub_group, :user2_sub_sub_group, :user3_sub_sub_group, :user4_sub_sub_group]
- [:direct, :inherited] | :sub_group | [:user1_sub_group, :user2_group, :user3_sub_group, :user4_group]
- [:direct, :descendants] | :sub_group | [:user1_sub_sub_group, :user2_sub_group, :user3_sub_group, :user4_sub_group]
- [:descendants, :inherited] | :sub_group | [:user1_sub_sub_group, :user2_group, :user3_sub_sub_group, :user4_group]
- [:direct, :descendants, :inherited] | :sub_group | [:user1_sub_sub_group, :user2_group, :user3_sub_group, :user4_group]
- nil | :sub_sub_group | [:user1_sub_sub_group, :user2_group, :user3_sub_group, :user4_group]
- [:direct] | :sub_sub_group | [:user1_sub_sub_group, :user2_sub_sub_group, :user3_sub_sub_group, :user4_sub_sub_group]
- [:inherited] | :sub_sub_group | [:user1_sub_group, :user2_group, :user3_sub_group, :user4_group]
- [:descendants] | :sub_sub_group | []
- [:direct, :inherited] | :sub_sub_group | [:user1_sub_sub_group, :user2_group, :user3_sub_group, :user4_group]
- [:direct, :descendants] | :sub_sub_group | [:user1_sub_sub_group, :user2_sub_sub_group, :user3_sub_sub_group, :user4_sub_sub_group]
- [:descendants, :inherited] | :sub_sub_group | [:user1_sub_group, :user2_group, :user3_sub_group, :user4_group]
- [:direct, :descendants, :inherited] | :sub_sub_group | [:user1_sub_sub_group, :user2_group, :user3_sub_group, :user4_group]
+ [] | :group | []
+ GroupMembersFinder::DEFAULT_RELATIONS | :group | [:user1_group, :user2_group, :user3_group, :user4_group]
+ [:direct] | :group | [:user1_group, :user2_group, :user3_group, :user4_group]
+ [:inherited] | :group | []
+ [:descendants] | :group | [:user1_sub_sub_group, :user2_sub_group, :user3_sub_group, :user4_sub_group]
+ [:shared_from_groups] | :group | [:user1_public_shared_group, :user2_public_shared_group, :user3_public_shared_group, :user4_public_shared_group]
+ [:direct, :inherited, :descendants, :shared_from_groups] | :group | [:user1_sub_sub_group, :user2_group, :user3_sub_group, :user4_public_shared_group]
+ [] | :sub_group | []
+ GroupMembersFinder::DEFAULT_RELATIONS | :sub_group | [:user1_sub_group, :user2_group, :user3_sub_group, :user4_group]
+ [:direct] | :sub_group | [:user1_sub_group, :user2_sub_group, :user3_sub_group, :user4_sub_group]
+ [:inherited] | :sub_group | [:user1_group, :user2_group, :user3_group, :user4_group]
+ [:descendants] | :sub_group | [:user1_sub_sub_group, :user2_sub_sub_group, :user3_sub_sub_group, :user4_sub_sub_group]
+ [:shared_from_groups] | :sub_group | []
+ [:direct, :inherited, :descendants, :shared_from_groups] | :sub_group | [:user1_sub_sub_group, :user2_group, :user3_sub_group, :user4_group]
+ [] | :sub_sub_group | []
+ GroupMembersFinder::DEFAULT_RELATIONS | :sub_sub_group | [:user1_sub_sub_group, :user2_group, :user3_sub_group, :user4_group]
+ [:direct] | :sub_sub_group | [:user1_sub_sub_group, :user2_sub_sub_group, :user3_sub_sub_group, :user4_sub_sub_group]
+ [:inherited] | :sub_sub_group | [:user1_sub_group, :user2_group, :user3_sub_group, :user4_group]
+ [:descendants] | :sub_sub_group | []
+ [:shared_from_groups] | :sub_sub_group | []
+ [:direct, :inherited, :descendants, :shared_from_groups] | :sub_sub_group | [:user1_sub_sub_group, :user2_group, :user3_sub_group, :user4_group]
end
with_them do
it 'returns correct members' do
- result = if subject_relations
- described_class.new(groups[subject_group]).execute(include_relations: subject_relations)
- else
- described_class.new(groups[subject_group]).execute
- end
+ result = described_class.new(groups[subject_group]).execute(include_relations: subject_relations)
expect(result.to_a).to match_array(expected_members.map { |name| members[name] })
end
diff --git a/spec/frontend/vue_mr_widget/components/terraform/mock_data.js b/spec/frontend/vue_mr_widget/components/terraform/mock_data.js
index ae280146c22..8e46af5dfd6 100644
--- a/spec/frontend/vue_mr_widget/components/terraform/mock_data.js
+++ b/spec/frontend/vue_mr_widget/components/terraform/mock_data.js
@@ -1,6 +1,6 @@
export const invalidPlanWithName = {
job_name: 'Invalid Plan',
- job_path: '/path/to/ci/logs/1',
+ job_path: '/path/to/ci/logs/3',
tf_report_error: 'api_error',
};
@@ -20,12 +20,12 @@ export const validPlanWithoutName = {
create: 10,
update: 20,
delete: 30,
- job_path: '/path/to/ci/logs/1',
+ job_path: '/path/to/ci/logs/2',
};
export const plans = {
invalid_plan_one: invalidPlanWithName,
- invalid_plan_two: invalidPlanWithName,
+ invalid_plan_two: invalidPlanWithoutName,
valid_plan_one: validPlanWithName,
valid_plan_two: validPlanWithoutName,
};
diff --git a/spec/frontend/vue_mr_widget/extentions/terraform/index_spec.js b/spec/frontend/vue_mr_widget/extentions/terraform/index_spec.js
new file mode 100644
index 00000000000..f8ea6fc23a2
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/extentions/terraform/index_spec.js
@@ -0,0 +1,178 @@
+import MockAdapter from 'axios-mock-adapter';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import axios from '~/lib/utils/axios_utils';
+import Poll from '~/lib/utils/poll';
+import extensionsContainer from '~/vue_merge_request_widget/components/extensions/container';
+import { registerExtension } from '~/vue_merge_request_widget/components/extensions';
+import terraformExtension from '~/vue_merge_request_widget/extensions/terraform';
+import {
+ plans,
+ validPlanWithName,
+ validPlanWithoutName,
+ invalidPlanWithName,
+ invalidPlanWithoutName,
+} from '../../components/terraform/mock_data';
+
+describe('Terraform extension', () => {
+ let wrapper;
+ let mock;
+
+ const endpoint = '/path/to/terraform/report.json';
+ const successStatusCode = 200;
+ const errorStatusCode = 500;
+
+ const findListItem = (at) => wrapper.findAllByTestId('extension-list-item').at(at);
+
+ registerExtension(terraformExtension);
+
+ const mockPollingApi = (response, body, header) => {
+ mock.onGet(endpoint).reply(response, body, header);
+ };
+
+ const createComponent = () => {
+ wrapper = mountExtended(extensionsContainer, {
+ propsData: {
+ mr: {
+ terraformReportsPath: endpoint,
+ },
+ },
+ });
+ return axios.waitForAll();
+ };
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ mock.restore();
+ });
+
+ describe('summary', () => {
+ describe('while loading', () => {
+ const loadingText = 'Loading Terraform reports...';
+ it('should render loading text', async () => {
+ mockPollingApi(successStatusCode, plans, {});
+ createComponent();
+
+ expect(wrapper.text()).toContain(loadingText);
+ await waitForPromises();
+ expect(wrapper.text()).not.toContain(loadingText);
+ });
+ });
+
+ describe('when the fetching fails', () => {
+ beforeEach(() => {
+ mockPollingApi(errorStatusCode, null, {});
+ return createComponent();
+ });
+
+ it('should generate one invalid plan and render correct summary text', () => {
+ expect(wrapper.text()).toContain('1 Terraform report failed to generate');
+ });
+ });
+
+ describe('when the fetching succeeds', () => {
+ describe.each`
+ responseType | response | summaryTitle | summarySubtitle
+ ${'1 invalid report'} | ${{ 0: invalidPlanWithName }} | ${'1 Terraform report failed to generate'} | ${''}
+ ${'2 valid reports'} | ${{ 0: validPlanWithName, 1: validPlanWithName }} | ${'2 Terraform reports were generated in your pipelines'} | ${''}
+ ${'1 valid and 2 invalid reports'} | ${{ 0: validPlanWithName, 1: invalidPlanWithName, 2: invalidPlanWithName }} | ${'Terraform report was generated in your pipelines'} | ${'2 Terraform reports failed to generate'}
+ `('and received $responseType', ({ response, summaryTitle, summarySubtitle }) => {
+ beforeEach(async () => {
+ mockPollingApi(successStatusCode, response, {});
+ return createComponent();
+ });
+
+ it(`should render correct summary text`, () => {
+ expect(wrapper.text()).toContain(summaryTitle);
+
+ if (summarySubtitle) {
+ expect(wrapper.text()).toContain(summarySubtitle);
+ }
+ });
+ });
+ });
+ });
+
+ describe('expanded data', () => {
+ beforeEach(async () => {
+ mockPollingApi(successStatusCode, plans, {});
+ await createComponent();
+
+ wrapper.findByTestId('toggle-button').trigger('click');
+ });
+
+ describe.each`
+ reportType | title | subtitle | logLink | lineNumber
+ ${'a valid report with name'} | ${`The job ${validPlanWithName.job_name} generated a report.`} | ${`Reported Resource Changes: ${validPlanWithName.create} to add, ${validPlanWithName.update} to change, ${validPlanWithName.delete} to delete`} | ${validPlanWithName.job_path} | ${0}
+ ${'a valid report without name'} | ${'A Terraform report was generated in your pipelines.'} | ${`Reported Resource Changes: ${validPlanWithoutName.create} to add, ${validPlanWithoutName.update} to change, ${validPlanWithoutName.delete} to delete`} | ${validPlanWithoutName.job_path} | ${1}
+ ${'an invalid report with name'} | ${`The job ${invalidPlanWithName.job_name} failed to generate a report.`} | ${'Generating the report caused an error.'} | ${invalidPlanWithName.job_path} | ${2}
+ ${'an invalid report without name'} | ${'A Terraform report failed to generate.'} | ${'Generating the report caused an error.'} | ${invalidPlanWithoutName.job_path} | ${3}
+ `('renders correct text for $reportType', ({ title, subtitle, logLink, lineNumber }) => {
+ it('renders correct text', () => {
+ expect(findListItem(lineNumber).text()).toContain(title);
+ expect(findListItem(lineNumber).text()).toContain(subtitle);
+ });
+
+ it(`${logLink ? 'renders' : "doesn't render"} the log link`, () => {
+ const logText = 'Full log';
+ if (logLink) {
+ expect(
+ findListItem(lineNumber)
+ .find('[data-testid="extension-actions-button"]')
+ .attributes('href'),
+ ).toBe(logLink);
+ } else {
+ expect(findListItem(lineNumber).text()).not.toContain(logText);
+ }
+ });
+ });
+ });
+
+ describe('polling', () => {
+ let pollRequest;
+ let pollStop;
+
+ beforeEach(() => {
+ pollRequest = jest.spyOn(Poll.prototype, 'makeRequest');
+ pollStop = jest.spyOn(Poll.prototype, 'stop');
+ });
+
+ afterEach(() => {
+ pollRequest.mockRestore();
+ pollStop.mockRestore();
+ });
+
+ describe('successful poll', () => {
+ beforeEach(() => {
+ mockPollingApi(successStatusCode, plans, {});
+
+ return createComponent();
+ });
+
+ it('does not make additional requests after poll is successful', () => {
+ expect(pollRequest).toHaveBeenCalledTimes(1);
+ expect(pollStop).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('polling fails', () => {
+ beforeEach(() => {
+ mockPollingApi(errorStatusCode, null, {});
+ return createComponent();
+ });
+
+ it('generates one broken plan', () => {
+ expect(wrapper.text()).toContain('1 Terraform report failed to generate');
+ });
+
+ it('does not make additional requests after poll is unsuccessful', () => {
+ expect(pollRequest).toHaveBeenCalledTimes(1);
+ expect(pollStop).toHaveBeenCalledTimes(1);
+ });
+ });
+ });
+});
diff --git a/spec/graphql/types/group_member_relation_enum_spec.rb b/spec/graphql/types/group_member_relation_enum_spec.rb
index 315809ef75e..89ee8c574c4 100644
--- a/spec/graphql/types/group_member_relation_enum_spec.rb
+++ b/spec/graphql/types/group_member_relation_enum_spec.rb
@@ -6,6 +6,6 @@ RSpec.describe Types::GroupMemberRelationEnum do
specify { expect(described_class.graphql_name).to eq('GroupMemberRelation') }
it 'exposes all the existing group member relation type values' do
- expect(described_class.values.keys).to contain_exactly('DIRECT', 'INHERITED', 'DESCENDANTS')
+ expect(described_class.values.keys).to contain_exactly('DIRECT', 'INHERITED', 'DESCENDANTS', 'SHARED_FROM_GROUPS')
end
end
diff --git a/spec/lib/api/helpers/rate_limiter_spec.rb b/spec/lib/api/helpers/rate_limiter_spec.rb
new file mode 100644
index 00000000000..2fed1cf3604
--- /dev/null
+++ b/spec/lib/api/helpers/rate_limiter_spec.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Helpers::RateLimiter do
+ let(:key) { :some_key }
+ let(:scope) { [:some, :scope] }
+ let(:request) { instance_double('Rack::Request') }
+ let(:user) { build_stubbed(:user) }
+
+ let(:api_class) do
+ Class.new do
+ include API::Helpers::RateLimiter
+
+ attr_reader :request, :current_user
+
+ def initialize(request, current_user)
+ @request = request
+ @current_user = current_user
+ end
+
+ def render_api_error!(**args)
+ end
+ end
+ end
+
+ subject { api_class.new(request, user) }
+
+ before do
+ allow(::Gitlab::ApplicationRateLimiter).to receive(:throttled?)
+ allow(::Gitlab::ApplicationRateLimiter).to receive(:log_request)
+ end
+
+ describe '#check_rate_limit!' do
+ it 'calls ApplicationRateLimiter#throttled? with the right arguments' do
+ expect(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).with(key, scope: scope).and_return(false)
+ expect(subject).not_to receive(:render_api_error!)
+
+ subject.check_rate_limit!(key, scope: scope)
+ end
+
+ it 'renders api error and logs request if throttled' do
+ expect(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).with(key, scope: scope).and_return(true)
+ expect(::Gitlab::ApplicationRateLimiter).to receive(:log_request).with(request, "#{key}_request_limit".to_sym, user)
+ expect(subject).to receive(:render_api_error!).with({ error: _('This endpoint has been requested too many times. Try again later.') }, 429)
+
+ subject.check_rate_limit!(key, scope: scope)
+ end
+
+ context 'when the bypass header is set' do
+ before do
+ allow(Gitlab::Throttle).to receive(:bypass_header).and_return('SOME_HEADER')
+ end
+
+ it 'skips rate limit if set to "1"' do
+ allow(request).to receive(:get_header).with(Gitlab::Throttle.bypass_header).and_return('1')
+
+ expect(::Gitlab::ApplicationRateLimiter).not_to receive(:throttled?)
+ expect(subject).not_to receive(:render_api_error!)
+
+ subject.check_rate_limit!(key, scope: scope)
+ end
+
+ it 'does not skip rate limit if set to something else than "1"' do
+ allow(request).to receive(:get_header).with(Gitlab::Throttle.bypass_header).and_return('0')
+
+ expect(::Gitlab::ApplicationRateLimiter).to receive(:throttled?)
+
+ subject.check_rate_limit!(key, scope: scope)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/samplers/action_cable_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/action_cable_sampler_spec.rb
index d834b796179..e1e4877cd50 100644
--- a/spec/lib/gitlab/metrics/samplers/action_cable_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/action_cable_sampler_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Metrics::Samplers::ActionCableSampler do
let(:action_cable) { instance_double(ActionCable::Server::Base) }
- subject { described_class.new(action_cable: action_cable) }
+ subject { described_class.new(action_cable: action_cable, logger: double) }
it_behaves_like 'metrics sampler', 'ACTION_CABLE_SAMPLER'
diff --git a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
index 6f1e0480197..a4877208bcf 100644
--- a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
@@ -84,7 +84,7 @@ RSpec.describe Gitlab::Metrics::Samplers::RubySampler do
end
describe '#sample_gc' do
- let!(:sampler) { described_class.new(5) }
+ let!(:sampler) { described_class.new }
let(:gc_reports) { [{ GC_TIME: 0.1 }, { GC_TIME: 0.2 }, { GC_TIME: 0.3 }] }
diff --git a/spec/lib/gitlab_edition_spec.rb b/spec/lib/gitlab_edition_spec.rb
new file mode 100644
index 00000000000..2f1316819ec
--- /dev/null
+++ b/spec/lib/gitlab_edition_spec.rb
@@ -0,0 +1,160 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabEdition do
+ before do
+ # Make sure the ENV is clean
+ stub_env('FOSS_ONLY', nil)
+ stub_env('EE_ONLY', nil)
+
+ described_class.instance_variable_set(:@is_ee, nil)
+ described_class.instance_variable_set(:@is_jh, nil)
+ end
+
+ after do
+ described_class.instance_variable_set(:@is_ee, nil)
+ described_class.instance_variable_set(:@is_jh, nil)
+ end
+
+ describe '.root' do
+ it 'returns the root path of the app' do
+ expect(described_class.root).to eq(Pathname.new(File.expand_path('../..', __dir__)))
+ end
+ end
+
+ describe 'extensions' do
+ context 'when .jh? is true' do
+ before do
+ allow(described_class).to receive(:jh?).and_return(true)
+ end
+
+ it 'returns %w[ee jh]' do
+ expect(described_class.extensions).to match_array(%w[ee jh])
+ end
+ end
+
+ context 'when .ee? is true' do
+ before do
+ allow(described_class).to receive(:jh?).and_return(false)
+ allow(described_class).to receive(:ee?).and_return(true)
+ end
+
+ it 'returns %w[ee]' do
+ expect(described_class.extensions).to match_array(%w[ee])
+ end
+ end
+
+ context 'when neither .jh? and .ee? are true' do
+ before do
+ allow(described_class).to receive(:jh?).and_return(false)
+ allow(described_class).to receive(:ee?).and_return(false)
+ end
+
+ it 'returns the exyensions according to the current edition' do
+ expect(described_class.extensions).to be_empty
+ end
+ end
+ end
+
+ describe '.ee? and .jh?' do
+ def stub_path(*paths, **arguments)
+ root = Pathname.new('dummy')
+ pathname = double(:path, **arguments)
+
+ allow(described_class)
+ .to receive(:root)
+ .and_return(root)
+
+ allow(root).to receive(:join)
+
+ paths.each do |path|
+ allow(root)
+ .to receive(:join)
+ .with(path)
+ .and_return(pathname)
+ end
+ end
+
+ describe '.ee?' do
+ context 'for EE' do
+ before do
+ stub_path('ee/app/models/license.rb', exist?: true)
+ end
+
+ context 'when using FOSS_ONLY=1' do
+ before do
+ stub_env('FOSS_ONLY', '1')
+ end
+
+ it 'returns not to be EE' do
+ expect(described_class).not_to be_ee
+ end
+ end
+
+ context 'when using FOSS_ONLY=0' do
+ before do
+ stub_env('FOSS_ONLY', '0')
+ end
+
+ it 'returns to be EE' do
+ expect(described_class).to be_ee
+ end
+ end
+
+ context 'when using default FOSS_ONLY' do
+ it 'returns to be EE' do
+ expect(described_class).to be_ee
+ end
+ end
+ end
+
+ context 'for CE' do
+ before do
+ stub_path('ee/app/models/license.rb', exist?: false)
+ end
+
+ it 'returns not to be EE' do
+ expect(described_class).not_to be_ee
+ end
+ end
+ end
+
+ describe '.jh?' do
+ context 'for JH' do
+ before do
+ stub_path(
+ 'ee/app/models/license.rb',
+ 'jh',
+ exist?: true)
+ end
+
+ context 'when using default FOSS_ONLY and EE_ONLY' do
+ it 'returns to be JH' do
+ expect(described_class).to be_jh
+ end
+ end
+
+ context 'when using FOSS_ONLY=1' do
+ before do
+ stub_env('FOSS_ONLY', '1')
+ end
+
+ it 'returns not to be JH' do
+ expect(described_class).not_to be_jh
+ end
+ end
+
+ context 'when using EE_ONLY=1' do
+ before do
+ stub_env('EE_ONLY', '1')
+ end
+
+ it 'returns not to be JH' do
+ expect(described_class).not_to be_jh
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb
index 869eaf26772..49ba4debe31 100644
--- a/spec/lib/gitlab_spec.rb
+++ b/spec/lib/gitlab_spec.rb
@@ -3,9 +3,19 @@
require 'spec_helper'
RSpec.describe Gitlab do
- describe '.root' do
- it 'returns the root path of the app' do
- expect(described_class.root).to eq(Pathname.new(File.expand_path('../..', __dir__)))
+ %w[root extensions ee? jh?].each do |method_name|
+ it "delegates #{method_name} to GitlabEdition" do
+ expect(GitlabEdition).to receive(method_name)
+
+ described_class.public_send(method_name)
+ end
+ end
+
+ %w[ee jh].each do |method_name|
+ it "delegates #{method_name} to GitlabEdition" do
+ expect(GitlabEdition).to receive(method_name)
+
+ described_class.public_send(method_name) {}
end
end
@@ -248,121 +258,6 @@ RSpec.describe Gitlab do
end
end
- describe 'ee? and jh?' do
- before do
- # Make sure the ENV is clean
- stub_env('FOSS_ONLY', nil)
- stub_env('EE_ONLY', nil)
-
- described_class.instance_variable_set(:@is_ee, nil)
- described_class.instance_variable_set(:@is_jh, nil)
- end
-
- after do
- described_class.instance_variable_set(:@is_ee, nil)
- described_class.instance_variable_set(:@is_jh, nil)
- end
-
- def stub_path(*paths, **arguments)
- root = Pathname.new('dummy')
- pathname = double(:path, **arguments)
-
- allow(described_class)
- .to receive(:root)
- .and_return(root)
-
- allow(root).to receive(:join)
-
- paths.each do |path|
- allow(root)
- .to receive(:join)
- .with(path)
- .and_return(pathname)
- end
- end
-
- describe '.ee?' do
- context 'for EE' do
- before do
- stub_path('ee/app/models/license.rb', exist?: true)
- end
-
- context 'when using FOSS_ONLY=1' do
- before do
- stub_env('FOSS_ONLY', '1')
- end
-
- it 'returns not to be EE' do
- expect(described_class).not_to be_ee
- end
- end
-
- context 'when using FOSS_ONLY=0' do
- before do
- stub_env('FOSS_ONLY', '0')
- end
-
- it 'returns to be EE' do
- expect(described_class).to be_ee
- end
- end
-
- context 'when using default FOSS_ONLY' do
- it 'returns to be EE' do
- expect(described_class).to be_ee
- end
- end
- end
-
- context 'for CE' do
- before do
- stub_path('ee/app/models/license.rb', exist?: false)
- end
-
- it 'returns not to be EE' do
- expect(described_class).not_to be_ee
- end
- end
- end
-
- describe '.jh?' do
- context 'for JH' do
- before do
- stub_path(
- 'ee/app/models/license.rb',
- 'jh',
- exist?: true)
- end
-
- context 'when using default FOSS_ONLY and EE_ONLY' do
- it 'returns to be JH' do
- expect(described_class).to be_jh
- end
- end
-
- context 'when using FOSS_ONLY=1' do
- before do
- stub_env('FOSS_ONLY', '1')
- end
-
- it 'returns not to be JH' do
- expect(described_class).not_to be_jh
- end
- end
-
- context 'when using EE_ONLY=1' do
- before do
- stub_env('EE_ONLY', '1')
- end
-
- it 'returns not to be JH' do
- expect(described_class).not_to be_jh
- end
- end
- end
- end
- end
-
describe '.http_proxy_env?' do
it 'returns true when lower case https' do
stub_env('https_proxy', 'https://my.proxy')
diff --git a/spec/lib/sidebars/projects/panel_spec.rb b/spec/lib/sidebars/projects/panel_spec.rb
index 2e79ced7039..7e69a2dfe52 100644
--- a/spec/lib/sidebars/projects/panel_spec.rb
+++ b/spec/lib/sidebars/projects/panel_spec.rb
@@ -3,7 +3,8 @@
require 'spec_helper'
RSpec.describe Sidebars::Projects::Panel do
- let(:project) { build(:project) }
+ let_it_be(:project) { create(:project) }
+
let(:context) { Sidebars::Projects::Context.new(current_user: nil, container: project) }
subject { described_class.new(context) }
diff --git a/spec/metrics_server/metrics_server_spec.rb b/spec/metrics_server/metrics_server_spec.rb
index 4e3c6900875..4f0d5bc5bb2 100644
--- a/spec/metrics_server/metrics_server_spec.rb
+++ b/spec/metrics_server/metrics_server_spec.rb
@@ -8,18 +8,24 @@ require_relative '../support/helpers/next_instance_of'
RSpec.describe MetricsServer do # rubocop:disable RSpec/FilePath
include NextInstanceOf
+ let(:metrics_dir) { Dir.mktmpdir }
+
before do
# We do not want this to have knock-on effects on the test process.
allow(Gitlab::ProcessManagement).to receive(:modify_signals)
end
+ after do
+ FileUtils.rm_rf(metrics_dir, secure: true)
+ end
+
describe '.spawn' do
context 'when in parent process' do
it 'forks into a new process and detaches it' do
expect(Process).to receive(:fork).and_return(99)
expect(Process).to receive(:detach).with(99)
- described_class.spawn('sidekiq', metrics_dir: 'path/to/metrics')
+ described_class.spawn('sidekiq', metrics_dir: metrics_dir)
end
end
@@ -35,13 +41,13 @@ RSpec.describe MetricsServer do # rubocop:disable RSpec/FilePath
expect(server).to receive(:start)
end
- described_class.spawn('sidekiq', metrics_dir: 'path/to/metrics')
+ described_class.spawn('sidekiq', metrics_dir: metrics_dir)
end
it 'resets signal handlers from parent process' do
expect(Gitlab::ProcessManagement).to receive(:modify_signals).with(%i[A B], 'DEFAULT')
- described_class.spawn('sidekiq', metrics_dir: 'path/to/metrics', trapped_signals: %i[A B])
+ described_class.spawn('sidekiq', metrics_dir: metrics_dir, trapped_signals: %i[A B])
end
end
end
@@ -50,9 +56,9 @@ RSpec.describe MetricsServer do # rubocop:disable RSpec/FilePath
let(:exporter_class) { Class.new(Gitlab::Metrics::Exporter::BaseExporter) }
let(:exporter_double) { double('fake_exporter', start: true) }
let(:prometheus_config) { ::Prometheus::Client.configuration }
- let(:metrics_dir) { Dir.mktmpdir }
let(:settings) { { "fake_exporter" => { "enabled" => true } } }
let!(:old_metrics_dir) { prometheus_config.multiprocess_files_dir }
+ let(:ruby_sampler_double) { double(Gitlab::Metrics::Samplers::RubySampler) }
subject(:metrics_server) { described_class.new('fake', metrics_dir, true)}
@@ -60,11 +66,13 @@ RSpec.describe MetricsServer do # rubocop:disable RSpec/FilePath
stub_const('Gitlab::Metrics::Exporter::FakeExporter', exporter_class)
expect(exporter_class).to receive(:instance).with(settings['fake_exporter'], synchronous: true).and_return(exporter_double)
expect(Settings).to receive(:monitoring).and_return(settings)
+
+ allow(Gitlab::Metrics::Samplers::RubySampler).to receive(:initialize_instance).and_return(ruby_sampler_double)
+ allow(ruby_sampler_double).to receive(:start)
end
after do
Gitlab::Metrics.reset_registry!
- FileUtils.rm_rf(metrics_dir, secure: true)
prometheus_config.multiprocess_files_dir = old_metrics_dir
end
@@ -72,6 +80,7 @@ RSpec.describe MetricsServer do # rubocop:disable RSpec/FilePath
metrics_server.start
expect(prometheus_config.multiprocess_files_dir).to eq metrics_dir
+ expect(::Prometheus::Client.configuration.pid_provider.call).to eq 'fake_exporter'
end
it 'ensures that metrics directory exists in correct mode (0700)' do
@@ -105,5 +114,11 @@ RSpec.describe MetricsServer do # rubocop:disable RSpec/FilePath
metrics_server.start
end
+
+ it 'starts a RubySampler instance' do
+ expect(ruby_sampler_double).to receive(:start)
+
+ subject.start
+ end
end
end
diff --git a/spec/models/integrations/jira_spec.rb b/spec/models/integrations/jira_spec.rb
index 9163a7ef845..e80fa6e3b70 100644
--- a/spec/models/integrations/jira_spec.rb
+++ b/spec/models/integrations/jira_spec.rb
@@ -937,18 +937,6 @@ RSpec.describe Integrations::Jira do
end
end
- context 'with jira_use_first_ref_by_oid feature flag disabled' do
- before do
- stub_feature_flags(jira_use_first_ref_by_oid: false)
- end
-
- it 'creates a comment and remote link on Jira' do
- expect(subject).to eq(success_message)
- expect(WebMock).to have_requested(:post, comment_url).with(body: comment_body).once
- expect(WebMock).to have_requested(:post, remote_link_url).once
- end
- end
-
it 'tracks usage' do
expect(Gitlab::UsageDataCounters::HLLRedisCounter)
.to receive(:track_event)
diff --git a/spec/requests/api/graphql/group/group_members_spec.rb b/spec/requests/api/graphql/group/group_members_spec.rb
index 31cb0393d7f..06afb5b9a49 100644
--- a/spec/requests/api/graphql/group/group_members_spec.rb
+++ b/spec/requests/api/graphql/group/group_members_spec.rb
@@ -56,12 +56,16 @@ RSpec.describe 'getting group members information' do
context 'member relations' do
let_it_be(:child_group) { create(:group, :public, parent: parent_group) }
let_it_be(:grandchild_group) { create(:group, :public, parent: child_group) }
+ let_it_be(:invited_group) { create(:group, :public) }
let_it_be(:child_user) { create(:user) }
let_it_be(:grandchild_user) { create(:user) }
+ let_it_be(:invited_user) { create(:user) }
+ let_it_be(:group_link) { create(:group_group_link, shared_group: child_group, shared_with_group: invited_group) }
before_all do
child_group.add_guest(child_user)
grandchild_group.add_guest(grandchild_user)
+ invited_group.add_guest(invited_user)
end
it 'returns direct members' do
@@ -71,6 +75,13 @@ RSpec.describe 'getting group members information' do
expect_array_response(child_user)
end
+ it 'returns invited members plus inherited members' do
+ fetch_members(group: child_group, args: { relations: [:DIRECT, :INHERITED, :SHARED_FROM_GROUPS] })
+
+ expect(graphql_errors).to be_nil
+ expect_array_response(invited_user, user_1, user_2, child_user)
+ end
+
it 'returns direct and inherited members' do
fetch_members(group: child_group, args: { relations: [:DIRECT, :INHERITED] })
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index b93df2f3bae..0fb0150ecc9 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -498,6 +498,10 @@ RSpec.describe API::Users do
describe "GET /users/:id" do
let_it_be(:user2, reload: true) { create(:user, username: 'another_user') }
+ before do
+ allow(Gitlab::ApplicationRateLimiter).to receive(:throttled?).with(:users_get_by_id, scope: user).and_return(false)
+ end
+
it "returns a user by id" do
get api("/users/#{user.id}", user)
@@ -593,6 +597,55 @@ RSpec.describe API::Users do
expect(json_response).not_to have_key('sign_in_count')
end
+ context 'when the rate limit is not exceeded' do
+ it 'returns a success status' do
+ expect(Gitlab::ApplicationRateLimiter)
+ .to receive(:throttled?).with(:users_get_by_id, scope: user)
+ .and_return(false)
+
+ get api("/users/#{user.id}", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'when the rate limit is exceeded' do
+ context 'when feature flag is enabled' do
+ it 'returns "too many requests" status' do
+ expect(Gitlab::ApplicationRateLimiter)
+ .to receive(:throttled?).with(:users_get_by_id, scope: user)
+ .and_return(true)
+
+ get api("/users/#{user.id}", user)
+
+ expect(response).to have_gitlab_http_status(:too_many_requests)
+ end
+
+ it 'still allows admin users' do
+ expect(Gitlab::ApplicationRateLimiter)
+ .not_to receive(:throttled?)
+
+ get api("/users/#{user.id}", admin)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'when feature flag is disabled' do
+ before do
+ stub_feature_flags(rate_limit_user_by_id_endpoint: false)
+ end
+
+ it 'does not throttle the request' do
+ expect(Gitlab::ApplicationRateLimiter).not_to receive(:throttled?)
+
+ get api("/users/#{user.id}", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+
context 'when job title is present' do
let(:job_title) { 'Fullstack Engineer' }
diff --git a/spec/rubocop/code_reuse_helpers_spec.rb b/spec/rubocop/code_reuse_helpers_spec.rb
index 3220cff1681..d437ada85ee 100644
--- a/spec/rubocop/code_reuse_helpers_spec.rb
+++ b/spec/rubocop/code_reuse_helpers_spec.rb
@@ -315,76 +315,11 @@ RSpec.describe RuboCop::CodeReuseHelpers do
end
end
- describe '#ee?' do
- before do
- stub_env('FOSS_ONLY', nil)
- allow(File).to receive(:exist?).with(ee_file_path) { true }
- end
-
- it 'returns true when ee/app/models/license.rb exists' do
- expect(cop.ee?).to eq(true)
- end
- end
-
- describe '#jh?' do
- context 'when jh directory exists and EE_ONLY is not set' do
- before do
- stub_env('EE_ONLY', nil)
-
- allow(Dir).to receive(:exist?).with(File.expand_path('../../jh', __dir__)) { true }
- end
-
- context 'when ee/app/models/license.rb exists' do
- before do
- allow(File).to receive(:exist?).with(ee_file_path) { true }
- end
-
- context 'when FOSS_ONLY is not set' do
- before do
- stub_env('FOSS_ONLY', nil)
- end
-
- it 'returns true' do
- expect(cop.jh?).to eq(true)
- end
- end
-
- context 'when FOSS_ONLY is set to 1' do
- before do
- stub_env('FOSS_ONLY', '1')
- end
+ %w[ee? jh?].each do |method_name|
+ it "delegates #{method_name} to GitlabEdition" do
+ expect(GitlabEdition).to receive(method_name)
- it 'returns false' do
- expect(cop.jh?).to eq(false)
- end
- end
- end
-
- context 'when ee/app/models/license.rb not exist' do
- before do
- allow(File).to receive(:exist?).with(ee_file_path) { false }
- end
-
- context 'when FOSS_ONLY is not set' do
- before do
- stub_env('FOSS_ONLY', nil)
- end
-
- it 'returns true' do
- expect(cop.jh?).to eq(false)
- end
- end
-
- context 'when FOSS_ONLY is set to 1' do
- before do
- stub_env('FOSS_ONLY', '1')
- end
-
- it 'returns false' do
- expect(cop.jh?).to eq(false)
- end
- end
- end
+ cop.public_send(method_name)
end
end
end
diff --git a/spec/support/shared_examples/metrics/sampler_shared_examples.rb b/spec/support/shared_examples/metrics/sampler_shared_examples.rb
index ebf199c3a8d..cec540cd120 100644
--- a/spec/support/shared_examples/metrics/sampler_shared_examples.rb
+++ b/spec/support/shared_examples/metrics/sampler_shared_examples.rb
@@ -2,26 +2,98 @@
RSpec.shared_examples 'metrics sampler' do |env_prefix|
context 'when sampling interval is passed explicitly' do
- subject { described_class.new(42) }
+ subject(:sampler) { described_class.new(interval: 42, logger: double) }
- specify { expect(subject.interval).to eq(42) }
+ specify { expect(sampler.interval).to eq(42) }
end
context 'when sampling interval is passed through the environment' do
- subject { described_class.new }
+ subject(:sampler) { described_class.new(logger: double) }
before do
stub_env("#{env_prefix}_INTERVAL_SECONDS", '42')
end
- specify { expect(subject.interval).to eq(42) }
+ specify { expect(sampler.interval).to eq(42) }
end
context 'when no sampling interval is passed anywhere' do
- subject { described_class.new }
+ subject(:sampler) { described_class.new(logger: double) }
it 'uses the hardcoded default' do
- expect(subject.interval).to eq(described_class::DEFAULT_SAMPLING_INTERVAL_SECONDS)
+ expect(sampler.interval).to eq(described_class::DEFAULT_SAMPLING_INTERVAL_SECONDS)
+ end
+ end
+
+ describe '#start' do
+ include WaitHelpers
+
+ subject(:sampler) { described_class.new(interval: 0.1) }
+
+ it 'calls the sample method on the sampler thread' do
+ sampling_threads = []
+ expect(sampler).to receive(:sample).at_least(:once) { sampling_threads << Thread.current }
+
+ sampler.start
+
+ wait_for('sampler has sampled', max_wait_time: 3) { sampling_threads.any? }
+ expect(sampling_threads.first.name).to eq(sampler.thread_name)
+
+ sampler.stop
+ end
+
+ context 'with warmup set to true' do
+ subject(:sampler) { described_class.new(interval: 0.1, warmup: true) }
+
+ it 'calls the sample method first on the caller thread' do
+ sampling_threads = []
+ current_thread = Thread.current
+ # Instead of sampling, we're keeping track of which thread the sampling happened on.
+ # We want the first sample to be on the spec thread, which would mean a blocking sample
+ # before the actual sampler thread starts.
+ expect(sampler).to receive(:sample).at_least(:once) { sampling_threads << Thread.current }
+
+ sampler.start
+
+ wait_for('sampler has sampled', max_wait_time: 3) { sampling_threads.size == 2 }
+
+ expect(sampling_threads.first).to be(current_thread)
+ expect(sampling_threads.last.name).to eq(sampler.thread_name)
+
+ sampler.stop
+ end
+ end
+ end
+
+ describe '#safe_sample' do
+ let(:logger) { Logger.new(File::NULL) }
+
+ subject(:sampler) { described_class.new(logger: logger) }
+
+ it 'calls #sample once' do
+ expect(sampler).to receive(:sample)
+
+ sampler.safe_sample
+ end
+
+ context 'when sampling fails with error' do
+ before do
+ expect(sampler).to receive(:sample).and_raise "something failed"
+ end
+
+ it 'recovers from errors' do
+ expect { sampler.safe_sample }.not_to raise_error
+ end
+
+ context 'with logger' do
+ let(:logger) { double('logger') }
+
+ it 'logs errors' do
+ expect(logger).to receive(:warn).with(an_instance_of(String))
+
+ expect { sampler.safe_sample }.not_to raise_error
+ end
+ end
end
end
end
diff --git a/spec/views/shared/nav/_sidebar.html.haml_spec.rb b/spec/views/shared/nav/_sidebar.html.haml_spec.rb
index 2eeebdff7a8..0eb945f5624 100644
--- a/spec/views/shared/nav/_sidebar.html.haml_spec.rb
+++ b/spec/views/shared/nav/_sidebar.html.haml_spec.rb
@@ -3,7 +3,8 @@
require 'spec_helper'
RSpec.describe 'shared/nav/_sidebar.html.haml' do
- let(:project) { build(:project, id: non_existing_record_id) }
+ let_it_be(:project) { create(:project) }
+
let(:context) { Sidebars::Projects::Context.new(current_user: nil, container: project) }
let(:sidebar) { Sidebars::Projects::Panel.new(context) }