summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-03-26 09:07:52 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-03-26 09:07:52 +0000
commit7e019504f5ac6decde690565857238e7e59aa034 (patch)
treefab8832b40e25fc9bc1ae54b9303b95ea146b5d5 /spec
parent116d4e56e83a1f408afe710ce070e699ba206475 (diff)
downloadgitlab-ce-7e019504f5ac6decde690565857238e7e59aa034.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/factories/user_highest_roles.rb13
-rw-r--r--spec/finders/protected_branches_finder_spec.rb2
-rw-r--r--spec/frontend/monitoring/components/dashboard_url_time_spec.js27
-rw-r--r--spec/frontend/monitoring/components/panel_type_spec.js17
-rw-r--r--spec/frontend/notes/components/notes_app_spec.js63
-rw-r--r--spec/frontend/notes/components/sort_discussion_spec.js72
-rw-r--r--spec/frontend/notes/stores/actions_spec.js13
-rw-r--r--spec/frontend/notes/stores/getters_spec.js8
-rw-r--r--spec/frontend/notes/stores/mutation_spec.js25
-rw-r--r--spec/frontend/vue_shared/components/ordered_layout_spec.js63
-rw-r--r--spec/models/member_spec.rb52
-rw-r--r--spec/models/project_spec.rb2
-rw-r--r--spec/models/user_spec.rb20
-rw-r--r--spec/services/members/update_highest_role_service_spec.rb42
-rw-r--r--spec/services/users/update_highest_member_role_service_spec.rb41
-rw-r--r--spec/workers/cluster_update_app_worker_spec.rb15
-rw-r--r--spec/workers/update_highest_role_worker_spec.rb64
17 files changed, 511 insertions, 28 deletions
diff --git a/spec/factories/user_highest_roles.rb b/spec/factories/user_highest_roles.rb
index d04db507e44..761a8b6c583 100644
--- a/spec/factories/user_highest_roles.rb
+++ b/spec/factories/user_highest_roles.rb
@@ -2,14 +2,13 @@
FactoryBot.define do
factory :user_highest_role do
+ highest_access_level { nil }
user
- trait :maintainer do
- highest_access_level { Gitlab::Access::MAINTAINER }
- end
-
- trait :developer do
- highest_access_level { Gitlab::Access::DEVELOPER }
- end
+ trait(:guest) { highest_access_level { GroupMember::GUEST } }
+ trait(:reporter) { highest_access_level { GroupMember::REPORTER } }
+ trait(:developer) { highest_access_level { GroupMember::DEVELOPER } }
+ trait(:maintainer) { highest_access_level { GroupMember::MAINTAINER } }
+ trait(:owner) { highest_access_level { GroupMember::OWNER } }
end
end
diff --git a/spec/finders/protected_branches_finder_spec.rb b/spec/finders/protected_branches_finder_spec.rb
index e6a2cf4577c..c6b9964b6c5 100644
--- a/spec/finders/protected_branches_finder_spec.rb
+++ b/spec/finders/protected_branches_finder_spec.rb
@@ -30,7 +30,7 @@ describe ProtectedBranchesFinder do
end
it 'returns limited protected branches of project' do
- expect(subject).to eq([another_protected_branch])
+ expect(subject.count).to eq(1)
end
end
end
diff --git a/spec/frontend/monitoring/components/dashboard_url_time_spec.js b/spec/frontend/monitoring/components/dashboard_url_time_spec.js
index bf5a11a536e..ebfa09874fa 100644
--- a/spec/frontend/monitoring/components/dashboard_url_time_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_url_time_spec.js
@@ -1,7 +1,13 @@
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import createFlash from '~/flash';
-import { queryToObject, redirectTo, removeParams, mergeUrlParams } from '~/lib/utils/url_utility';
+import {
+ queryToObject,
+ redirectTo,
+ removeParams,
+ mergeUrlParams,
+ updateHistory,
+} from '~/lib/utils/url_utility';
import axios from '~/lib/utils/axios_utils';
import { mockProjectDir } from '../mock_data';
@@ -137,4 +143,23 @@ describe('dashboard invalid url parameters', () => {
expect(redirectTo).toHaveBeenCalledTimes(1);
});
});
+
+ it('changes the url when a panel moves the time slider', () => {
+ const timeRange = {
+ start: '2020-01-01T00:00:00.000Z',
+ end: '2020-01-01T01:00:00.000Z',
+ };
+
+ queryToObject.mockReturnValue(timeRange);
+
+ createMountedWrapper();
+
+ return wrapper.vm.$nextTick().then(() => {
+ wrapper.vm.onTimeRangeZoom(timeRange);
+
+ expect(updateHistory).toHaveBeenCalled();
+ expect(wrapper.vm.selectedTimeRange.start.toString()).toBe(timeRange.start);
+ expect(wrapper.vm.selectedTimeRange.end.toString()).toBe(timeRange.end);
+ });
+ });
});
diff --git a/spec/frontend/monitoring/components/panel_type_spec.js b/spec/frontend/monitoring/components/panel_type_spec.js
index 058c201d325..927d93ab697 100644
--- a/spec/frontend/monitoring/components/panel_type_spec.js
+++ b/spec/frontend/monitoring/components/panel_type_spec.js
@@ -99,6 +99,8 @@ describe('Panel Type component', () => {
});
describe('when graph data is available', () => {
+ const findTimeChart = () => wrapper.find({ ref: 'timeChart' });
+
beforeEach(() => {
createWrapper({
graphData: graphDataPrometheusQueryRange,
@@ -122,6 +124,21 @@ describe('Panel Type component', () => {
expect(findCopyLink().exists()).toBe(false);
});
+ it('should emit `timerange` event when a zooming in/out in a chart occcurs', () => {
+ const timeRange = {
+ start: '2020-01-01T00:00:00.000Z',
+ end: '2020-01-01T01:00:00.000Z',
+ };
+
+ jest.spyOn(wrapper.vm, '$emit');
+
+ findTimeChart().vm.$emit('datazoom', timeRange);
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('timerangezoom', timeRange);
+ });
+ });
+
describe('Time Series Chart panel type', () => {
it('is rendered', () => {
expect(wrapper.find(TimeSeriesChart).isVueInstance()).toBe(true);
diff --git a/spec/frontend/notes/components/notes_app_spec.js b/spec/frontend/notes/components/notes_app_spec.js
index 60e866542a6..e22dd85f221 100644
--- a/spec/frontend/notes/components/notes_app_spec.js
+++ b/spec/frontend/notes/components/notes_app_spec.js
@@ -1,26 +1,44 @@
import $ from 'jquery';
import AxiosMockAdapter from 'axios-mock-adapter';
import Vue from 'vue';
-import { mount } from '@vue/test-utils';
+import { mount, shallowMount } from '@vue/test-utils';
import { setTestTimeout } from 'helpers/timeout';
import axios from '~/lib/utils/axios_utils';
import NotesApp from '~/notes/components/notes_app.vue';
+import CommentForm from '~/notes/components/comment_form.vue';
import createStore from '~/notes/stores';
+import * as constants from '~/notes/constants';
import '~/behaviors/markdown/render_gfm';
// TODO: use generated fixture (https://gitlab.com/gitlab-org/gitlab-foss/issues/62491)
import * as mockData from '../../notes/mock_data';
import * as urlUtility from '~/lib/utils/url_utility';
+import OrderedLayout from '~/vue_shared/components/ordered_layout.vue';
jest.mock('~/user_popovers', () => jest.fn());
setTestTimeout(1000);
+const TYPE_COMMENT_FORM = 'comment-form';
+const TYPE_NOTES_LIST = 'notes-list';
+
+const propsData = {
+ noteableData: mockData.noteableDataMock,
+ notesData: mockData.notesDataMock,
+ userData: mockData.userDataMock,
+};
+
describe('note_app', () => {
let axiosMock;
let mountComponent;
let wrapper;
let store;
+ const getComponentOrder = () => {
+ return wrapper
+ .findAll('#notes-list,.js-comment-form')
+ .wrappers.map(node => (node.is(CommentForm) ? TYPE_COMMENT_FORM : TYPE_NOTES_LIST));
+ };
+
/**
* waits for fetchNotes() to complete
*/
@@ -43,13 +61,7 @@ describe('note_app', () => {
axiosMock = new AxiosMockAdapter(axios);
store = createStore();
- mountComponent = data => {
- const propsData = data || {
- noteableData: mockData.noteableDataMock,
- notesData: mockData.notesDataMock,
- userData: mockData.userDataMock,
- };
-
+ mountComponent = () => {
return mount(
{
components: {
@@ -346,4 +358,39 @@ describe('note_app', () => {
expect(setTargetNoteHash).toHaveBeenCalled();
});
});
+
+ describe('when sort direction is desc', () => {
+ beforeEach(() => {
+ store = createStore();
+ store.state.discussionSortOrder = constants.DESC;
+ wrapper = shallowMount(NotesApp, {
+ propsData,
+ store,
+ stubs: {
+ 'ordered-layout': OrderedLayout,
+ },
+ });
+ });
+
+ it('finds CommentForm before notes list', () => {
+ expect(getComponentOrder()).toStrictEqual([TYPE_COMMENT_FORM, TYPE_NOTES_LIST]);
+ });
+ });
+
+ describe('when sort direction is asc', () => {
+ beforeEach(() => {
+ store = createStore();
+ wrapper = shallowMount(NotesApp, {
+ propsData,
+ store,
+ stubs: {
+ 'ordered-layout': OrderedLayout,
+ },
+ });
+ });
+
+ it('finds CommentForm after notes list', () => {
+ expect(getComponentOrder()).toStrictEqual([TYPE_NOTES_LIST, TYPE_COMMENT_FORM]);
+ });
+ });
});
diff --git a/spec/frontend/notes/components/sort_discussion_spec.js b/spec/frontend/notes/components/sort_discussion_spec.js
new file mode 100644
index 00000000000..785e8c75233
--- /dev/null
+++ b/spec/frontend/notes/components/sort_discussion_spec.js
@@ -0,0 +1,72 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
+import SortDiscussion from '~/notes/components/sort_discussion.vue';
+import createStore from '~/notes/stores';
+import { ASC, DESC } from '~/notes/constants';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('Sort Discussion component', () => {
+ let wrapper;
+ let store;
+
+ const createComponent = () => {
+ jest.spyOn(store, 'dispatch').mockImplementation();
+
+ wrapper = shallowMount(SortDiscussion, {
+ localVue,
+ store,
+ });
+ };
+
+ beforeEach(() => {
+ store = createStore();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('when asc', () => {
+ describe('when the dropdown is clicked', () => {
+ it('calls the right actions', () => {
+ createComponent();
+
+ wrapper.find('.js-newest-first').trigger('click');
+
+ expect(store.dispatch).toHaveBeenCalledWith('setDiscussionSortDirection', DESC);
+ });
+ });
+
+ it('shows the "Oldest First" as the dropdown', () => {
+ createComponent();
+
+ expect(wrapper.find('.js-dropdown-text').text()).toBe('Oldest first');
+ });
+ });
+
+ describe('when desc', () => {
+ beforeEach(() => {
+ store.state.discussionSortOrder = DESC;
+ createComponent();
+ });
+
+ describe('when the dropdown item is clicked', () => {
+ it('calls the right actions', () => {
+ wrapper.find('.js-oldest-first').trigger('click');
+
+ expect(store.dispatch).toHaveBeenCalledWith('setDiscussionSortDirection', ASC);
+ });
+
+ it('applies the active class to the correct button in the dropdown', () => {
+ expect(wrapper.find('.js-newest-first').classes()).toContain('is-active');
+ });
+ });
+
+ it('shows the "Newest First" as the dropdown', () => {
+ expect(wrapper.find('.js-dropdown-text').text()).toBe('Newest first');
+ });
+ });
+});
diff --git a/spec/frontend/notes/stores/actions_spec.js b/spec/frontend/notes/stores/actions_spec.js
index 40b0134e12e..544d482e7fc 100644
--- a/spec/frontend/notes/stores/actions_spec.js
+++ b/spec/frontend/notes/stores/actions_spec.js
@@ -902,4 +902,17 @@ describe('Actions Notes Store', () => {
]);
});
});
+
+ describe('setDiscussionSortDirection', () => {
+ it('calls the correct mutation with the correct args', done => {
+ testAction(
+ actions.setDiscussionSortDirection,
+ notesConstants.DESC,
+ {},
+ [{ type: mutationTypes.SET_DISCUSSIONS_SORT, payload: notesConstants.DESC }],
+ [],
+ done,
+ );
+ });
+ });
});
diff --git a/spec/frontend/notes/stores/getters_spec.js b/spec/frontend/notes/stores/getters_spec.js
index 602e4c70741..a07aa45d812 100644
--- a/spec/frontend/notes/stores/getters_spec.js
+++ b/spec/frontend/notes/stores/getters_spec.js
@@ -1,4 +1,5 @@
import * as getters from '~/notes/stores/getters';
+import { DESC } from '~/notes/constants';
import {
notesDataMock,
userDataMock,
@@ -36,6 +37,7 @@ describe('Getters Notes Store', () => {
userData: userDataMock,
noteableData: noteableDataMock,
descriptionVersions: 'descriptionVersions',
+ discussionSortOrder: DESC,
};
});
@@ -392,4 +394,10 @@ describe('Getters Notes Store', () => {
expect(getters.descriptionVersions(state)).toEqual('descriptionVersions');
});
});
+
+ describe('sortDirection', () => {
+ it('should return `discussionSortOrder`', () => {
+ expect(getters.sortDirection(state)).toBe(DESC);
+ });
+ });
});
diff --git a/spec/frontend/notes/stores/mutation_spec.js b/spec/frontend/notes/stores/mutation_spec.js
index ea5658821b1..06d2654ceca 100644
--- a/spec/frontend/notes/stores/mutation_spec.js
+++ b/spec/frontend/notes/stores/mutation_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import mutations from '~/notes/stores/mutations';
-import { DISCUSSION_NOTE } from '~/notes/constants';
+import { DISCUSSION_NOTE, ASC, DESC } from '~/notes/constants';
import {
note,
discussionMock,
@@ -22,7 +22,10 @@ describe('Notes Store mutations', () => {
let noteData;
beforeEach(() => {
- state = { discussions: [] };
+ state = {
+ discussions: [],
+ discussionSortOrder: ASC,
+ };
noteData = {
expanded: true,
id: note.discussion_id,
@@ -34,9 +37,7 @@ describe('Notes Store mutations', () => {
});
it('should add a new note to an array of notes', () => {
- expect(state).toEqual({
- discussions: [noteData],
- });
+ expect(state).toEqual(expect.objectContaining({ discussions: [noteData] }));
expect(state.discussions.length).toBe(1);
});
@@ -649,4 +650,18 @@ describe('Notes Store mutations', () => {
expect(state.descriptionVersions[versionId]).toBe(deleted);
});
});
+
+ describe('SET_DISCUSSIONS_SORT', () => {
+ let state;
+
+ beforeEach(() => {
+ state = { discussionSortOrder: ASC };
+ });
+
+ it('sets sort order', () => {
+ mutations.SET_DISCUSSIONS_SORT(state, DESC);
+
+ expect(state.discussionSortOrder).toBe(DESC);
+ });
+ });
});
diff --git a/spec/frontend/vue_shared/components/ordered_layout_spec.js b/spec/frontend/vue_shared/components/ordered_layout_spec.js
new file mode 100644
index 00000000000..e8667d9ee4a
--- /dev/null
+++ b/spec/frontend/vue_shared/components/ordered_layout_spec.js
@@ -0,0 +1,63 @@
+import { mount } from '@vue/test-utils';
+import orderedLayout from '~/vue_shared/components/ordered_layout.vue';
+
+const children = `
+ <template v-slot:header>
+ <header></header>
+ </template>
+ <template v-slot:footer>
+ <footer></footer>
+ </template>
+ `;
+
+const TestComponent = {
+ components: { orderedLayout },
+ template: `
+ <div>
+ <ordered-layout v-bind="$attrs">
+ ${children}
+ </ordered-layout>
+ </div>
+ `,
+};
+
+const regularSlotOrder = ['header', 'footer'];
+
+describe('Ordered Layout', () => {
+ let wrapper;
+
+ const verifyOrder = () =>
+ wrapper.findAll('footer,header').wrappers.map(x => (x.is('footer') ? 'footer' : 'header'));
+
+ const createComponent = (props = {}) => {
+ wrapper = mount(TestComponent, {
+ propsData: props,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when slotKeys are in initial slot order', () => {
+ beforeEach(() => {
+ createComponent({ slotKeys: regularSlotOrder });
+ });
+
+ it('confirms order of the component is reflective of slotKeys', () => {
+ expect(verifyOrder()).toEqual(regularSlotOrder);
+ });
+ });
+
+ describe('when slotKeys reverse the order of the props', () => {
+ const reversedSlotOrder = regularSlotOrder.reverse();
+
+ beforeEach(() => {
+ createComponent({ slotKeys: reversedSlotOrder });
+ });
+
+ it('confirms order of the component is reflective of slotKeys', () => {
+ expect(verifyOrder()).toEqual(reversedSlotOrder);
+ });
+ });
+});
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index e7f03226826..ce3ee3fcfb0 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
describe Member do
+ using RSpec::Parameterized::TableSyntax
+
describe "Associations" do
it { is_expected.to belong_to(:user) }
end
@@ -582,4 +584,54 @@ describe Member do
expect(user.authorized_projects).not_to include(project)
end
end
+
+ context 'when after_commit :update_highest_role' do
+ where(:member_type, :source_type) do
+ :project_member | :project
+ :group_member | :group
+ end
+
+ with_them do
+ describe 'create member' do
+ it 'initializes a new Members::UpdateHighestRoleService object' do
+ source = create(source_type) # source owner initializes a new service object too
+ user = create(:user)
+
+ expect(Members::UpdateHighestRoleService).to receive(:new).with(user.id).and_call_original
+
+ create(member_type, :guest, user: user, source_type => source)
+ end
+ end
+
+ context 'when member exists' do
+ let!(:member) { create(member_type) }
+
+ describe 'update member' do
+ context 'when access level was changed' do
+ it 'initializes a new Members::UpdateHighestRoleService object' do
+ expect(Members::UpdateHighestRoleService).to receive(:new).with(member.user_id).and_call_original
+
+ member.update(access_level: Gitlab::Access::GUEST)
+ end
+ end
+
+ context 'when access level was not changed' do
+ it 'does not initialize a new Members::UpdateHighestRoleService object' do
+ expect(Members::UpdateHighestRoleService).not_to receive(:new).with(member.user_id)
+
+ member.update(notification_level: NotificationSetting.levels[:disabled])
+ end
+ end
+ end
+
+ describe 'destroy member' do
+ it 'initializes a new Members::UpdateHighestRoleService object' do
+ expect(Members::UpdateHighestRoleService).to receive(:new).with(member.user_id).and_call_original
+
+ member.destroy
+ end
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 2586289a699..51fcee29485 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -5718,7 +5718,7 @@ describe Project do
subject { project.limited_protected_branches(1) }
it 'returns limited number of protected branches based on specified limit' do
- expect(subject).to eq([another_protected_branch])
+ expect(subject.count).to eq(1)
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 3286a891203..bbd45afadd7 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -4360,4 +4360,24 @@ describe User, :do_not_mock_admin_mode do
it { is_expected.to be expected_result }
end
end
+
+ describe '#current_highest_access_level' do
+ let_it_be(:user) { create(:user) }
+
+ context 'when no memberships exist' do
+ it 'returns nil' do
+ expect(user.current_highest_access_level).to be_nil
+ end
+ end
+
+ context 'when memberships exist' do
+ it 'returns the highest access level for non requested memberships' do
+ create(:group_member, :reporter, user_id: user.id)
+ create(:project_member, :guest, user_id: user.id)
+ create(:project_member, :maintainer, user_id: user.id, requested_at: Time.current)
+
+ expect(user.current_highest_access_level).to eq(Gitlab::Access::REPORTER)
+ end
+ end
+ end
end
diff --git a/spec/services/members/update_highest_role_service_spec.rb b/spec/services/members/update_highest_role_service_spec.rb
new file mode 100644
index 00000000000..b56a51f83f9
--- /dev/null
+++ b/spec/services/members/update_highest_role_service_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'sidekiq/testing'
+
+describe Members::UpdateHighestRoleService, :clean_gitlab_redis_shared_state do
+ include ExclusiveLeaseHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:lease_key) { "update_highest_role:#{user.id}" }
+ let(:service) { described_class.new(user.id) }
+
+ describe '#perform' do
+ subject { service.execute }
+
+ context 'when lease is obtained' do
+ it 'takes the lease but does not release it', :aggregate_failures do
+ expect_to_obtain_exclusive_lease(lease_key, 'uuid', timeout: described_class::LEASE_TIMEOUT)
+
+ subject
+
+ expect(service.exclusive_lease.exists?).to be_truthy
+ end
+
+ it 'schedules a job' do
+ Sidekiq::Testing.fake! do
+ expect { subject }.to change(UpdateHighestRoleWorker.jobs, :size).by(1)
+ end
+ end
+ end
+
+ context 'when lease cannot be obtained' do
+ it 'only schedules one job' do
+ Sidekiq::Testing.fake! do
+ stub_exclusive_lease_taken(lease_key, timeout: described_class::LEASE_TIMEOUT)
+
+ expect { subject }.not_to change(UpdateHighestRoleWorker.jobs, :size)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/users/update_highest_member_role_service_spec.rb b/spec/services/users/update_highest_member_role_service_spec.rb
new file mode 100644
index 00000000000..8063abffc2a
--- /dev/null
+++ b/spec/services/users/update_highest_member_role_service_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Users::UpdateHighestMemberRoleService do
+ let(:user) { create(:user) }
+ let(:execute_service) { described_class.new(user).execute }
+
+ describe '#execute' do
+ context 'when user_highest_role already exists' do
+ let!(:user_highest_role) { create(:user_highest_role, :guest, user: user) }
+
+ context 'when the current highest access level equals the already stored highest access level' do
+ it 'does not update the highest access level' do
+ create(:group_member, :guest, user: user)
+
+ expect { execute_service }.not_to change { user_highest_role.reload.highest_access_level }
+ end
+ end
+
+ context 'when the current highest access level does not equal the already stored highest access level' do
+ it 'updates the highest access level' do
+ create(:group_member, :developer, user: user)
+
+ expect { execute_service }
+ .to change { user_highest_role.reload.highest_access_level }
+ .from(Gitlab::Access::GUEST)
+ .to(Gitlab::Access::DEVELOPER)
+ end
+ end
+ end
+
+ context 'when user_highest_role does not exist' do
+ it 'creates an user_highest_role object to store the highest access level' do
+ create(:group_member, :guest, user: user)
+
+ expect { execute_service }.to change { UserHighestRole.count }.from(0).to(1)
+ end
+ end
+ end
+end
diff --git a/spec/workers/cluster_update_app_worker_spec.rb b/spec/workers/cluster_update_app_worker_spec.rb
index e540ede4bc0..d91104334e5 100644
--- a/spec/workers/cluster_update_app_worker_spec.rb
+++ b/spec/workers/cluster_update_app_worker_spec.rb
@@ -47,11 +47,11 @@ describe ClusterUpdateAppWorker do
end
context 'with exclusive lease' do
+ let_it_be(:user) { create(:user) }
let(:application) { create(:clusters_applications_prometheus, :installed) }
let(:lease_key) { "#{described_class.name.underscore}-#{application.id}" }
before do
- allow(Gitlab::ExclusiveLease).to receive(:new)
stub_exclusive_lease_taken(lease_key)
end
@@ -61,8 +61,10 @@ describe ClusterUpdateAppWorker do
subject.perform(application.name, application.id, project.id, Time.now)
end
- it 'does not allow same app to be updated concurrently by different project' do
- project1 = create(:project)
+ it 'does not allow same app to be updated concurrently by different project', :aggregate_failures do
+ stub_exclusive_lease("refresh_authorized_projects:#{user.id}")
+ stub_exclusive_lease("update_highest_role:#{user.id}")
+ project1 = create(:project, namespace: create(:namespace, owner: user))
expect(Clusters::Applications::PrometheusUpdateService).not_to receive(:new)
@@ -81,10 +83,13 @@ describe ClusterUpdateAppWorker do
subject.perform(application2.name, application2.id, project.id, Time.now)
end
- it 'allows different app to be updated by different project' do
+ it 'allows different app to be updated by different project', :aggregate_failures do
application2 = create(:clusters_applications_prometheus, :installed)
lease_key2 = "#{described_class.name.underscore}-#{application2.id}"
- project2 = create(:project)
+
+ stub_exclusive_lease("refresh_authorized_projects:#{user.id}")
+ stub_exclusive_lease("update_highest_role:#{user.id}")
+ project2 = create(:project, namespace: create(:namespace, owner: user))
stub_exclusive_lease(lease_key2)
diff --git a/spec/workers/update_highest_role_worker_spec.rb b/spec/workers/update_highest_role_worker_spec.rb
new file mode 100644
index 00000000000..cb112ebe07e
--- /dev/null
+++ b/spec/workers/update_highest_role_worker_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe UpdateHighestRoleWorker, :clean_gitlab_redis_shared_state do
+ include ExclusiveLeaseHelpers
+
+ let(:worker) { described_class.new }
+
+ describe '#perform' do
+ let(:active_scope_attributes) do
+ {
+ state: 'active',
+ ghost: false,
+ user_type: nil
+ }
+ end
+ let(:user) { create(:user, attributes) }
+
+ subject { worker.perform(user.id) }
+
+ context 'when user is found' do
+ let(:attributes) { active_scope_attributes }
+
+ it 'updates the highest role for the user' do
+ user_highest_role = create(:user_highest_role, user: user)
+ create(:group_member, :developer, user: user)
+
+ expect { subject }
+ .to change { user_highest_role.reload.highest_access_level }
+ .from(nil)
+ .to(Gitlab::Access::DEVELOPER)
+ end
+ end
+
+ context 'when user is not found' do
+ shared_examples 'no update' do
+ it 'does not update any highest role' do
+ expect(Users::UpdateHighestMemberRoleService).not_to receive(:new)
+
+ worker.perform(user.id)
+ end
+ end
+
+ context 'when user is blocked' do
+ let(:attributes) { active_scope_attributes.merge(state: 'blocked') }
+
+ it_behaves_like 'no update'
+ end
+
+ context 'when user is a ghost' do
+ let(:attributes) { active_scope_attributes.merge(ghost: true) }
+
+ it_behaves_like 'no update'
+ end
+
+ context 'when user has a user type' do
+ let(:attributes) { active_scope_attributes.merge(user_type: :alert_bot) }
+
+ it_behaves_like 'no update'
+ end
+ end
+ end
+end