summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/repositories/git_http_controller_spec.rb11
-rw-r--r--spec/frontend/monitoring/store/utils_spec.js113
-rw-r--r--spec/frontend/monitoring/store/variable_mapping_spec.js73
-rw-r--r--spec/frontend/monitoring/utils_spec.js78
-rw-r--r--spec/initializers/lograge_spec.rb12
-rw-r--r--spec/lib/api/entities/deploy_key_spec.rb22
-rw-r--r--spec/lib/api/entities/deploy_keys_project_spec.rb25
-rw-r--r--spec/lib/api/entities/ssh_key_spec.rb22
-rw-r--r--spec/requests/api/deploy_keys_spec.rb54
-rw-r--r--spec/requests/jwt_controller_spec.rb9
-rw-r--r--spec/services/authorized_project_update/periodic_recalculate_service_spec.rb30
-rw-r--r--spec/services/authorized_project_update/recalculate_for_user_range_service_spec.rb19
-rw-r--r--spec/support/shared_contexts/controllers/logging_shared_context.rb21
-rw-r--r--spec/workers/authorized_project_update/periodic_recalculate_worker_spec.rb27
-rw-r--r--spec/workers/authorized_project_update/user_refresh_over_user_range_worker_spec.rb30
15 files changed, 401 insertions, 145 deletions
diff --git a/spec/controllers/repositories/git_http_controller_spec.rb b/spec/controllers/repositories/git_http_controller_spec.rb
index aafb933df32..c938df8cf4e 100644
--- a/spec/controllers/repositories/git_http_controller_spec.rb
+++ b/spec/controllers/repositories/git_http_controller_spec.rb
@@ -60,10 +60,21 @@ RSpec.describe Repositories::GitHttpController do
get :info_refs, params: params
end
+
+ include_context 'parsed logs' do
+ it 'adds user info to the logs' do
+ get :info_refs, params: params
+
+ expect(log_data).to include('username' => user.username,
+ 'user_id' => user.id,
+ 'meta.user' => user.username)
+ end
+ end
end
context 'with exceptions' do
before do
+ allow(controller).to receive(:authenticate_user).and_return(true)
allow(controller).to receive(:verify_workhorse_api!).and_return(true)
end
diff --git a/spec/frontend/monitoring/store/utils_spec.js b/spec/frontend/monitoring/store/utils_spec.js
index 3a70bda51da..2dea40585f1 100644
--- a/spec/frontend/monitoring/store/utils_spec.js
+++ b/spec/frontend/monitoring/store/utils_spec.js
@@ -9,6 +9,7 @@ import {
convertToGrafanaTimeRange,
addDashboardMetaDataToLink,
} from '~/monitoring/stores/utils';
+import * as urlUtils from '~/lib/utils/url_utility';
import { annotationsData } from '../mock_data';
import { NOT_IN_DB_PREFIX } from '~/monitoring/constants';
@@ -398,6 +399,118 @@ describe('mapToDashboardViewModel', () => {
});
});
});
+
+ describe('templating variables mapping', () => {
+ beforeEach(() => {
+ jest.spyOn(urlUtils, 'queryToObject');
+ });
+
+ afterEach(() => {
+ urlUtils.queryToObject.mockRestore();
+ });
+
+ it('sets variables as-is from yml file if URL has no variables', () => {
+ const response = {
+ dashboard: 'Dashboard Name',
+ links: [],
+ templating: {
+ variables: {
+ pod: 'kubernetes',
+ pod_2: 'kubernetes-2',
+ },
+ },
+ };
+
+ urlUtils.queryToObject.mockReturnValueOnce();
+
+ expect(mapToDashboardViewModel(response)).toMatchObject({
+ dashboard: 'Dashboard Name',
+ links: [],
+ variables: {
+ pod: {
+ label: 'pod',
+ type: 'text',
+ value: 'kubernetes',
+ },
+ pod_2: {
+ label: 'pod_2',
+ type: 'text',
+ value: 'kubernetes-2',
+ },
+ },
+ });
+ });
+
+ it('sets variables as-is from yml file if URL has no matching variables', () => {
+ const response = {
+ dashboard: 'Dashboard Name',
+ links: [],
+ templating: {
+ variables: {
+ pod: 'kubernetes',
+ pod_2: 'kubernetes-2',
+ },
+ },
+ };
+
+ urlUtils.queryToObject.mockReturnValueOnce({
+ 'var-environment': 'POD',
+ });
+
+ expect(mapToDashboardViewModel(response)).toMatchObject({
+ dashboard: 'Dashboard Name',
+ links: [],
+ variables: {
+ pod: {
+ label: 'pod',
+ type: 'text',
+ value: 'kubernetes',
+ },
+ pod_2: {
+ label: 'pod_2',
+ type: 'text',
+ value: 'kubernetes-2',
+ },
+ },
+ });
+ });
+
+ it('merges variables from URL with the ones from yml file', () => {
+ const response = {
+ dashboard: 'Dashboard Name',
+ links: [],
+ templating: {
+ variables: {
+ pod: 'kubernetes',
+ pod_2: 'kubernetes-2',
+ },
+ },
+ };
+
+ urlUtils.queryToObject.mockReturnValueOnce({
+ 'var-environment': 'POD',
+ 'var-pod': 'POD1',
+ 'var-pod_2': 'POD2',
+ });
+
+ expect(mapToDashboardViewModel(response)).toMatchObject({
+ dashboard: 'Dashboard Name',
+ links: [],
+ variables: {
+ pod: {
+ label: 'pod',
+ type: 'text',
+ value: 'POD1',
+ },
+ pod_2: {
+ label: 'pod_2',
+ type: 'text',
+ value: 'POD2',
+ },
+ },
+ });
+ });
+ });
});
describe('normalizeQueryResult', () => {
diff --git a/spec/frontend/monitoring/store/variable_mapping_spec.js b/spec/frontend/monitoring/store/variable_mapping_spec.js
index c44bb957166..5164ed1b54b 100644
--- a/spec/frontend/monitoring/store/variable_mapping_spec.js
+++ b/spec/frontend/monitoring/store/variable_mapping_spec.js
@@ -1,4 +1,5 @@
-import { parseTemplatingVariables } from '~/monitoring/stores/variable_mapping';
+import { parseTemplatingVariables, mergeURLVariables } from '~/monitoring/stores/variable_mapping';
+import * as urlUtils from '~/lib/utils/url_utility';
import { mockTemplatingData, mockTemplatingDataResponses } from '../mock_data';
describe('parseTemplatingVariables', () => {
@@ -21,3 +22,73 @@ describe('parseTemplatingVariables', () => {
expect(parseTemplatingVariables(input?.dashboard?.templating)).toEqual(expected);
});
});
+
+describe('mergeURLVariables', () => {
+ beforeEach(() => {
+ jest.spyOn(urlUtils, 'queryToObject');
+ });
+
+ afterEach(() => {
+ urlUtils.queryToObject.mockRestore();
+ });
+
+ it('returns empty object if variables are not defined in yml or URL', () => {
+ urlUtils.queryToObject.mockReturnValueOnce({});
+
+ expect(mergeURLVariables({})).toEqual({});
+ });
+
+ it('returns empty object if variables are defined in URL but not in yml', () => {
+ urlUtils.queryToObject.mockReturnValueOnce({
+ 'var-env': 'one',
+ 'var-instance': 'localhost',
+ });
+
+ expect(mergeURLVariables({})).toEqual({});
+ });
+
+ it('returns yml variables if variables defined in yml but not in the URL', () => {
+ urlUtils.queryToObject.mockReturnValueOnce({});
+
+ const params = {
+ env: 'one',
+ instance: 'localhost',
+ };
+
+ expect(mergeURLVariables(params)).toEqual(params);
+ });
+
+ it('returns yml variables if variables defined in URL do not match with yml variables', () => {
+ const urlParams = {
+ 'var-env': 'one',
+ 'var-instance': 'localhost',
+ };
+ const ymlParams = {
+ pod: { value: 'one' },
+ service: { value: 'database' },
+ };
+ urlUtils.queryToObject.mockReturnValueOnce(urlParams);
+
+ expect(mergeURLVariables(ymlParams)).toEqual(ymlParams);
+ });
+
+ it('returns merged yml and URL variables if there is some match', () => {
+ const urlParams = {
+ 'var-env': 'one',
+ 'var-instance': 'localhost:8080',
+ };
+ const ymlParams = {
+ instance: { value: 'localhost' },
+ service: { value: 'database' },
+ };
+
+ const merged = {
+ instance: { value: 'localhost:8080' },
+ service: { value: 'database' },
+ };
+
+ urlUtils.queryToObject.mockReturnValueOnce(urlParams);
+
+ expect(mergeURLVariables(ymlParams)).toEqual(merged);
+ });
+});
diff --git a/spec/frontend/monitoring/utils_spec.js b/spec/frontend/monitoring/utils_spec.js
index aa5a4459a72..039cf275eea 100644
--- a/spec/frontend/monitoring/utils_spec.js
+++ b/spec/frontend/monitoring/utils_spec.js
@@ -169,8 +169,8 @@ describe('monitoring/utils', () => {
});
});
- describe('getPromCustomVariablesFromUrl', () => {
- const { getPromCustomVariablesFromUrl } = monitoringUtils;
+ describe('templatingVariablesFromUrl', () => {
+ const { templatingVariablesFromUrl } = monitoringUtils;
beforeEach(() => {
jest.spyOn(urlUtils, 'queryToObject');
@@ -195,7 +195,7 @@ describe('monitoring/utils', () => {
'var-pod': 'POD',
});
- expect(getPromCustomVariablesFromUrl()).toEqual(expect.objectContaining({ pod: 'POD' }));
+ expect(templatingVariablesFromUrl()).toEqual(expect.objectContaining({ pod: 'POD' }));
});
it('returns an empty object when no custom variables are present', () => {
@@ -203,7 +203,7 @@ describe('monitoring/utils', () => {
dashboard: '.gitlab/dashboards/custom_dashboard.yml',
});
- expect(getPromCustomVariablesFromUrl()).toStrictEqual({});
+ expect(templatingVariablesFromUrl()).toStrictEqual({});
});
});
@@ -427,76 +427,6 @@ describe('monitoring/utils', () => {
});
});
- describe('mergeURLVariables', () => {
- beforeEach(() => {
- jest.spyOn(urlUtils, 'queryToObject');
- });
-
- afterEach(() => {
- urlUtils.queryToObject.mockRestore();
- });
-
- it('returns empty object if variables are not defined in yml or URL', () => {
- urlUtils.queryToObject.mockReturnValueOnce({});
-
- expect(monitoringUtils.mergeURLVariables({})).toEqual({});
- });
-
- it('returns empty object if variables are defined in URL but not in yml', () => {
- urlUtils.queryToObject.mockReturnValueOnce({
- 'var-env': 'one',
- 'var-instance': 'localhost',
- });
-
- expect(monitoringUtils.mergeURLVariables({})).toEqual({});
- });
-
- it('returns yml variables if variables defined in yml but not in the URL', () => {
- urlUtils.queryToObject.mockReturnValueOnce({});
-
- const params = {
- env: 'one',
- instance: 'localhost',
- };
-
- expect(monitoringUtils.mergeURLVariables(params)).toEqual(params);
- });
-
- it('returns yml variables if variables defined in URL do not match with yml variables', () => {
- const urlParams = {
- 'var-env': 'one',
- 'var-instance': 'localhost',
- };
- const ymlParams = {
- pod: { value: 'one' },
- service: { value: 'database' },
- };
- urlUtils.queryToObject.mockReturnValueOnce(urlParams);
-
- expect(monitoringUtils.mergeURLVariables(ymlParams)).toEqual(ymlParams);
- });
-
- it('returns merged yml and URL variables if there is some match', () => {
- const urlParams = {
- 'var-env': 'one',
- 'var-instance': 'localhost:8080',
- };
- const ymlParams = {
- instance: { value: 'localhost' },
- service: { value: 'database' },
- };
-
- const merged = {
- instance: { value: 'localhost:8080' },
- service: { value: 'database' },
- };
-
- urlUtils.queryToObject.mockReturnValueOnce(urlParams);
-
- expect(monitoringUtils.mergeURLVariables(ymlParams)).toEqual(merged);
- });
- });
-
describe('convertVariablesForURL', () => {
it.each`
input | expected
diff --git a/spec/initializers/lograge_spec.rb b/spec/initializers/lograge_spec.rb
index f283ac100a9..9e5eab4fc6b 100644
--- a/spec/initializers/lograge_spec.rb
+++ b/spec/initializers/lograge_spec.rb
@@ -99,6 +99,8 @@ describe 'lograge', type: :request do
end
context 'with a log subscriber' do
+ include_context 'parsed logs'
+
let(:subscriber) { Lograge::LogSubscribers::ActionController.new }
let(:event) do
@@ -119,16 +121,6 @@ describe 'lograge', type: :request do
)
end
- let(:log_output) { StringIO.new }
- let(:logger) do
- Logger.new(log_output).tap { |logger| logger.formatter = ->(_, _, _, msg) { msg } }
- end
- let(:log_data) { Gitlab::Json.parse(log_output.string) }
-
- before do
- Lograge.logger = logger
- end
-
describe 'with an exception' do
let(:exception) { RuntimeError.new('bad request') }
let(:backtrace) { caller }
diff --git a/spec/lib/api/entities/deploy_key_spec.rb b/spec/lib/api/entities/deploy_key_spec.rb
new file mode 100644
index 00000000000..704dabae63b
--- /dev/null
+++ b/spec/lib/api/entities/deploy_key_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::Entities::DeployKey do
+ describe '#as_json' do
+ subject { entity.as_json }
+
+ let(:deploy_key) { create(:deploy_key, public: true) }
+ let(:entity) { described_class.new(deploy_key) }
+
+ it 'includes basic fields', :aggregate_failures do
+ is_expected.to include(
+ id: deploy_key.id,
+ title: deploy_key.title,
+ created_at: deploy_key.created_at,
+ expires_at: deploy_key.expires_at,
+ key: deploy_key.key
+ )
+ end
+ end
+end
diff --git a/spec/lib/api/entities/deploy_keys_project_spec.rb b/spec/lib/api/entities/deploy_keys_project_spec.rb
new file mode 100644
index 00000000000..a357467d7ce
--- /dev/null
+++ b/spec/lib/api/entities/deploy_keys_project_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::Entities::DeployKeysProject do
+ describe '#as_json' do
+ subject { entity.as_json }
+
+ let(:deploy_keys_project) { create(:deploy_keys_project, :write_access) }
+ let(:entity) { described_class.new(deploy_keys_project) }
+
+ it 'includes basic fields', :aggregate_failures do
+ deploy_key = deploy_keys_project.deploy_key
+
+ is_expected.to include(
+ id: deploy_key.id,
+ title: deploy_key.title,
+ created_at: deploy_key.created_at,
+ expires_at: deploy_key.expires_at,
+ key: deploy_key.key,
+ can_push: deploy_keys_project.can_push
+ )
+ end
+ end
+end
diff --git a/spec/lib/api/entities/ssh_key_spec.rb b/spec/lib/api/entities/ssh_key_spec.rb
new file mode 100644
index 00000000000..25a0fecfb75
--- /dev/null
+++ b/spec/lib/api/entities/ssh_key_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::Entities::SSHKey do
+ describe '#as_json' do
+ subject { entity.as_json }
+
+ let(:key) { create(:key, user: create(:user)) }
+ let(:entity) { described_class.new(key) }
+
+ it 'includes basic fields', :aggregate_failures do
+ is_expected.to include(
+ id: key.id,
+ title: key.title,
+ created_at: key.created_at,
+ expires_at: key.expires_at,
+ key: key.publishable_key
+ )
+ end
+ end
+end
diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb
index 1baa18b53ce..e8cc6bc71ae 100644
--- a/spec/requests/api/deploy_keys_spec.rb
+++ b/spec/requests/api/deploy_keys_spec.rb
@@ -8,7 +8,7 @@ describe API::DeployKeys do
let(:admin) { create(:admin) }
let(:project) { create(:project, creator_id: user.id) }
let(:project2) { create(:project, creator_id: user.id) }
- let(:deploy_key) { create(:deploy_key, public: true, user: user) }
+ let(:deploy_key) { create(:deploy_key, public: true) }
let!(:deploy_keys_project) do
create(:deploy_keys_project, project: project, deploy_key: deploy_key)
@@ -40,32 +40,6 @@ describe API::DeployKeys do
expect(json_response).to be_an Array
expect(json_response.first['id']).to eq(deploy_keys_project.deploy_key.id)
end
-
- it 'returns all deploy keys with comments replaced with'\
- 'a simple identifier of username + hostname' do
- get api('/deploy_keys', admin)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
-
- keys = json_response.map { |key_detail| key_detail['key'] }
- expect(keys).to all(include("#{user.name} (#{Gitlab.config.gitlab.host}"))
- end
-
- context 'N+1 queries' do
- before do
- get api('/deploy_keys', admin)
- end
-
- it 'avoids N+1 queries', :request_store do
- control_count = ActiveRecord::QueryRecorder.new { get api('/deploy_keys', admin) }.count
-
- create_list(:deploy_key, 2, public: true, user: create(:user))
-
- expect { get api('/deploy_keys', admin) }.not_to exceed_query_limit(control_count)
- end
- end
end
end
@@ -82,25 +56,6 @@ describe API::DeployKeys do
expect(json_response).to be_an Array
expect(json_response.first['title']).to eq(deploy_key.title)
end
-
- context 'N+1 queries' do
- before do
- get api("/projects/#{project.id}/deploy_keys", admin)
- end
-
- it 'avoids N+1 queries', :request_store do
- control_count = ActiveRecord::QueryRecorder.new do
- get api("/projects/#{project.id}/deploy_keys", admin)
- end.count
-
- deploy_key = create(:deploy_key, user: create(:user))
- create(:deploy_keys_project, project: project, deploy_key: deploy_key)
-
- expect do
- get api("/projects/#{project.id}/deploy_keys", admin)
- end.not_to exceed_query_limit(control_count)
- end
- end
end
describe 'GET /projects/:id/deploy_keys/:key_id' do
@@ -111,13 +66,6 @@ describe API::DeployKeys do
expect(json_response['title']).to eq(deploy_key.title)
end
- it 'exposes key comment as a simple identifier of username + hostname' do
- get api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", admin)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['key']).to include("#{deploy_key.user_name} (#{Gitlab.config.gitlab.host})")
- end
-
it 'returns 404 Not Found with invalid ID' do
get api("/projects/#{project.id}/deploy_keys/404", admin)
diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb
index d860179f0a7..617587e2fa6 100644
--- a/spec/requests/jwt_controller_spec.rb
+++ b/spec/requests/jwt_controller_spec.rb
@@ -3,19 +3,14 @@
require 'spec_helper'
describe JwtController do
+ include_context 'parsed logs'
+
let(:service) { double(execute: {}) }
let(:service_class) { double(new: service) }
let(:service_name) { 'test' }
let(:parameters) { { service: service_name } }
- let(:log_output) { StringIO.new }
- let(:logger) do
- Logger.new(log_output).tap { |logger| logger.formatter = ->(_, _, _, msg) { msg } }
- end
- let(:log_data) { Gitlab::Json.parse(log_output.string) }
before do
- Lograge.logger = logger
-
stub_const('JwtController::SERVICES', service_name => service_class)
end
diff --git a/spec/services/authorized_project_update/periodic_recalculate_service_spec.rb b/spec/services/authorized_project_update/periodic_recalculate_service_spec.rb
new file mode 100644
index 00000000000..020056da36e
--- /dev/null
+++ b/spec/services/authorized_project_update/periodic_recalculate_service_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AuthorizedProjectUpdate::PeriodicRecalculateService do
+ subject(:service) { described_class.new }
+
+ describe '#execute' do
+ let(:batch_size) { 2 }
+
+ let_it_be(:users) { create_list(:user, 4) }
+
+ before do
+ stub_const('AuthorizedProjectUpdate::PeriodicRecalculateService::BATCH_SIZE', batch_size)
+
+ User.delete([users[1], users[2]])
+ end
+
+ it 'calls AuthorizedProjectUpdate::UserRefreshOverUserRangeWorker' do
+ (1..User.maximum(:id)).each_slice(batch_size).with_index do |batch, index|
+ delay = AuthorizedProjectUpdate::PeriodicRecalculateService::DELAY_INTERVAL * index
+
+ expect(AuthorizedProjectUpdate::UserRefreshOverUserRangeWorker).to(
+ receive(:perform_in).with(delay, *batch.minmax))
+ end
+
+ service.execute
+ end
+ end
+end
diff --git a/spec/services/authorized_project_update/recalculate_for_user_range_service_spec.rb b/spec/services/authorized_project_update/recalculate_for_user_range_service_spec.rb
new file mode 100644
index 00000000000..28cbda6f4fd
--- /dev/null
+++ b/spec/services/authorized_project_update/recalculate_for_user_range_service_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AuthorizedProjectUpdate::RecalculateForUserRangeService do
+ describe '#execute' do
+ let_it_be(:users) { create_list(:user, 2) }
+
+ it 'calls Users::RefreshAuthorizedProjectsService' do
+ users.each do |user|
+ expect(Users::RefreshAuthorizedProjectsService).to(
+ receive(:new).with(user).and_call_original)
+ end
+
+ range = users.map(&:id).minmax
+ described_class.new(*range).execute
+ end
+ end
+end
diff --git a/spec/support/shared_contexts/controllers/logging_shared_context.rb b/spec/support/shared_contexts/controllers/logging_shared_context.rb
new file mode 100644
index 00000000000..986a96f3a8d
--- /dev/null
+++ b/spec/support/shared_contexts/controllers/logging_shared_context.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+# This context replaces the logger and exposes the `log_data` variable for
+# inspection
+RSpec.shared_context 'parsed logs' do
+ let(:logger) do
+ Logger.new(log_output).tap { |logger| logger.formatter = ->(_, _, _, msg) { msg } }
+ end
+
+ let(:log_output) { StringIO.new }
+ let(:log_data) { Gitlab::Json.parse(log_output.string) }
+
+ around do |example|
+ initial_logger = Lograge.logger
+ Lograge.logger = logger
+
+ example.run
+
+ Lograge.logger = initial_logger
+ end
+end
diff --git a/spec/workers/authorized_project_update/periodic_recalculate_worker_spec.rb b/spec/workers/authorized_project_update/periodic_recalculate_worker_spec.rb
new file mode 100644
index 00000000000..fcd073953b6
--- /dev/null
+++ b/spec/workers/authorized_project_update/periodic_recalculate_worker_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AuthorizedProjectUpdate::PeriodicRecalculateWorker do
+ describe '#perform' do
+ it 'calls AuthorizedProjectUpdate::PeriodicRecalculateService' do
+ expect_next_instance_of(AuthorizedProjectUpdate::PeriodicRecalculateService) do |service|
+ expect(service).to receive(:execute)
+ end
+
+ subject.perform
+ end
+
+ context 'feature flag :periodic_project_authorization_recalculation is disabled' do
+ before do
+ stub_feature_flags(periodic_project_authorization_recalculation: false)
+ end
+
+ it 'does not call AuthorizedProjectUpdate::PeriodicRecalculateService' do
+ expect(AuthorizedProjectUpdate::PeriodicRecalculateService).not_to receive(:new)
+
+ subject.perform
+ end
+ end
+ end
+end
diff --git a/spec/workers/authorized_project_update/user_refresh_over_user_range_worker_spec.rb b/spec/workers/authorized_project_update/user_refresh_over_user_range_worker_spec.rb
new file mode 100644
index 00000000000..5d1c405dfd0
--- /dev/null
+++ b/spec/workers/authorized_project_update/user_refresh_over_user_range_worker_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AuthorizedProjectUpdate::UserRefreshOverUserRangeWorker do
+ let(:start_user_id) { 42 }
+ let(:end_user_id) { 4242 }
+
+ describe '#perform' do
+ it 'calls AuthorizedProjectUpdate::RecalculateForUserRangeService' do
+ expect_next_instance_of(AuthorizedProjectUpdate::RecalculateForUserRangeService) do |service|
+ expect(service).to receive(:execute)
+ end
+
+ subject.perform(start_user_id, end_user_id)
+ end
+
+ context 'feature flag :periodic_project_authorization_recalculation is disabled' do
+ before do
+ stub_feature_flags(periodic_project_authorization_recalculation: false)
+ end
+
+ it 'does not call AuthorizedProjectUpdate::RecalculateForUserRangeService' do
+ expect(AuthorizedProjectUpdate::RecalculateForUserRangeService).not_to receive(:new)
+
+ subject.perform(start_user_id, end_user_id)
+ end
+ end
+ end
+end