@@ -131,66 +133,60 @@ exports[`Learn GitLab renders correctly 1`] = `
@@ -349,22 +339,20 @@ exports[`Learn GitLab renders correctly 1`] = `
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js
index 882d233a239..f7b2154a935 100644
--- a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js
+++ b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js
@@ -1,4 +1,7 @@
import { shallowMount } from '@vue/test-utils';
+import { stubExperiments } from 'helpers/experimentation_helper';
+import { mockTracking, triggerEvent, unmockTracking } from 'helpers/tracking_helper';
+import eventHub from '~/invite_members/event_hub';
import LearnGitlabSectionLink from '~/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue';
const defaultAction = 'gitWrite';
@@ -23,6 +26,9 @@ describe('Learn GitLab Section Link', () => {
});
};
+ const openInviteMembesrModalLink = () =>
+ wrapper.find('[data-testid="invite-for-help-continuous-onboarding-experiment-link"]');
+
it('renders no icon when not completed', () => {
createWrapper(undefined, { completed: false });
@@ -46,4 +52,54 @@ describe('Learn GitLab Section Link', () => {
expect(wrapper.find('[data-testid="trial-only"]').exists()).toBe(true);
});
+
+ describe('rendering a link to open the invite_members modal instead of a regular link', () => {
+ it.each`
+ action | experimentVariant | showModal
+ ${'userAdded'} | ${'candidate'} | ${true}
+ ${'userAdded'} | ${'control'} | ${false}
+ ${defaultAction} | ${'candidate'} | ${false}
+ ${defaultAction} | ${'control'} | ${false}
+ `(
+ 'when the invite_for_help_continuous_onboarding experiment has variant: $experimentVariant and action is $action, the modal link is shown: $showModal',
+ ({ action, experimentVariant, showModal }) => {
+ stubExperiments({ invite_for_help_continuous_onboarding: experimentVariant });
+ createWrapper(action);
+
+ expect(openInviteMembesrModalLink().exists()).toBe(showModal);
+ },
+ );
+ });
+
+ describe('clicking the link to open the invite_members modal', () => {
+ beforeEach(() => {
+ jest.spyOn(eventHub, '$emit').mockImplementation();
+
+ stubExperiments({ invite_for_help_continuous_onboarding: 'candidate' });
+ createWrapper('userAdded');
+ });
+
+ it('calls the eventHub', () => {
+ openInviteMembesrModalLink().vm.$emit('click');
+
+ expect(eventHub.$emit).toHaveBeenCalledWith('openModal', {
+ inviteeType: 'members',
+ source: 'learn_gitlab',
+ tasksToBeDoneEnabled: true,
+ });
+ });
+
+ it('tracks the click', async () => {
+ const trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
+
+ triggerEvent(openInviteMembesrModalLink().element);
+
+ expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_link', {
+ label: 'Invite your colleagues',
+ property: 'Growth::Activation::Experiment::InviteForHelpContinuousOnboarding',
+ });
+
+ unmockTracking();
+ });
+ });
});
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js
index 7e97a539a99..7e71622770f 100644
--- a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js
+++ b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js
@@ -1,20 +1,35 @@
-import { GlProgressBar } from '@gitlab/ui';
+import { GlProgressBar, GlAlert } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import LearnGitlab from '~/pages/projects/learn_gitlab/components/learn_gitlab.vue';
import eventHub from '~/invite_members/event_hub';
-import { testActions, testSections } from './mock_data';
+import { testActions, testSections, testProject } from './mock_data';
describe('Learn GitLab', () => {
let wrapper;
+ let sidebar;
let inviteMembersOpen = false;
const createWrapper = () => {
wrapper = mount(LearnGitlab, {
- propsData: { actions: testActions, sections: testSections, inviteMembersOpen },
+ propsData: {
+ actions: testActions,
+ sections: testSections,
+ project: testProject,
+ inviteMembersOpen,
+ },
});
};
beforeEach(() => {
+ sidebar = document.createElement('div');
+ sidebar.innerHTML = `
+
+ `;
+ document.body.appendChild(sidebar);
createWrapper();
});
@@ -22,6 +37,7 @@ describe('Learn GitLab', () => {
wrapper.destroy();
wrapper = null;
inviteMembersOpen = false;
+ sidebar.remove();
});
it('renders correctly', () => {
@@ -66,4 +82,26 @@ describe('Learn GitLab', () => {
expect(spy).not.toHaveBeenCalled();
});
});
+
+ describe('when the showSuccessfulInvitationsAlert event is fired', () => {
+ const findAlert = () => wrapper.findComponent(GlAlert);
+
+ beforeEach(() => {
+ eventHub.$emit('showSuccessfulInvitationsAlert');
+ });
+
+ it('displays the successful invitations alert', () => {
+ expect(findAlert().exists()).toBe(true);
+ });
+
+ it('displays a message with the project name', () => {
+ expect(findAlert().text()).toBe(
+ "Your team is growing! You've successfully invited new team members to the test-project project.",
+ );
+ });
+
+ it('modifies the sidebar percentage', () => {
+ expect(sidebar.textContent.trim()).toBe('22%');
+ });
+ });
});
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/mock_data.js b/spec/frontend/pages/projects/learn_gitlab/components/mock_data.js
index 8d6ac737db8..1e633cb7cf5 100644
--- a/spec/frontend/pages/projects/learn_gitlab/components/mock_data.js
+++ b/spec/frontend/pages/projects/learn_gitlab/components/mock_data.js
@@ -57,3 +57,7 @@ export const testSections = {
svg: 'plan.svg',
},
};
+
+export const testProject = {
+ name: 'test-project',
+};
diff --git a/spec/helpers/invite_members_helper_spec.rb b/spec/helpers/invite_members_helper_spec.rb
index 02f0416a17a..8653ef0afcb 100644
--- a/spec/helpers/invite_members_helper_spec.rb
+++ b/spec/helpers/invite_members_helper_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe InviteMembersHelper do
include Devise::Test::ControllerHelpers
let_it_be(:project) { create(:project) }
+ let_it_be(:group) { create(:group, projects: [project]) }
let_it_be(:developer) { create(:user, developer_projects: [project]) }
let(:owner) { project.owner }
@@ -64,48 +65,13 @@ RSpec.describe InviteMembersHelper do
end
context 'tasks_to_be_done' do
+ using RSpec::Parameterized::TableSyntax
+
subject(:output) { helper.common_invite_modal_dataset(source) }
- let_it_be(:source) { project }
-
- before do
- stub_experiments(invite_members_for_task: true)
- end
-
- context 'when not logged in' do
- before do
- allow(helper).to receive(:params).and_return({ open_modal: 'invite_members_for_task' })
- end
-
- it "doesn't have the tasks to be done attributes" do
- expect(output[:tasks_to_be_done_options]).to be_nil
- expect(output[:projects]).to be_nil
- expect(output[:new_project_path]).to be_nil
- end
- end
-
- context 'when logged in but the open_modal param is not present' do
- before do
- allow(helper).to receive(:current_user).and_return(developer)
- end
-
- it "doesn't have the tasks to be done attributes" do
- expect(output[:tasks_to_be_done_options]).to be_nil
- expect(output[:projects]).to be_nil
- expect(output[:new_project_path]).to be_nil
- end
- end
-
- context 'when logged in and the open_modal param is present' do
- before do
- allow(helper).to receive(:current_user).and_return(developer)
- allow(helper).to receive(:params).and_return({ open_modal: 'invite_members_for_task' })
- end
-
- context 'for a group' do
- let_it_be(:source) { create(:group, projects: [project]) }
-
- it 'has the expected attributes', :aggregate_failures do
+ shared_examples_for 'including the tasks to be done attributes' do
+ it 'includes the tasks to be done attributes when expected' do
+ if expected?
expect(output[:tasks_to_be_done_options]).to eq(
[
{ value: :code, text: 'Create/import code into a project (repository)' },
@@ -117,24 +83,75 @@ RSpec.describe InviteMembersHelper do
[{ id: project.id, title: project.title }].to_json
)
expect(output[:new_project_path]).to eq(
- new_project_path(namespace_id: source.id)
+ source.is_a?(Project) ? '' : new_project_path(namespace_id: group.id)
)
+ else
+ expect(output[:tasks_to_be_done_options]).to be_nil
+ expect(output[:projects]).to be_nil
+ expect(output[:new_project_path]).to be_nil
end
end
+ end
- context 'for a project' do
- it 'has the expected attributes', :aggregate_failures do
- expect(output[:tasks_to_be_done_options]).to eq(
- [
- { value: :code, text: 'Create/import code into a project (repository)' },
- { value: :ci, text: 'Set up CI/CD pipelines to build, test, deploy, and monitor code' },
- { value: :issues, text: 'Create/import issues (tickets) to collaborate on ideas and plan work' }
- ].to_json
- )
- expect(output[:projects]).to eq(
- [{ id: project.id, title: project.title }].to_json
- )
- expect(output[:new_project_path]).to eq('')
+ context 'the invite_members_for_task experiment' do
+ where(:invite_members_for_task_enabled?, :open_modal_param_present?, :logged_in?, :expected?) do
+ true | true | true | true
+ true | true | false | false
+ true | false | true | false
+ true | false | false | false
+ false | true | true | false
+ false | true | false | false
+ false | false | true | false
+ false | false | false | false
+ end
+
+ with_them do
+ before do
+ allow(helper).to receive(:current_user).and_return(developer) if logged_in?
+ stub_experiments(invite_members_for_task: true) if invite_members_for_task_enabled?
+ allow(helper).to receive(:params).and_return({ open_modal: 'invite_members_for_task' }) if open_modal_param_present?
+ end
+
+ context 'when the source is a project' do
+ let_it_be(:source) { project }
+
+ it_behaves_like 'including the tasks to be done attributes'
+ end
+
+ context 'when the source is a group' do
+ let_it_be(:source) { group }
+
+ it_behaves_like 'including the tasks to be done attributes'
+ end
+ end
+ end
+
+ context 'the invite_for_help_continuous_onboarding experiment' do
+ where(:invite_for_help_continuous_onboarding?, :logged_in?, :expected?) do
+ true | true | true
+ true | false | false
+ false | true | false
+ false | false | false
+ end
+
+ with_them do
+ before do
+ allow(helper).to receive(:current_user).and_return(developer) if logged_in?
+ stub_experiments(invite_for_help_continuous_onboarding: :candidate) if invite_for_help_continuous_onboarding?
+ end
+
+ context 'when the source is a project' do
+ let_it_be(:source) { project }
+
+ it_behaves_like 'including the tasks to be done attributes'
+ end
+
+ context 'when the source is a group' do
+ let_it_be(:source) { group }
+
+ let(:expected?) { false }
+
+ it_behaves_like 'including the tasks to be done attributes'
end
end
end
diff --git a/spec/helpers/learn_gitlab_helper_spec.rb b/spec/helpers/learn_gitlab_helper_spec.rb
index b9f34853a77..9d13fc65de7 100644
--- a/spec/helpers/learn_gitlab_helper_spec.rb
+++ b/spec/helpers/learn_gitlab_helper_spec.rb
@@ -60,6 +60,7 @@ RSpec.describe LearnGitlabHelper do
let(:onboarding_actions_data) { Gitlab::Json.parse(learn_gitlab_data[:actions]).deep_symbolize_keys }
let(:onboarding_sections_data) { Gitlab::Json.parse(learn_gitlab_data[:sections]).deep_symbolize_keys }
+ let(:onboarding_project_data) { Gitlab::Json.parse(learn_gitlab_data[:project]).deep_symbolize_keys }
shared_examples 'has all data' do
it 'has all actions' do
@@ -82,6 +83,11 @@ RSpec.describe LearnGitlabHelper do
expect(onboarding_sections_data.keys).to contain_exactly(:deploy, :plan, :workspace)
expect(onboarding_sections_data.values.map { |section| section.keys }).to match_array([[:svg]] * 3)
end
+
+ it 'has all project data', :aggregate_failures do
+ expect(onboarding_project_data.keys).to contain_exactly(:name)
+ expect(onboarding_project_data.values).to match_array([project.name])
+ end
end
it_behaves_like 'has all data'
diff --git a/spec/lib/gitlab/grape_logging/loggers/exception_logger_spec.rb b/spec/lib/gitlab/grape_logging/loggers/exception_logger_spec.rb
index 3ce09740ec8..968d938a911 100644
--- a/spec/lib/gitlab/grape_logging/loggers/exception_logger_spec.rb
+++ b/spec/lib/gitlab/grape_logging/loggers/exception_logger_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::GrapeLogging::Loggers::ExceptionLogger do
- let(:mock_request) { OpenStruct.new(env: {}) }
+ let(:mock_request) { double('env', env: {}) }
let(:response_body) { nil }
describe ".parameters" do
@@ -76,7 +76,7 @@ RSpec.describe Gitlab::GrapeLogging::Loggers::ExceptionLogger do
describe 'when an exception is available' do
let(:exception) { RuntimeError.new('This is a test') }
let(:mock_request) do
- OpenStruct.new(
+ double('env',
env: {
::API::Helpers::API_EXCEPTION_ENV => exception
}
diff --git a/spec/models/u2f_registration_spec.rb b/spec/models/u2f_registration_spec.rb
index 7a70cf69566..6bb9ccfcf35 100644
--- a/spec/models/u2f_registration_spec.rb
+++ b/spec/models/u2f_registration_spec.rb
@@ -20,9 +20,9 @@ RSpec.describe U2fRegistration do
describe '#create_webauthn_registration' do
shared_examples_for 'creates webauthn registration' do
it 'creates webauthn registration' do
- u2f_registration.save!
+ created_record = u2f_registration
- webauthn_registration = WebauthnRegistration.where(u2f_registration_id: u2f_registration.id)
+ webauthn_registration = WebauthnRegistration.where(u2f_registration_id: created_record.id)
expect(webauthn_registration).to exist
end
end
@@ -43,13 +43,16 @@ RSpec.describe U2fRegistration do
it 'logs error' do
allow(Gitlab::Auth::U2fWebauthnConverter).to receive(:new).and_raise('boom!')
- expect(Gitlab::AppJsonLogger).to(
- receive(:error).with(a_hash_including(event: 'u2f_migration',
- error: 'RuntimeError',
- message: 'U2F to WebAuthn conversion failed'))
- )
- u2f_registration.save!
+ allow_next_instance_of(U2fRegistration) do |u2f_registration|
+ allow(u2f_registration).to receive(:id).and_return(123)
+ end
+
+ expect(Gitlab::ErrorTracking).to(
+ receive(:track_exception).with(kind_of(StandardError),
+ u2f_registration_id: 123))
+
+ u2f_registration
end
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index b5d4614d206..8c91ee2d29f 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -3673,309 +3673,321 @@ RSpec.describe User do
end
describe '#ci_owned_runners' do
- let(:user) { create(:user) }
+ shared_examples 'ci_owned_runners examples' do
+ let(:user) { create(:user) }
- shared_examples :nested_groups_owner do
- context 'when the user is the owner of a multi-level group' do
- before do
- set_permissions_for_users
- end
+ shared_examples :nested_groups_owner do
+ context 'when the user is the owner of a multi-level group' do
+ before do
+ set_permissions_for_users
+ end
- it 'loads all the runners in the tree of groups' do
- expect(user.ci_owned_runners).to contain_exactly(runner, group_runner)
- end
+ it 'loads all the runners in the tree of groups' do
+ expect(user.ci_owned_runners).to contain_exactly(runner, group_runner)
+ end
- it 'returns true for owns_runner?' do
- expect(user.owns_runner?(runner)).to eq(true)
- expect(user.owns_runner?(group_runner)).to eq(true)
+ it 'returns true for owns_runner?' do
+ expect(user.owns_runner?(runner)).to eq(true)
+ expect(user.owns_runner?(group_runner)).to eq(true)
+ end
end
end
- end
- shared_examples :group_owner do
- context 'when the user is the owner of a one level group' do
- before do
+ shared_examples :group_owner do
+ context 'when the user is the owner of a one level group' do
+ before do
+ group.add_owner(user)
+ end
+
+ it 'loads the runners in the group' do
+ expect(user.ci_owned_runners).to contain_exactly(group_runner)
+ end
+
+ it 'returns true for owns_runner?' do
+ expect(user.owns_runner?(group_runner)).to eq(true)
+ end
+ end
+ end
+
+ shared_examples :project_owner do
+ context 'when the user is the owner of a project' do
+ it 'loads the runner belonging to the project' do
+ expect(user.ci_owned_runners).to contain_exactly(runner)
+ end
+
+ it 'returns true for owns_runner?' do
+ expect(user.owns_runner?(runner)).to eq(true)
+ end
+ end
+ end
+
+ shared_examples :project_member do
+ context 'when the user is a maintainer' do
+ before do
+ add_user(:maintainer)
+ end
+
+ it 'loads the runners of the project' do
+ expect(user.ci_owned_runners).to contain_exactly(project_runner)
+ end
+
+ it 'returns true for owns_runner?' do
+ expect(user.owns_runner?(project_runner)).to eq(true)
+ end
+ end
+
+ context 'when the user is a developer' do
+ before do
+ add_user(:developer)
+ end
+
+ it 'does not load any runner' do
+ expect(user.ci_owned_runners).to be_empty
+ end
+
+ it 'returns false for owns_runner?' do
+ expect(user.owns_runner?(project_runner)).to eq(false)
+ end
+ end
+
+ context 'when the user is a reporter' do
+ before do
+ add_user(:reporter)
+ end
+
+ it 'does not load any runner' do
+ expect(user.ci_owned_runners).to be_empty
+ end
+
+ it 'returns false for owns_runner?' do
+ expect(user.owns_runner?(project_runner)).to eq(false)
+ end
+ end
+
+ context 'when the user is a guest' do
+ before do
+ add_user(:guest)
+ end
+
+ it 'does not load any runner' do
+ expect(user.ci_owned_runners).to be_empty
+ end
+
+ it 'returns false for owns_runner?' do
+ expect(user.owns_runner?(project_runner)).to eq(false)
+ end
+ end
+ end
+
+ shared_examples :group_member do
+ context 'when the user is a maintainer' do
+ before do
+ add_user(:maintainer)
+ end
+
+ it 'does not load the runners of the group' do
+ expect(user.ci_owned_runners).to be_empty
+ end
+
+ it 'returns false for owns_runner?' do
+ expect(user.owns_runner?(runner)).to eq(false)
+ end
+ end
+
+ context 'when the user is a developer' do
+ before do
+ add_user(:developer)
+ end
+
+ it 'does not load any runner' do
+ expect(user.ci_owned_runners).to be_empty
+ end
+
+ it 'returns false for owns_runner?' do
+ expect(user.owns_runner?(runner)).to eq(false)
+ end
+ end
+
+ context 'when the user is a reporter' do
+ before do
+ add_user(:reporter)
+ end
+
+ it 'does not load any runner' do
+ expect(user.ci_owned_runners).to be_empty
+ end
+
+ it 'returns false for owns_runner?' do
+ expect(user.owns_runner?(runner)).to eq(false)
+ end
+ end
+
+ context 'when the user is a guest' do
+ before do
+ add_user(:guest)
+ end
+
+ it 'does not load any runner' do
+ expect(user.ci_owned_runners).to be_empty
+ end
+
+ it 'returns false for owns_runner?' do
+ expect(user.owns_runner?(runner)).to eq(false)
+ end
+ end
+ end
+
+ context 'without any projects nor groups' do
+ it 'does not load any runner' do
+ expect(user.ci_owned_runners).to be_empty
+ end
+
+ it 'returns false for owns_runner?' do
+ expect(user.owns_runner?(create(:ci_runner))).to eq(false)
+ end
+ end
+
+ context 'with runner in a personal project' do
+ let!(:namespace) { create(:user_namespace, owner: user) }
+ let!(:project) { create(:project, namespace: namespace) }
+ let!(:runner) { create(:ci_runner, :project, projects: [project]) }
+
+ it_behaves_like :project_owner
+ end
+
+ context 'with group runner in a non owned group' do
+ let!(:group) { create(:group) }
+ let!(:runner) { create(:ci_runner, :group, groups: [group]) }
+
+ def add_user(access)
+ group.add_user(user, access)
+ end
+
+ it_behaves_like :group_member
+ end
+
+ context 'with group runner in an owned group' do
+ let!(:group) { create(:group) }
+ let!(:group_runner) { create(:ci_runner, :group, groups: [group]) }
+
+ it_behaves_like :group_owner
+ end
+
+ context 'with group runner in an owned group and group runner in a different owner subgroup' do
+ let!(:group) { create(:group) }
+ let!(:runner) { create(:ci_runner, :group, groups: [group]) }
+ let!(:subgroup) { create(:group, parent: group) }
+ let!(:group_runner) { create(:ci_runner, :group, groups: [subgroup]) }
+ let!(:another_user) { create(:user) }
+
+ def set_permissions_for_users
+ group.add_owner(user)
+ subgroup.add_owner(another_user)
+ end
+
+ it_behaves_like :nested_groups_owner
+ end
+
+ context 'with personal project runner in an an owned group and a group runner in that same group' do
+ let!(:group) { create(:group) }
+ let!(:group_runner) { create(:ci_runner, :group, groups: [group]) }
+ let!(:project) { create(:project, group: group) }
+ let!(:runner) { create(:ci_runner, :project, projects: [project]) }
+
+ def set_permissions_for_users
group.add_owner(user)
end
- it 'loads the runners in the group' do
- expect(user.ci_owned_runners).to contain_exactly(group_runner)
- end
-
- it 'returns true for owns_runner?' do
- expect(user.owns_runner?(group_runner)).to eq(true)
- end
- end
- end
-
- shared_examples :project_owner do
- context 'when the user is the owner of a project' do
- it 'loads the runner belonging to the project' do
- expect(user.ci_owned_runners).to contain_exactly(runner)
- end
-
- it 'returns true for owns_runner?' do
- expect(user.owns_runner?(runner)).to eq(true)
- end
- end
- end
-
- shared_examples :project_member do
- context 'when the user is a maintainer' do
- before do
- add_user(:maintainer)
- end
-
- it 'loads the runners of the project' do
- expect(user.ci_owned_runners).to contain_exactly(project_runner)
- end
-
- it 'returns true for owns_runner?' do
- expect(user.owns_runner?(project_runner)).to eq(true)
- end
+ it_behaves_like :nested_groups_owner
end
- context 'when the user is a developer' do
- before do
- add_user(:developer)
+ context 'with personal project runner in an owned group and a group runner in a subgroup' do
+ let!(:group) { create(:group) }
+ let!(:subgroup) { create(:group, parent: group) }
+ let!(:group_runner) { create(:ci_runner, :group, groups: [subgroup]) }
+ let!(:project) { create(:project, group: group) }
+ let!(:runner) { create(:ci_runner, :project, projects: [project]) }
+
+ def set_permissions_for_users
+ group.add_owner(user)
end
+ it_behaves_like :nested_groups_owner
+ end
+
+ context 'with personal project runner in an owned group in an owned namespace and a group runner in that group' do
+ let!(:namespace) { create(:user_namespace, owner: user) }
+ let!(:group) { create(:group) }
+ let!(:group_runner) { create(:ci_runner, :group, groups: [group]) }
+ let!(:project) { create(:project, namespace: namespace, group: group) }
+ let!(:runner) { create(:ci_runner, :project, projects: [project]) }
+
+ def set_permissions_for_users
+ group.add_owner(user)
+ end
+
+ it_behaves_like :nested_groups_owner
+ end
+
+ context 'with personal project runner in an owned namespace, an owned group, a subgroup and a group runner in that subgroup' do
+ let!(:namespace) { create(:user_namespace, owner: user) }
+ let!(:group) { create(:group) }
+ let!(:subgroup) { create(:group, parent: group) }
+ let!(:group_runner) { create(:ci_runner, :group, groups: [subgroup]) }
+ let!(:project) { create(:project, namespace: namespace, group: group) }
+ let!(:runner) { create(:ci_runner, :project, projects: [project]) }
+
+ def set_permissions_for_users
+ group.add_owner(user)
+ end
+
+ it_behaves_like :nested_groups_owner
+ end
+
+ context 'with a project runner that belong to projects that belong to a not owned group' do
+ let!(:group) { create(:group) }
+ let!(:project) { create(:project, group: group) }
+ let!(:project_runner) { create(:ci_runner, :project, projects: [project]) }
+
+ def add_user(access)
+ project.add_user(user, access)
+ end
+
+ it_behaves_like :project_member
+ end
+
+ context 'with project runners that belong to projects that do not belong to any group' do
+ let!(:project) { create(:project) }
+ let!(:runner) { create(:ci_runner, :project, projects: [project]) }
+
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
end
-
- it 'returns false for owns_runner?' do
- expect(user.owns_runner?(project_runner)).to eq(false)
- end
end
- context 'when the user is a reporter' do
- before do
- add_user(:reporter)
+ context 'with a group runner that belongs to a subgroup of a group owned by another user' do
+ let!(:group) { create(:group) }
+ let!(:subgroup) { create(:group, parent: group) }
+ let!(:runner) { create(:ci_runner, :group, groups: [subgroup]) }
+ let!(:another_user) { create(:user) }
+
+ def add_user(access)
+ subgroup.add_user(user, access)
+ group.add_user(another_user, :owner)
end
- it 'does not load any runner' do
- expect(user.ci_owned_runners).to be_empty
- end
-
- it 'returns false for owns_runner?' do
- expect(user.owns_runner?(project_runner)).to eq(false)
- end
- end
-
- context 'when the user is a guest' do
- before do
- add_user(:guest)
- end
-
- it 'does not load any runner' do
- expect(user.ci_owned_runners).to be_empty
- end
-
- it 'returns false for owns_runner?' do
- expect(user.owns_runner?(project_runner)).to eq(false)
- end
+ it_behaves_like :group_member
end
end
- shared_examples :group_member do
- context 'when the user is a maintainer' do
- before do
- add_user(:maintainer)
- end
+ it_behaves_like 'ci_owned_runners examples'
- it 'does not load the runners of the group' do
- expect(user.ci_owned_runners).to be_empty
- end
-
- it 'returns false for owns_runner?' do
- expect(user.owns_runner?(runner)).to eq(false)
- end
+ context 'when feature flag :linear_user_ci_owned_runners is disabled' do
+ before do
+ stub_feature_flags(linear_user_ci_owned_runners: false)
end
- context 'when the user is a developer' do
- before do
- add_user(:developer)
- end
-
- it 'does not load any runner' do
- expect(user.ci_owned_runners).to be_empty
- end
-
- it 'returns false for owns_runner?' do
- expect(user.owns_runner?(runner)).to eq(false)
- end
- end
-
- context 'when the user is a reporter' do
- before do
- add_user(:reporter)
- end
-
- it 'does not load any runner' do
- expect(user.ci_owned_runners).to be_empty
- end
-
- it 'returns false for owns_runner?' do
- expect(user.owns_runner?(runner)).to eq(false)
- end
- end
-
- context 'when the user is a guest' do
- before do
- add_user(:guest)
- end
-
- it 'does not load any runner' do
- expect(user.ci_owned_runners).to be_empty
- end
-
- it 'returns false for owns_runner?' do
- expect(user.owns_runner?(runner)).to eq(false)
- end
- end
- end
-
- context 'without any projects nor groups' do
- it 'does not load any runner' do
- expect(user.ci_owned_runners).to be_empty
- end
-
- it 'returns false for owns_runner?' do
- expect(user.owns_runner?(create(:ci_runner))).to eq(false)
- end
- end
-
- context 'with runner in a personal project' do
- let!(:namespace) { create(:user_namespace, owner: user) }
- let!(:project) { create(:project, namespace: namespace) }
- let!(:runner) { create(:ci_runner, :project, projects: [project]) }
-
- it_behaves_like :project_owner
- end
-
- context 'with group runner in a non owned group' do
- let!(:group) { create(:group) }
- let!(:runner) { create(:ci_runner, :group, groups: [group]) }
-
- def add_user(access)
- group.add_user(user, access)
- end
-
- it_behaves_like :group_member
- end
-
- context 'with group runner in an owned group' do
- let!(:group) { create(:group) }
- let!(:group_runner) { create(:ci_runner, :group, groups: [group]) }
-
- it_behaves_like :group_owner
- end
-
- context 'with group runner in an owned group and group runner in a different owner subgroup' do
- let!(:group) { create(:group) }
- let!(:runner) { create(:ci_runner, :group, groups: [group]) }
- let!(:subgroup) { create(:group, parent: group) }
- let!(:group_runner) { create(:ci_runner, :group, groups: [subgroup]) }
- let!(:another_user) { create(:user) }
-
- def set_permissions_for_users
- group.add_owner(user)
- subgroup.add_owner(another_user)
- end
-
- it_behaves_like :nested_groups_owner
- end
-
- context 'with personal project runner in an an owned group and a group runner in that same group' do
- let!(:group) { create(:group) }
- let!(:group_runner) { create(:ci_runner, :group, groups: [group]) }
- let!(:project) { create(:project, group: group) }
- let!(:runner) { create(:ci_runner, :project, projects: [project]) }
-
- def set_permissions_for_users
- group.add_owner(user)
- end
-
- it_behaves_like :nested_groups_owner
- end
-
- context 'with personal project runner in an owned group and a group runner in a subgroup' do
- let!(:group) { create(:group) }
- let!(:subgroup) { create(:group, parent: group) }
- let!(:group_runner) { create(:ci_runner, :group, groups: [subgroup]) }
- let!(:project) { create(:project, group: group) }
- let!(:runner) { create(:ci_runner, :project, projects: [project]) }
-
- def set_permissions_for_users
- group.add_owner(user)
- end
-
- it_behaves_like :nested_groups_owner
- end
-
- context 'with personal project runner in an owned group in an owned namespace and a group runner in that group' do
- let!(:namespace) { create(:user_namespace, owner: user) }
- let!(:group) { create(:group) }
- let!(:group_runner) { create(:ci_runner, :group, groups: [group]) }
- let!(:project) { create(:project, namespace: namespace, group: group) }
- let!(:runner) { create(:ci_runner, :project, projects: [project]) }
-
- def set_permissions_for_users
- group.add_owner(user)
- end
-
- it_behaves_like :nested_groups_owner
- end
-
- context 'with personal project runner in an owned namespace, an owned group, a subgroup and a group runner in that subgroup' do
- let!(:namespace) { create(:user_namespace, owner: user) }
- let!(:group) { create(:group) }
- let!(:subgroup) { create(:group, parent: group) }
- let!(:group_runner) { create(:ci_runner, :group, groups: [subgroup]) }
- let!(:project) { create(:project, namespace: namespace, group: group) }
- let!(:runner) { create(:ci_runner, :project, projects: [project]) }
-
- def set_permissions_for_users
- group.add_owner(user)
- end
-
- it_behaves_like :nested_groups_owner
- end
-
- context 'with a project runner that belong to projects that belong to a not owned group' do
- let!(:group) { create(:group) }
- let!(:project) { create(:project, group: group) }
- let!(:project_runner) { create(:ci_runner, :project, projects: [project]) }
-
- def add_user(access)
- project.add_user(user, access)
- end
-
- it_behaves_like :project_member
- end
-
- context 'with project runners that belong to projects that do not belong to any group' do
- let!(:project) { create(:project) }
- let!(:runner) { create(:ci_runner, :project, projects: [project]) }
-
- it 'does not load any runner' do
- expect(user.ci_owned_runners).to be_empty
- end
- end
-
- context 'with a group runner that belongs to a subgroup of a group owned by another user' do
- let!(:group) { create(:group) }
- let!(:subgroup) { create(:group, parent: group) }
- let!(:runner) { create(:ci_runner, :group, groups: [subgroup]) }
- let!(:another_user) { create(:user) }
-
- def add_user(access)
- subgroup.add_user(user, access)
- group.add_user(another_user, :owner)
- end
-
- it_behaves_like :group_member
+ it_behaves_like 'ci_owned_runners examples'
end
end
diff --git a/spec/requests/api/import_github_spec.rb b/spec/requests/api/import_github_spec.rb
index d5fed330401..f0c4fcc4f29 100644
--- a/spec/requests/api/import_github_spec.rb
+++ b/spec/requests/api/import_github_spec.rb
@@ -11,12 +11,12 @@ RSpec.describe API::ImportGithub do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:provider_username) { user.username }
- let(:provider_user) { OpenStruct.new(login: provider_username) }
+ let(:provider_user) { double('provider', login: provider_username) }
let(:provider_repo) do
- OpenStruct.new(
+ double('provider',
name: 'vim',
full_name: "#{provider_username}/vim",
- owner: OpenStruct.new(login: provider_username)
+ owner: double('provider', login: provider_username)
)
end
diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb
index 7ea08131419..81cab973b30 100644
--- a/spec/services/groups/create_service_spec.rb
+++ b/spec/services/groups/create_service_spec.rb
@@ -24,6 +24,16 @@ RSpec.describe Groups::CreateService, '#execute' do
end
end
+ context 'when `setup_for_company:true` is passed' do
+ let(:params) { group_params.merge(setup_for_company: true) }
+ let(:service) { described_class.new(user, params) }
+ let(:created_group) { service.execute }
+
+ it 'creates group with the specified setup_for_company' do
+ expect(created_group.setup_for_company).to eq(true)
+ end
+ end
+
context 'creating a group with `default_branch_protection` attribute' do
let(:params) { group_params.merge(default_branch_protection: Gitlab::Access::PROTECTION_NONE) }
let(:service) { described_class.new(user, params) }
diff --git a/yarn.lock b/yarn.lock
index 78cdedccd40..d9dfcf2a36a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -914,20 +914,20 @@
stylelint-declaration-strict-value "1.7.7"
stylelint-scss "3.18.0"
-"@gitlab/svgs@1.220.0":
- version "1.220.0"
- resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.220.0.tgz#188bdefe86cdbf8be1faa7a92dbac31c728066c7"
- integrity sha512-9QRXQG6IrQoviU86g2Y4l19yE81UyEg/iMoGetMfUdQ64NW6unLN7uNbUaO1ws1J0p7uG0dKwR6ohD7tEUPLFA==
+"@gitlab/svgs@1.221.0":
+ version "1.221.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.221.0.tgz#1804d181b09672d13005d49818eb2da040d67488"
+ integrity sha512-Xn29eer39uVqeuefL/3hVuxo2tazHoEFIXC0F7DnESmBggrcjueNM2tuBUk40oaX6kCzM2Bn4Qn9ESIR+V0PgQ==
"@gitlab/tributejs@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8"
integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw==
-"@gitlab/ui@32.36.0":
- version "32.36.0"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-32.36.0.tgz#f3fb6f86dc51a6941bd230c047f0014364732efd"
- integrity sha512-cX/+P011FD6TrD9/tKuG5xX/tSx9oRwb/bRA45RG1zbkmRh/ohr5zQMuNK2utrHFl4OqrcPirqXMyFl+kKnCdg==
+"@gitlab/ui@32.38.0":
+ version "32.38.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-32.38.0.tgz#580bda8daacdf23c1f6a75d77f6df44b1dbe1726"
+ integrity sha512-BS0+4JicfuiCbaWTTok0dQUzUCI8m8t5T7//DQUQqqwCZLYeJlb1AxMatd6IjwcdE0m+AhST3iOZi2x+hDrkbQ==
dependencies:
"@babel/standalone" "^7.0.0"
bootstrap-vue "2.20.1"