diff options
Diffstat (limited to 'spec')
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 |