diff options
Diffstat (limited to 'spec/lib/api')
-rw-r--r-- | spec/lib/api/ci/helpers/runner_spec.rb | 34 | ||||
-rw-r--r-- | spec/lib/api/entities/merge_request_basic_spec.rb | 2 | ||||
-rw-r--r-- | spec/lib/api/entities/merge_request_diff_spec.rb | 44 | ||||
-rw-r--r-- | spec/lib/api/entities/ml/mlflow/get_run_spec.rb | 63 | ||||
-rw-r--r-- | spec/lib/api/entities/ml/mlflow/run_info_spec.rb | 2 | ||||
-rw-r--r-- | spec/lib/api/entities/ml/mlflow/run_spec.rb | 20 | ||||
-rw-r--r-- | spec/lib/api/entities/ml/mlflow/search_runs_spec.rb | 37 | ||||
-rw-r--r-- | spec/lib/api/entities/project_spec.rb | 2 | ||||
-rw-r--r-- | spec/lib/api/helpers/packages_helpers_spec.rb | 4 | ||||
-rw-r--r-- | spec/lib/api/helpers_spec.rb | 222 | ||||
-rw-r--r-- | spec/lib/api/ml/mlflow/api_helpers_spec.rb | 24 |
11 files changed, 422 insertions, 32 deletions
diff --git a/spec/lib/api/ci/helpers/runner_spec.rb b/spec/lib/api/ci/helpers/runner_spec.rb index 62b79c77b4a..70504a58af3 100644 --- a/spec/lib/api/ci/helpers/runner_spec.rb +++ b/spec/lib/api/ci/helpers/runner_spec.rb @@ -3,10 +3,18 @@ require 'spec_helper' RSpec.describe API::Ci::Helpers::Runner do - let(:helper) { Class.new { include API::Ci::Helpers::Runner }.new } + let(:helper) do + Class.new do + include API::Ci::Helpers::Runner + include Gitlab::RackLoadBalancingHelpers + end.new + end + + let(:env_hash) { {} } + let(:request) { instance_double(Rack::Request, env: env_hash) } before do - allow(helper).to receive(:env).and_return({}) + allow(helper).to receive(:request).and_return(request) end describe '#current_job', feature_category: :continuous_integration do @@ -16,17 +24,22 @@ RSpec.describe API::Ci::Helpers::Runner do allow(helper).to receive(:params).and_return(id: build.id) expect(Ci::Build.sticking) - .to receive(:stick_or_unstick_request) - .with({}, :build, build.id) + .to receive(:find_caught_up_replica) + .with(:build, build.id) helper.current_job + + stick_object = env_hash[::Gitlab::Database::LoadBalancing::RackMiddleware::STICK_OBJECT].first + expect(stick_object[0]).to eq(Ci::Build.sticking) + expect(stick_object[1]).to eq(:build) + expect(stick_object[2]).to eq(build.id) end it 'does not handle sticking if no build ID was specified' do allow(helper).to receive(:params).and_return({}) expect(Ci::Build.sticking) - .not_to receive(:stick_or_unstick_request) + .not_to receive(:find_caught_up_replica) helper.current_job end @@ -45,17 +58,22 @@ RSpec.describe API::Ci::Helpers::Runner do allow(helper).to receive(:params).and_return(token: runner.token) expect(Ci::Runner.sticking) - .to receive(:stick_or_unstick_request) - .with({}, :runner, runner.token) + .to receive(:find_caught_up_replica) + .with(:runner, runner.token) helper.current_runner + + stick_object = env_hash[::Gitlab::Database::LoadBalancing::RackMiddleware::STICK_OBJECT].first + expect(stick_object[0]).to eq(Ci::Runner.sticking) + expect(stick_object[1]).to eq(:runner) + expect(stick_object[2]).to eq(runner.token) end it 'does not handle sticking if no token was specified' do allow(helper).to receive(:params).and_return({}) expect(Ci::Runner.sticking) - .not_to receive(:stick_or_unstick_request) + .not_to receive(:find_caught_up_replica) helper.current_runner end diff --git a/spec/lib/api/entities/merge_request_basic_spec.rb b/spec/lib/api/entities/merge_request_basic_spec.rb index 89e19f8529e..0cf0a57fa87 100644 --- a/spec/lib/api/entities/merge_request_basic_spec.rb +++ b/spec/lib/api/entities/merge_request_basic_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ::API::Entities::MergeRequestBasic do +RSpec.describe ::API::Entities::MergeRequestBasic, feature_category: :code_review_workflow do let_it_be(:user) { create(:user) } let_it_be(:merge_request) { create(:merge_request) } let_it_be(:labels) { create_list(:label, 3) } diff --git a/spec/lib/api/entities/merge_request_diff_spec.rb b/spec/lib/api/entities/merge_request_diff_spec.rb new file mode 100644 index 00000000000..a6927914316 --- /dev/null +++ b/spec/lib/api/entities/merge_request_diff_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::API::Entities::MergeRequestDiff, feature_category: :code_review_workflow do + let_it_be(:user) { create(:user) } + let_it_be(:merge_request) { create(:merge_request) } + let_it_be(:project) { merge_request.target_project } + let_it_be(:entity) { described_class.new(merge_request.merge_request_diffs.first) } + + before do + merge_request.merge_request_diffs.create!(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') + merge_request.merge_request_diffs.create!(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e') + end + + subject(:json) { entity.as_json } + + it "includes expected fields" do + expected_fields = %i[ + id head_commit_sha base_commit_sha start_commit_sha created_at + merge_request_id state real_size patch_id_sha + ] + + is_expected.to include(*expected_fields) + end + + it "returns expected data" do + merge_request_diff = merge_request.merge_request_diffs.first + + expect(entity.as_json).to eq( + { + id: merge_request_diff.id, + head_commit_sha: merge_request_diff.head_commit_sha, + base_commit_sha: merge_request_diff.base_commit_sha, + start_commit_sha: merge_request_diff.start_commit_sha, + created_at: merge_request_diff.created_at, + merge_request_id: merge_request.id, + state: merge_request_diff.state, + real_size: merge_request_diff.real_size, + patch_id_sha: merge_request_diff.patch_id_sha + } + ) + end +end diff --git a/spec/lib/api/entities/ml/mlflow/get_run_spec.rb b/spec/lib/api/entities/ml/mlflow/get_run_spec.rb new file mode 100644 index 00000000000..513ecdeee3c --- /dev/null +++ b/spec/lib/api/entities/ml/mlflow/get_run_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe API::Entities::Ml::Mlflow::GetRun, feature_category: :mlops do + let_it_be(:candidate) { build(:ml_candidates, :with_metrics_and_params) } + + subject { described_class.new(candidate).as_json } + + it 'has run key' do + expect(subject).to have_key(:run) + end + + it 'has the id' do + expect(subject.dig(:run, :info, :run_id)).to eq(candidate.eid.to_s) + end + + it 'presents the metrics' do + expect(subject.dig(:run, :data, :metrics).size).to eq(candidate.metrics.size) + end + + it 'presents metrics correctly' do + presented_metric = subject.dig(:run, :data, :metrics)[0] + metric = candidate.metrics[0] + + expect(presented_metric[:key]).to eq(metric.name) + expect(presented_metric[:value]).to eq(metric.value) + expect(presented_metric[:timestamp]).to eq(metric.tracked_at) + expect(presented_metric[:step]).to eq(metric.step) + end + + it 'presents the params' do + expect(subject.dig(:run, :data, :params).size).to eq(candidate.params.size) + end + + it 'presents params correctly' do + presented_param = subject.dig(:run, :data, :params)[0] + param = candidate.params[0] + + expect(presented_param[:key]).to eq(param.name) + expect(presented_param[:value]).to eq(param.value) + end + + context 'when candidate has no metrics' do + before do + allow(candidate).to receive(:metrics).and_return([]) + end + + it 'returns empty data' do + expect(subject.dig(:run, :data, :metrics)).to be_empty + end + end + + context 'when candidate has no params' do + before do + allow(candidate).to receive(:params).and_return([]) + end + + it 'data is empty' do + expect(subject.dig(:run, :data, :params)).to be_empty + end + end +end diff --git a/spec/lib/api/entities/ml/mlflow/run_info_spec.rb b/spec/lib/api/entities/ml/mlflow/run_info_spec.rb index 28fef16a532..1664d9f18d2 100644 --- a/spec/lib/api/entities/ml/mlflow/run_info_spec.rb +++ b/spec/lib/api/entities/ml/mlflow/run_info_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe API::Entities::Ml::Mlflow::RunInfo, feature_category: :mlops do - let_it_be(:candidate) { create(:ml_candidates) } + let_it_be(:candidate) { build(:ml_candidates) } subject { described_class.new(candidate, packages_url: 'http://example.com').as_json } diff --git a/spec/lib/api/entities/ml/mlflow/run_spec.rb b/spec/lib/api/entities/ml/mlflow/run_spec.rb index a57f70f788b..58148212a7b 100644 --- a/spec/lib/api/entities/ml/mlflow/run_spec.rb +++ b/spec/lib/api/entities/ml/mlflow/run_spec.rb @@ -3,24 +3,20 @@ require 'spec_helper' RSpec.describe API::Entities::Ml::Mlflow::Run do - let_it_be(:candidate) { create(:ml_candidates, :with_metrics_and_params) } + let_it_be(:candidate) { build(:ml_candidates, :with_metrics_and_params) } subject { described_class.new(candidate).as_json } - it 'has run key' do - expect(subject).to have_key(:run) - end - it 'has the id' do - expect(subject.dig(:run, :info, :run_id)).to eq(candidate.eid.to_s) + expect(subject.dig(:info, :run_id)).to eq(candidate.eid.to_s) end it 'presents the metrics' do - expect(subject.dig(:run, :data, :metrics).size).to eq(candidate.metrics.size) + expect(subject.dig(:data, :metrics).size).to eq(candidate.metrics.size) end it 'presents metrics correctly' do - presented_metric = subject.dig(:run, :data, :metrics)[0] + presented_metric = subject.dig(:data, :metrics)[0] metric = candidate.metrics[0] expect(presented_metric[:key]).to eq(metric.name) @@ -30,11 +26,11 @@ RSpec.describe API::Entities::Ml::Mlflow::Run do end it 'presents the params' do - expect(subject.dig(:run, :data, :params).size).to eq(candidate.params.size) + expect(subject.dig(:data, :params).size).to eq(candidate.params.size) end it 'presents params correctly' do - presented_param = subject.dig(:run, :data, :params)[0] + presented_param = subject.dig(:data, :params)[0] param = candidate.params[0] expect(presented_param[:key]).to eq(param.name) @@ -47,7 +43,7 @@ RSpec.describe API::Entities::Ml::Mlflow::Run do end it 'returns empty data' do - expect(subject.dig(:run, :data, :metrics)).to be_empty + expect(subject.dig(:data, :metrics)).to be_empty end end @@ -57,7 +53,7 @@ RSpec.describe API::Entities::Ml::Mlflow::Run do end it 'data is empty' do - expect(subject.dig(:run, :data, :params)).to be_empty + expect(subject.dig(:data, :params)).to be_empty end end end diff --git a/spec/lib/api/entities/ml/mlflow/search_runs_spec.rb b/spec/lib/api/entities/ml/mlflow/search_runs_spec.rb new file mode 100644 index 00000000000..6ed59d454fa --- /dev/null +++ b/spec/lib/api/entities/ml/mlflow/search_runs_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe API::Entities::Ml::Mlflow::SearchRuns, feature_category: :mlops do + let_it_be(:candidates) { [build_stubbed(:ml_candidates, :with_metrics_and_params), build_stubbed(:ml_candidates)] } + + let(:next_page_token) { 'abcdef' } + + subject { described_class.new({ candidates: candidates, next_page_token: next_page_token }).as_json } + + it 'presents the candidates', :aggregate_failures do + expect(subject[:runs].size).to eq(2) + expect(subject.dig(:runs, 0, :info, :run_id)).to eq(candidates[0].eid.to_s) + expect(subject.dig(:runs, 1, :info, :run_id)).to eq(candidates[1].eid.to_s) + end + + it 'presents metrics', :aggregate_failures do + expect(subject.dig(:runs, 0, :data, :metrics).size).to eq(candidates[0].metrics.size) + expect(subject.dig(:runs, 1, :data, :metrics).size).to eq(0) + + presented_metric = subject.dig(:runs, 0, :data, :metrics, 0, :key) + metric = candidates[0].metrics[0].name + + expect(presented_metric).to eq(metric) + end + + it 'presents params', :aggregate_failures do + expect(subject.dig(:runs, 0, :data, :params).size).to eq(candidates[0].params.size) + expect(subject.dig(:runs, 1, :data, :params).size).to eq(0) + + presented_param = subject.dig(:runs, 0, :data, :params, 0, :key) + param = candidates[0].params[0].name + + expect(presented_param).to eq(param) + end +end diff --git a/spec/lib/api/entities/project_spec.rb b/spec/lib/api/entities/project_spec.rb index 5d18b93228f..2c2cabba5e9 100644 --- a/spec/lib/api/entities/project_spec.rb +++ b/spec/lib/api/entities/project_spec.rb @@ -26,7 +26,7 @@ RSpec.describe ::API::Entities::Project do end end - describe '.service_desk_address' do + describe '.service_desk_address', feature_category: :service_desk do before do allow(project).to receive(:service_desk_enabled?).and_return(true) end diff --git a/spec/lib/api/helpers/packages_helpers_spec.rb b/spec/lib/api/helpers/packages_helpers_spec.rb index 6ba4396c396..bb7b9d688ea 100644 --- a/spec/lib/api/helpers/packages_helpers_spec.rb +++ b/spec/lib/api/helpers/packages_helpers_spec.rb @@ -292,7 +292,7 @@ RSpec.describe API::Helpers::PackagesHelpers, feature_category: :package_registr let(:label) { 'counts.package_events_i_package_push_package_by_deploy_token' } let(:property) { 'i_package_push_package_by_deploy_token' } let(:service_ping_context) do - [Gitlab::Tracking::ServicePingContext.new(data_source: :redis, key_path: 'counts.package_events_i_package_push_package_by_deploy_token').to_h] + [Gitlab::Usage::MetricDefinition.context_for('counts.package_events_i_package_push_package_by_deploy_token').to_h] end it 'logs a snowplow event' do @@ -320,7 +320,7 @@ RSpec.describe API::Helpers::PackagesHelpers, feature_category: :package_registr let(:label) { 'counts.package_events_i_package_pull_package_by_guest' } let(:property) { 'i_package_pull_package_by_guest' } let(:service_ping_context) do - [Gitlab::Tracking::ServicePingContext.new(data_source: :redis, key_path: 'counts.package_events_i_package_pull_package_by_guest').to_h] + [Gitlab::Usage::MetricDefinition.context_for('counts.package_events_i_package_pull_package_by_guest').to_h] end it 'logs a snowplow event' do diff --git a/spec/lib/api/helpers_spec.rb b/spec/lib/api/helpers_spec.rb index 667ee72f821..dd62343890e 100644 --- a/spec/lib/api/helpers_spec.rb +++ b/spec/lib/api/helpers_spec.rb @@ -11,7 +11,6 @@ RSpec.describe API::Helpers, feature_category: :shared do include Rack::Test::Methods let(:user) { build(:user, id: 42) } - let(:request) { instance_double(Rack::Request) } let(:helper) do Class.new(Grape::API::Instance) do helpers API::APIGuard::HelperMethods @@ -36,18 +35,23 @@ RSpec.describe API::Helpers, feature_category: :shared do allow_any_instance_of(described_class).to receive(:initial_current_user).and_return(user) expect(ApplicationRecord.sticking) - .to receive(:stick_or_unstick_request).with(any_args, :user, 42) + .to receive(:find_caught_up_replica).with(:user, 42) get 'user' expect(Gitlab::Json.parse(last_response.body)).to eq({ 'id' => user.id }) + + stick_object = last_request.env[::Gitlab::Database::LoadBalancing::RackMiddleware::STICK_OBJECT].first + expect(stick_object[0]).to eq(User.sticking) + expect(stick_object[1]).to eq(:user) + expect(stick_object[2]).to eq(42) end it 'does not handle sticking if no user could be found' do allow_any_instance_of(described_class).to receive(:initial_current_user).and_return(nil) expect(ApplicationRecord.sticking) - .not_to receive(:stick_or_unstick_request) + .not_to receive(:find_caught_up_replica) get 'user' @@ -243,6 +247,165 @@ RSpec.describe API::Helpers, feature_category: :shared do end end + describe '#find_pipeline' do + let(:pipeline) { create(:ci_pipeline) } + + shared_examples 'pipeline finder' do + context 'when pipeline exists' do + it 'returns requested pipeline' do + expect(helper.find_pipeline(existing_id)).to eq(pipeline) + end + end + + context 'when pipeline does not exists' do + it 'returns nil' do + expect(helper.find_pipeline(non_existing_id)).to be_nil + end + end + + context 'when pipeline id is not provided' do + it 'returns nil' do + expect(helper.find_pipeline(nil)).to be_nil + end + end + end + + context 'when ID is used as an argument' do + let(:existing_id) { pipeline.id } + let(:non_existing_id) { non_existing_record_id } + + it_behaves_like 'pipeline finder' + end + + context 'when string ID is used as an argument' do + let(:existing_id) { pipeline.id.to_s } + let(:non_existing_id) { non_existing_record_id } + + it_behaves_like 'pipeline finder' + end + + context 'when ID is a negative number' do + let(:existing_id) { pipeline.id } + let(:non_existing_id) { -1 } + + it_behaves_like 'pipeline finder' + end + end + + describe '#find_pipeline!' do + let_it_be(:project) { create(:project, :public) } + let_it_be(:pipeline) { create(:ci_pipeline, project: project) } + let_it_be(:user) { create(:user) } + + shared_examples 'private project without access' do + before do + project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value('private')) + allow(helper).to receive(:authenticate_non_public?).and_return(false) + end + + it 'returns not found' do + expect(helper).to receive(:not_found!) + + helper.find_pipeline!(pipeline.id) + end + end + + context 'when user is authenticated' do + before do + allow(helper).to receive(:current_user).and_return(user) + allow(helper).to receive(:initial_current_user).and_return(user) + end + + context 'public project' do + it 'returns requested pipeline' do + expect(helper.find_pipeline!(pipeline.id)).to eq(pipeline) + end + end + + context 'private project' do + it_behaves_like 'private project without access' + + context 'without read pipeline permission' do + before do + allow(helper).to receive(:can?).with(user, :read_pipeline, pipeline).and_return(false) + end + + it_behaves_like 'private project without access' + end + end + + context 'with read pipeline permission' do + before do + allow(helper).to receive(:can?).with(user, :read_pipeline, pipeline).and_return(true) + end + + it 'returns requested pipeline' do + expect(helper.find_pipeline!(pipeline.id)).to eq(pipeline) + end + end + end + + context 'when user is not authenticated' do + before do + allow(helper).to receive(:current_user).and_return(nil) + allow(helper).to receive(:initial_current_user).and_return(nil) + end + + context 'public project' do + it 'returns requested pipeline' do + expect(helper.find_pipeline!(pipeline.id)).to eq(pipeline) + end + end + + context 'private project' do + it_behaves_like 'private project without access' + end + end + + context 'support for IDs and paths as argument' do + let_it_be(:project) { create(:project) } + let_it_be(:pipeline) { create(:ci_pipeline, project: project) } + + let(:user) { project.first_owner } + + before do + allow(helper).to receive(:current_user).and_return(user) + allow(helper).to receive(:authorized_project_scope?).and_return(true) + allow(helper).to receive(:job_token_authentication?).and_return(false) + allow(helper).to receive(:authenticate_non_public?).and_return(false) + end + + shared_examples 'pipeline finder' do + context 'when pipeline exists' do + it 'returns requested pipeline' do + expect(helper.find_pipeline!(existing_id)).to eq(pipeline) + end + + it 'returns nil' do + expect(helper).to receive(:render_api_error!).with('404 Pipeline Not Found', 404) + expect(helper.find_pipeline!(non_existing_id)).to be_nil + end + end + end + + context 'when ID is used as an argument' do + context 'when pipeline id is an integer' do + let(:existing_id) { pipeline.id } + let(:non_existing_id) { non_existing_record_id } + + it_behaves_like 'pipeline finder' + end + + context 'when pipeline id is a string' do + let(:existing_id) { pipeline.id.to_s } + let(:non_existing_id) { "non_existing_record_id" } + + it_behaves_like 'pipeline finder' + end + end + end + end + describe '#find_group!' do let_it_be(:group) { create(:group, :public) } let_it_be(:user) { create(:user) } @@ -628,10 +791,12 @@ RSpec.describe API::Helpers, feature_category: :shared do end it 'logs an exception for unknown event' do - expect(Gitlab::AppLogger).to receive(:warn).with( - "Internal Event tracking event failed for event: #{unknown_event}, message: Unknown event: #{unknown_event}" - ) - + expect(Gitlab::InternalEvents).to receive(:track_event).and_raise(Gitlab::InternalEvents::UnknownEventError, "Unknown event: #{unknown_event}") + expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception) + .with( + instance_of(Gitlab::InternalEvents::UnknownEventError), + event_name: unknown_event + ) helper.track_event(unknown_event, user_id: user_id, namespace_id: namespace_id, project_id: project_id) end @@ -1072,4 +1237,47 @@ RSpec.describe API::Helpers, feature_category: :shared do it_behaves_like 'authorized' end end + + describe "attributes_for_keys" do + let(:hash) do + { + existing_key_with_present_value: 'actual value', + existing_key_with_nil_value: nil, + existing_key_with_false_value: false + } + end + + let(:parameters) { ::ActionController::Parameters.new(hash) } + let(:symbol_keys) do + %i[ + existing_key_with_present_value + existing_key_with_nil_value + existing_key_with_false_value + non_existing_key + ] + end + + let(:string_keys) { symbol_keys.map(&:to_s) } + let(:filtered_attrs) do + { + 'existing_key_with_present_value' => 'actual value', + 'existing_key_with_false_value' => false + } + end + + let(:empty_attrs) { {} } + + where(:params, :keys, :attrs_result) do + ref(:hash) | ref(:symbol_keys) | ref(:filtered_attrs) + ref(:hash) | ref(:string_keys) | ref(:empty_attrs) + ref(:parameters) | ref(:symbol_keys) | ref(:filtered_attrs) + ref(:parameters) | ref(:string_keys) | ref(:filtered_attrs) + end + + with_them do + it 'returns the values for given keys' do + expect(helper.attributes_for_keys(keys, params)).to eq(attrs_result) + end + end + end end diff --git a/spec/lib/api/ml/mlflow/api_helpers_spec.rb b/spec/lib/api/ml/mlflow/api_helpers_spec.rb index 4f6a37c66c4..757a73ed612 100644 --- a/spec/lib/api/ml/mlflow/api_helpers_spec.rb +++ b/spec/lib/api/ml/mlflow/api_helpers_spec.rb @@ -37,4 +37,28 @@ RSpec.describe API::Ml::Mlflow::ApiHelpers, feature_category: :mlops do it { is_expected.to eql("http://localhost/gitlab/root/api/v4/projects/#{user_project.id}/packages/generic") } end end + + describe '#candidates_order_params' do + using RSpec::Parameterized::TableSyntax + + subject { candidates_order_params(params) } + + where(:input, :order_by, :order_by_type, :sort) do + '' | nil | nil | nil + 'created_at' | 'created_at' | 'column' | nil + 'created_at ASC' | 'created_at' | 'column' | 'ASC' + 'metrics.something' | 'something' | 'metric' | nil + 'metrics.something asc' | 'something' | 'metric' | 'asc' + 'metrics.something.blah asc' | 'something' | 'metric' | 'asc' + 'params.something ASC' | nil | nil | 'ASC' + 'metadata.something ASC' | nil | nil | 'ASC' + end + with_them do + let(:params) { { order_by: input } } + + it 'is correct' do + is_expected.to include({ order_by: order_by, order_by_type: order_by_type, sort: sort }) + end + end + end end |