diff options
Diffstat (limited to 'spec/lib')
43 files changed, 975 insertions, 229 deletions
diff --git a/spec/lib/api/entities/project_import_failed_relation_spec.rb b/spec/lib/api/entities/project_import_failed_relation_spec.rb new file mode 100644 index 00000000000..f8330713480 --- /dev/null +++ b/spec/lib/api/entities/project_import_failed_relation_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe API::Entities::ProjectImportFailedRelation do + describe '#as_json' do + subject { entity.as_json } + + let(:import_failure) { build(:import_failure) } + let(:entity) { described_class.new(import_failure) } + + it 'includes basic fields', :aggregate_failures do + expect(subject).to eq( + id: import_failure.id, + created_at: import_failure.created_at, + exception_class: import_failure.exception_class, + exception_message: import_failure.exception_message, + relation_name: import_failure.relation_key, + source: import_failure.source + ) + end + end +end diff --git a/spec/lib/api/entities/project_import_status_spec.rb b/spec/lib/api/entities/project_import_status_spec.rb new file mode 100644 index 00000000000..650f9c156a3 --- /dev/null +++ b/spec/lib/api/entities/project_import_status_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe API::Entities::ProjectImportStatus do + describe '#as_json' do + subject { entity.as_json } + + let(:correlation_id) { 'cid' } + + context 'when import has not finished yet' do + let(:project) { create(:project, :import_scheduled, import_correlation_id: correlation_id) } + let(:entity) { described_class.new(project) } + + it 'includes basic fields and no failures', :aggregate_failures do + expect(subject[:import_status]).to eq('scheduled') + expect(subject[:correlation_id]).to eq(correlation_id) + expect(subject[:import_error]).to be_nil + expect(subject[:failed_relations]).to eq([]) + end + end + + context 'when import has finished with failed relations' do + let(:project) { create(:project, :import_finished, import_correlation_id: correlation_id) } + let(:entity) { described_class.new(project) } + + it 'includes basic fields with failed relations', :aggregate_failures do + create(:import_failure, :hard_failure, project: project, correlation_id_value: correlation_id) + + expect(subject[:import_status]).to eq('finished') + expect(subject[:correlation_id]).to eq(correlation_id) + expect(subject[:import_error]).to be_nil + expect(subject[:failed_relations]).not_to be_empty + end + end + + context 'when import has failed' do + let(:project) { create(:project, :import_failed, import_correlation_id: correlation_id, import_last_error: 'error') } + let(:entity) { described_class.new(project) } + + it 'includes basic fields with import error', :aggregate_failures do + expect(subject[:import_status]).to eq('failed') + expect(subject[:correlation_id]).to eq(correlation_id) + expect(subject[:import_error]).to eq('error') + expect(subject[:failed_relations]).to eq([]) + end + end + end +end diff --git a/spec/lib/api/entities/user_spec.rb b/spec/lib/api/entities/user_spec.rb new file mode 100644 index 00000000000..20524b197e0 --- /dev/null +++ b/spec/lib/api/entities/user_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe API::Entities::User do + let(:user) { create(:user) } + let(:current_user) { create(:user) } + + subject { described_class.new(user, current_user: current_user).as_json } + + it 'exposes correct attributes' do + expect(subject).to include(:bio, :location, :public_email, :skype, :linkedin, :twitter, :website_url, :organization, :job_title, :work_information) + end + + it 'exposes created_at if the current user can read the user profile' do + allow(Ability).to receive(:allowed?).with(current_user, :read_user_profile, user).and_return(true) + + expect(subject).to include(:created_at) + end + + it 'does not expose created_at if the current user cannot read the user profile' do + allow(Ability).to receive(:allowed?).with(current_user, :read_user_profile, user).and_return(false) + + expect(subject).not_to include(:created_at) + end +end diff --git a/spec/lib/api/validations/validators/limit_spec.rb b/spec/lib/api/validations/validators/limit_spec.rb new file mode 100644 index 00000000000..600f74e1fb2 --- /dev/null +++ b/spec/lib/api/validations/validators/limit_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe API::Validations::Validators::Limit do + include ApiValidatorsHelpers + + subject do + described_class.new(['test'], 255, false, scope.new) + end + + context 'valid limit param' do + it 'does not raise a validation error' do + expect_no_validation_error('test' => '123-456') + expect_no_validation_error('test' => '00000000-ffff-0000-ffff-000000000000') + expect_no_validation_error('test' => "#{'a' * 255}") + end + end + + context 'longer than limit param' do + it 'raises a validation error' do + expect_validation_error('test' => "#{'a' * 256}") + end + end +end diff --git a/spec/lib/banzai/pipeline_spec.rb b/spec/lib/banzai/pipeline_spec.rb new file mode 100644 index 00000000000..eeff7287ff5 --- /dev/null +++ b/spec/lib/banzai/pipeline_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Banzai::Pipeline do + describe '.[]' do + subject { described_class[name] } + + shared_examples 'error' do |exception, message| + it do + expect { subject }.to raise_error(exception, message) + end + end + + context 'for nil' do + let(:name) { nil } + + it { is_expected.to eq(Banzai::Pipeline::FullPipeline) } + end + + context 'for symbols' do + context 'when known' do + let(:name) { :full } + + it { is_expected.to eq(Banzai::Pipeline::FullPipeline) } + end + + context 'when unknown' do + let(:name) { :unknown } + + it_behaves_like 'error', NameError, + 'uninitialized constant Banzai::Pipeline::UnknownPipeline' + end + end + + context 'for classes' do + let(:name) { klass } + + context 'subclassing Banzai::Pipeline::BasePipeline' do + let(:klass) { Class.new(Banzai::Pipeline::BasePipeline) } + + it { is_expected.to eq(klass) } + end + + context 'subclassing other types' do + let(:klass) { Class.new(Banzai::RenderContext) } + + before do + stub_const('Foo', klass) + end + + it_behaves_like 'error', ArgumentError, + 'unsupported pipeline name Foo (Class)' + end + end + + context 'for other types' do + let(:name) { 'label' } + + it_behaves_like 'error', ArgumentError, + 'unsupported pipeline name "label" (String)' + end + end +end diff --git a/spec/lib/csv_builder_spec.rb b/spec/lib/csv_builder_spec.rb new file mode 100644 index 00000000000..0d5e2b81b16 --- /dev/null +++ b/spec/lib/csv_builder_spec.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe CsvBuilder do + let(:object) { double(question: :answer) } + let(:fake_relation) { FakeRelation.new([object]) } + let(:subject) { described_class.new(fake_relation, 'Q & A' => :question, 'Reversed' => -> (o) { o.question.to_s.reverse }) } + let(:csv_data) { subject.render } + + before do + stub_const('FakeRelation', Array) + + FakeRelation.class_eval do + def find_each(&block) + each(&block) + end + end + end + + it 'generates a csv' do + expect(csv_data.scan(/(,|\n)/).join).to include ",\n," + end + + it 'uses a temporary file to reduce memory allocation' do + expect(CSV).to receive(:new).with(instance_of(Tempfile)).and_call_original + + subject.render + end + + it 'counts the number of rows' do + subject.render + + expect(subject.rows_written).to eq 1 + end + + describe 'rows_expected' do + it 'uses rows_written if CSV rendered successfully' do + subject.render + + expect(fake_relation).not_to receive(:count) + expect(subject.rows_expected).to eq 1 + end + + it 'falls back to calling .count before rendering begins' do + expect(subject.rows_expected).to eq 1 + end + end + + describe 'truncation' do + let(:big_object) { double(question: 'Long' * 1024) } + let(:row_size) { big_object.question.length * 2 } + let(:fake_relation) { FakeRelation.new([big_object, big_object, big_object]) } + + it 'occurs after given number of bytes' do + expect(subject.render(row_size * 2).length).to be_between(row_size * 2, row_size * 3) + expect(subject).to be_truncated + expect(subject.rows_written).to eq 2 + end + + it 'is ignored by default' do + expect(subject.render.length).to be > row_size * 3 + expect(subject.rows_written).to eq 3 + end + + it 'causes rows_expected to fall back to .count' do + subject.render(0) + + expect(fake_relation).to receive(:count).and_call_original + expect(subject.rows_expected).to eq 3 + end + end + + it 'avoids loading all data in a single query' do + expect(fake_relation).to receive(:find_each) + + subject.render + end + + it 'uses hash keys as headers' do + expect(csv_data).to start_with 'Q & A' + end + + it 'gets data by calling method provided as hash value' do + expect(csv_data).to include 'answer' + end + + it 'allows lamdas to look up more complicated data' do + expect(csv_data).to include 'rewsna' + end + + describe 'excel sanitization' do + let(:dangerous_title) { double(title: "=cmd|' /C calc'!A0 title", description: "*safe_desc") } + let(:dangerous_desc) { double(title: "*safe_title", description: "=cmd|' /C calc'!A0 desc") } + let(:fake_relation) { FakeRelation.new([dangerous_title, dangerous_desc]) } + let(:subject) { described_class.new(fake_relation, 'Title' => 'title', 'Description' => 'description') } + let(:csv_data) { subject.render } + + it 'sanitizes dangerous characters at the beginning of a column' do + expect(csv_data).to include "'=cmd|' /C calc'!A0 title" + expect(csv_data).to include "'=cmd|' /C calc'!A0 desc" + end + + it 'does not sanitize safe symbols at the beginning of a column' do + expect(csv_data).not_to include "'*safe_desc" + expect(csv_data).not_to include "'*safe_title" + end + end +end diff --git a/spec/lib/gitlab/application_context_spec.rb b/spec/lib/gitlab/application_context_spec.rb index 0903ca6f9e8..6674ea059a0 100644 --- a/spec/lib/gitlab/application_context_spec.rb +++ b/spec/lib/gitlab/application_context_spec.rb @@ -42,6 +42,18 @@ describe Gitlab::ApplicationContext do end end + describe '.current_context_include?' do + it 'returns true if the key was present in the context' do + described_class.with_context(caller_id: "Hello") do + expect(described_class.current_context_include?(:caller_id)).to be(true) + end + end + + it 'returns false if the key was not present in the current context' do + expect(described_class.current_context_include?(:caller_id)).to be(false) + end + end + describe '#to_lazy_hash' do let(:user) { build(:user) } let(:project) { build(:project) } diff --git a/spec/lib/gitlab/background_migration/user_mentions/create_resource_user_mention_spec.rb b/spec/lib/gitlab/background_migration/user_mentions/create_resource_user_mention_spec.rb index ff8b9dd1005..d4f52a11ce7 100644 --- a/spec/lib/gitlab/background_migration/user_mentions/create_resource_user_mention_spec.rb +++ b/spec/lib/gitlab/background_migration/user_mentions/create_resource_user_mention_spec.rb @@ -79,7 +79,7 @@ describe Gitlab::BackgroundMigration::UserMentions::CreateResourceUserMention, s context 'migrate commit mentions' do let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') } - let(:commit) { Commit.new(RepoHelpers.sample_commit, project.becomes(Project)) } + let(:commit) { Commit.new(RepoHelpers.sample_commit, project) } let(:commit_user_mentions) { table(:commit_user_mentions) } let!(:note1) { notes.create!(commit_id: commit.id, noteable_type: 'Commit', project_id: project.id, author_id: author.id, note: description_mentions) } diff --git a/spec/lib/gitlab/ci/jwt_spec.rb b/spec/lib/gitlab/ci/jwt_spec.rb new file mode 100644 index 00000000000..f2897708b08 --- /dev/null +++ b/spec/lib/gitlab/ci/jwt_spec.rb @@ -0,0 +1,124 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Jwt do + let(:namespace) { build_stubbed(:namespace) } + let(:project) { build_stubbed(:project, namespace: namespace) } + let(:user) { build_stubbed(:user) } + let(:pipeline) { build_stubbed(:ci_pipeline, ref: 'auto-deploy-2020-03-19') } + let(:build) do + build_stubbed( + :ci_build, + project: project, + user: user, + pipeline: pipeline + ) + end + + describe '#payload' do + subject(:payload) { described_class.new(build, ttl: 30).payload } + + it 'has correct values for the standard JWT attributes' do + Timecop.freeze do + now = Time.now.to_i + + aggregate_failures do + expect(payload[:iss]).to eq(Settings.gitlab.host) + expect(payload[:iat]).to eq(now) + expect(payload[:exp]).to eq(now + 30) + expect(payload[:sub]).to eq("job_#{build.id}") + end + end + end + + it 'has correct values for the custom attributes' do + aggregate_failures do + expect(payload[:namespace_id]).to eq(namespace.id.to_s) + expect(payload[:namespace_path]).to eq(namespace.full_path) + expect(payload[:project_id]).to eq(project.id.to_s) + expect(payload[:project_path]).to eq(project.full_path) + expect(payload[:user_id]).to eq(user.id.to_s) + expect(payload[:user_email]).to eq(user.email) + expect(payload[:user_login]).to eq(user.username) + expect(payload[:pipeline_id]).to eq(pipeline.id.to_s) + expect(payload[:job_id]).to eq(build.id.to_s) + expect(payload[:ref]).to eq(pipeline.source_ref) + end + end + + it 'skips user related custom attributes if build has no user assigned' do + allow(build).to receive(:user).and_return(nil) + + expect { payload }.not_to raise_error + end + + describe 'ref type' do + context 'branches' do + it 'is "branch"' do + expect(payload[:ref_type]).to eq('branch') + end + end + + context 'tags' do + let(:build) { build_stubbed(:ci_build, :on_tag, project: project) } + + it 'is "tag"' do + expect(payload[:ref_type]).to eq('tag') + end + end + + context 'merge requests' do + let(:pipeline) { build_stubbed(:ci_pipeline, :detached_merge_request_pipeline) } + + it 'is "branch"' do + expect(payload[:ref_type]).to eq('branch') + end + end + end + + describe 'ref_protected' do + it 'is false when ref is not protected' do + expect(build).to receive(:protected).and_return(false) + + expect(payload[:ref_protected]).to eq('false') + end + + it 'is true when ref is protected' do + expect(build).to receive(:protected).and_return(true) + + expect(payload[:ref_protected]).to eq('true') + end + end + end + + describe '.for_build' do + let(:rsa_key) { OpenSSL::PKey::RSA.new(Rails.application.secrets.openid_connect_signing_key) } + + subject(:jwt) { described_class.for_build(build) } + + it 'generates JWT with key id' do + _payload, headers = JWT.decode(jwt, rsa_key.public_key, true, { algorithm: 'RS256' }) + + expect(headers['kid']).to eq(rsa_key.public_key.to_jwk['kid']) + end + + it 'generates JWT for the given job with ttl equal to build timeout' do + expect(build).to receive(:metadata_timeout).and_return(3_600) + + payload, _headers = JWT.decode(jwt, rsa_key.public_key, true, { algorithm: 'RS256' }) + ttl = payload["exp"] - payload["iat"] + + expect(ttl).to eq(3_600) + end + + it 'generates JWT for the given job with default ttl if build timeout is not set' do + expect(build).to receive(:metadata_timeout).and_return(nil) + + payload, _headers = JWT.decode(jwt, rsa_key.public_key, true, { algorithm: 'RS256' }) + ttl = payload["exp"] - payload["iat"] + + expect(ttl).to eq(5.minutes.to_i) + end + end +end diff --git a/spec/lib/gitlab/ci/status/bridge/factory_spec.rb b/spec/lib/gitlab/ci/status/bridge/factory_spec.rb new file mode 100644 index 00000000000..1f417781988 --- /dev/null +++ b/spec/lib/gitlab/ci/status/bridge/factory_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Status::Bridge::Factory do + let(:user) { create(:user) } + let(:project) { bridge.project } + let(:status) { factory.fabricate! } + let(:factory) { described_class.new(bridge, user) } + + before do + stub_not_protect_default_branch + + project.add_developer(user) + end + + context 'when bridge is created' do + let(:bridge) { create(:ci_bridge) } + + it 'matches correct core status' do + expect(factory.core_status).to be_a Gitlab::Ci::Status::Created + end + + it 'fabricates status with correct details' do + expect(status.text).to eq s_('CiStatusText|created') + expect(status.icon).to eq 'status_created' + expect(status.favicon).to eq 'favicon_status_created' + expect(status.label).to be_nil + expect(status).not_to have_details + expect(status).not_to have_action + end + end + + context 'when bridge is failed' do + let(:bridge) { create(:ci_bridge, :failed) } + + it 'matches correct core status' do + expect(factory.core_status).to be_a Gitlab::Ci::Status::Failed + end + + it 'matches correct extended statuses' do + expect(factory.extended_statuses) + .to eq [Gitlab::Ci::Status::Bridge::Failed] + end + + it 'fabricates a failed bridge status' do + expect(status).to be_a Gitlab::Ci::Status::Bridge::Failed + end + + it 'fabricates status with correct details' do + expect(status.text).to eq s_('CiStatusText|failed') + expect(status.icon).to eq 'status_failed' + expect(status.favicon).to eq 'favicon_status_failed' + expect(status.label).to be_nil + expect(status.status_tooltip).to eq "#{s_('CiStatusText|failed')} - (unknown failure)" + expect(status).not_to have_details + expect(status).not_to have_action + end + + context 'failed with downstream_pipeline_creation_failed' do + before do + bridge.failure_reason = 'downstream_pipeline_creation_failed' + end + + it 'fabricates correct status_tooltip' do + expect(status.status_tooltip).to eq( + "#{s_('CiStatusText|failed')} - (downstream pipeline can not be created)" + ) + end + end + end +end diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb index adbd7eabd18..bfd9980ee9c 100644 --- a/spec/lib/gitlab/current_settings_spec.rb +++ b/spec/lib/gitlab/current_settings_spec.rb @@ -49,20 +49,16 @@ describe Gitlab::CurrentSettings do end end - context 'with DB unavailable' do - context 'and settings in cache' do - include_context 'with settings in cache' - - it 'fetches the settings from cache without issuing any query' do - expect(ActiveRecord::QueryRecorder.new { described_class.current_application_settings }.count).to eq(0) - end + context 'in a Rake task with DB unavailable' do + before do + allow(Gitlab::Runtime).to receive(:rake?).and_return(true) + # For some reason, `allow(described_class).to receive(:connect_to_db?).and_return(false)` causes issues + # during the initialization phase of the test suite, so instead let's mock the internals of it + allow(ActiveRecord::Base.connection).to receive(:active?).and_return(false) end context 'and no settings in cache' do before do - # For some reason, `allow(described_class).to receive(:connect_to_db?).and_return(false)` causes issues - # during the initialization phase of the test suite, so instead let's mock the internals of it - allow(ActiveRecord::Base.connection).to receive(:active?).and_return(false) expect(ApplicationSetting).not_to receive(:current) end @@ -185,17 +181,6 @@ describe Gitlab::CurrentSettings do expect(described_class.current_application_settings).to eq(:current_settings) end end - - context 'when the application_settings table does not exist' do - it 'returns a FakeApplicationSettings object' do - expect(Gitlab::Database) - .to receive(:cached_table_exists?) - .with('application_settings') - .and_return(false) - - expect(described_class.current_application_settings).to be_a(Gitlab::FakeApplicationSettings) - end - end end end end diff --git a/spec/lib/gitlab/cycle_analytics/group_stage_summary_spec.rb b/spec/lib/gitlab/cycle_analytics/group_stage_summary_spec.rb index 664009f140f..2242895f8ea 100644 --- a/spec/lib/gitlab/cycle_analytics/group_stage_summary_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/group_stage_summary_spec.rb @@ -20,7 +20,7 @@ describe Gitlab::CycleAnalytics::GroupStageSummary do end it "finds the number of issues created after it" do - expect(subject.first[:value]).to eq(2) + expect(subject.first[:value]).to eq('2') end context 'with subgroups' do @@ -29,7 +29,7 @@ describe Gitlab::CycleAnalytics::GroupStageSummary do end it "finds issues from them" do - expect(subject.first[:value]).to eq(3) + expect(subject.first[:value]).to eq('3') end end @@ -41,7 +41,7 @@ describe Gitlab::CycleAnalytics::GroupStageSummary do subject { described_class.new(group, options: { from: Time.now, current_user: user, projects: [project.id, project_2.id] }).data } it 'finds issues from those projects' do - expect(subject.first[:value]).to eq(2) + expect(subject.first[:value]).to eq('2') end end @@ -49,7 +49,7 @@ describe Gitlab::CycleAnalytics::GroupStageSummary do subject { described_class.new(group, options: { from: 10.days.ago, to: Time.now, current_user: user }).data } it 'finds issues from 5 days ago' do - expect(subject.first[:value]).to eq(2) + expect(subject.first[:value]).to eq('2') end end end @@ -62,7 +62,7 @@ describe Gitlab::CycleAnalytics::GroupStageSummary do end it "doesn't find issues from them" do - expect(subject.first[:value]).to eq(2) + expect(subject.first[:value]).to eq('2') end end end @@ -77,7 +77,7 @@ describe Gitlab::CycleAnalytics::GroupStageSummary do end it "finds the number of deploys made created after it" do - expect(subject.second[:value]).to eq(2) + expect(subject.second[:value]).to eq('2') end context 'with subgroups' do @@ -88,7 +88,7 @@ describe Gitlab::CycleAnalytics::GroupStageSummary do end it "finds deploys from them" do - expect(subject.second[:value]).to eq(3) + expect(subject.second[:value]).to eq('3') end end @@ -102,7 +102,7 @@ describe Gitlab::CycleAnalytics::GroupStageSummary do subject { described_class.new(group, options: { from: Time.now, current_user: user, projects: [project.id, project_2.id] }).data } it 'shows deploys from those projects' do - expect(subject.second[:value]).to eq(2) + expect(subject.second[:value]).to eq('2') end end @@ -110,7 +110,7 @@ describe Gitlab::CycleAnalytics::GroupStageSummary do subject { described_class.new(group, options: { from: 10.days.ago, to: Time.now, current_user: user }).data } it 'finds deployments from 5 days ago' do - expect(subject.second[:value]).to eq(2) + expect(subject.second[:value]).to eq('2') end end end @@ -123,7 +123,53 @@ describe Gitlab::CycleAnalytics::GroupStageSummary do end it "doesn't find deploys from them" do - expect(subject.second[:value]).to eq(0) + expect(subject.second[:value]).to eq('-') + end + end + end + + describe '#deployment_frequency' do + let(:from) { 6.days.ago } + let(:to) { nil } + + subject do + described_class.new(group, options: { + from: from, + to: to, + current_user: user + }).data.third + end + + it 'includes the unit: `per day`' do + expect(subject[:unit]).to eq(_('per day')) + end + + before do + Timecop.freeze(5.days.ago) do + create(:deployment, :success, project: project) + end + end + + context 'when `to` is nil' do + it 'includes range until now' do + # 1 deployment over 7 days + expect(subject[:value]).to eq('0.1') + end + end + + context 'when `to` is given' do + let(:from) { 10.days.ago } + let(:to) { 10.days.from_now } + + before do + Timecop.freeze(5.days.from_now) do + create(:deployment, :success, project: project) + end + end + + it 'returns deployment frequency within `from` and `to` range' do + # 2 deployments over 20 days + expect(subject[:value]).to eq('0.1') end end end diff --git a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb index 94edef20296..a86278871ff 100644 --- a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb @@ -20,13 +20,13 @@ describe Gitlab::CycleAnalytics::StageSummary do Timecop.freeze(5.days.ago) { create(:issue, project: project) } Timecop.freeze(5.days.from_now) { create(:issue, project: project) } - expect(subject).to eq(1) + expect(subject).to eq('1') end it "doesn't find issues from other projects" do Timecop.freeze(5.days.from_now) { create(:issue, project: create(:project)) } - expect(subject).to eq(0) + expect(subject).to eq('-') end context 'when `to` parameter is given' do @@ -38,14 +38,14 @@ describe Gitlab::CycleAnalytics::StageSummary do it "doesn't find any record" do options[:to] = Time.now - expect(subject).to eq(0) + expect(subject).to eq('-') end it "finds records created between `from` and `to` range" do options[:from] = 10.days.ago options[:to] = 10.days.from_now - expect(subject).to eq(2) + expect(subject).to eq('2') end end end @@ -57,19 +57,19 @@ describe Gitlab::CycleAnalytics::StageSummary do Timecop.freeze(5.days.ago) { create_commit("Test message", project, user, 'master') } Timecop.freeze(5.days.from_now) { create_commit("Test message", project, user, 'master') } - expect(subject).to eq(1) + expect(subject).to eq('1') end it "doesn't find commits from other projects" do Timecop.freeze(5.days.from_now) { create_commit("Test message", create(:project, :repository), user, 'master') } - expect(subject).to eq(0) + expect(subject).to eq('-') end - it "finds a large (> 100) snumber of commits if present" do + it "finds a large (> 100) number of commits if present" do Timecop.freeze(5.days.from_now) { create_commit("Test message", project, user, 'master', count: 100) } - expect(subject).to eq(100) + expect(subject).to eq('100') end context 'when `to` parameter is given' do @@ -81,14 +81,14 @@ describe Gitlab::CycleAnalytics::StageSummary do it "doesn't find any record" do options[:to] = Time.now - expect(subject).to eq(0) + expect(subject).to eq('-') end it "finds records created between `from` and `to` range" do options[:from] = 10.days.ago options[:to] = 10.days.from_now - expect(subject).to eq(2) + expect(subject).to eq('2') end end @@ -118,7 +118,7 @@ describe Gitlab::CycleAnalytics::StageSummary do Timecop.freeze(5.days.ago) { create(:deployment, :success, project: project) } Timecop.freeze(5.days.from_now) { create(:deployment, :success, project: project) } - expect(subject).to eq(1) + expect(subject).to eq('1') end it "doesn't find commits from other projects" do @@ -126,7 +126,7 @@ describe Gitlab::CycleAnalytics::StageSummary do create(:deployment, :success, project: create(:project, :repository)) end - expect(subject).to eq(0) + expect(subject).to eq('-') end context 'when `to` parameter is given' do @@ -138,14 +138,76 @@ describe Gitlab::CycleAnalytics::StageSummary do it "doesn't find any record" do options[:to] = Time.now - expect(subject).to eq(0) + expect(subject).to eq('-') end it "finds records created between `from` and `to` range" do options[:from] = 10.days.ago options[:to] = 10.days.from_now - expect(subject).to eq(2) + expect(subject).to eq('2') + end + end + end + + describe '#deployment_frequency' do + subject { stage_summary.fourth[:value] } + + it 'includes the unit: `per day`' do + expect(stage_summary.fourth[:unit]).to eq _('per day') + end + + before do + Timecop.freeze(5.days.ago) { create(:deployment, :success, project: project) } + end + + it 'returns 0.0 when there were deploys but the frequency was too low' do + options[:from] = 30.days.ago + + # 1 deployment over 30 days + # frequency of 0.03, rounded off to 0.0 + expect(subject).to eq('0') + end + + it 'returns `-` when there were no deploys' do + options[:from] = 4.days.ago + + # 0 deployment in the last 4 days + expect(subject).to eq('-') + end + + context 'when `to` is nil' do + it 'includes range until now' do + options[:from] = 6.days.ago + options[:to] = nil + + # 1 deployment over 7 days + expect(subject).to eq('0.1') + end + end + + context 'when `to` is given' do + before do + Timecop.freeze(5.days.from_now) { create(:deployment, :success, project: project) } + end + + it 'finds records created between `from` and `to` range' do + options[:from] = 10.days.ago + options[:to] = 10.days.from_now + + # 2 deployments over 20 days + expect(subject).to eq('0.1') + end + + context 'when `from` and `to` are within a day' do + it 'returns the number of deployments made on that day' do + Timecop.freeze(Time.now) do + create(:deployment, :success, project: project) + options[:from] = options[:to] = Time.now + + expect(subject).to eq('1') + end + end end end end diff --git a/spec/lib/gitlab/data_builder/pipeline_spec.rb b/spec/lib/gitlab/data_builder/pipeline_spec.rb index da22da8de0f..519f5873d75 100644 --- a/spec/lib/gitlab/data_builder/pipeline_spec.rb +++ b/spec/lib/gitlab/data_builder/pipeline_spec.rb @@ -83,7 +83,7 @@ describe Gitlab::DataBuilder::Pipeline do expect(merge_request_attrs[:target_branch]).to eq(merge_request.target_branch) expect(merge_request_attrs[:target_project_id]).to eq(merge_request.target_project_id) expect(merge_request_attrs[:state]).to eq(merge_request.state) - expect(merge_request_attrs[:merge_status]).to eq(merge_request.merge_status) + expect(merge_request_attrs[:merge_status]).to eq(merge_request.public_merge_status) expect(merge_request_attrs[:url]).to eq("http://localhost/#{merge_request.target_project.full_path}/-/merge_requests/#{merge_request.iid}") end end diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index 3db9320c021..3a0148615b9 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -215,6 +215,7 @@ describe Gitlab::Database::MigrationHelpers do context 'ON DELETE statements' do context 'on_delete: :nullify' do it 'appends ON DELETE SET NULL statement' do + expect(model).to receive(:with_lock_retries).and_call_original expect(model).to receive(:disable_statement_timeout).and_call_original expect(model).to receive(:execute).with(/statement_timeout/) expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/) @@ -230,6 +231,7 @@ describe Gitlab::Database::MigrationHelpers do context 'on_delete: :cascade' do it 'appends ON DELETE CASCADE statement' do + expect(model).to receive(:with_lock_retries).and_call_original expect(model).to receive(:disable_statement_timeout).and_call_original expect(model).to receive(:execute).with(/statement_timeout/) expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/) @@ -245,6 +247,7 @@ describe Gitlab::Database::MigrationHelpers do context 'on_delete: nil' do it 'appends no ON DELETE statement' do + expect(model).to receive(:with_lock_retries).and_call_original expect(model).to receive(:disable_statement_timeout).and_call_original expect(model).to receive(:execute).with(/statement_timeout/) expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/) @@ -261,6 +264,7 @@ describe Gitlab::Database::MigrationHelpers do context 'when no custom key name is supplied' do it 'creates a concurrent foreign key and validates it' do + expect(model).to receive(:with_lock_retries).and_call_original expect(model).to receive(:disable_statement_timeout).and_call_original expect(model).to receive(:execute).with(/statement_timeout/) expect(model).to receive(:execute).ordered.with(/NOT VALID/) @@ -287,6 +291,7 @@ describe Gitlab::Database::MigrationHelpers do context 'when a custom key name is supplied' do context 'for creating a new foreign key for a column that does not presently exist' do it 'creates a new foreign key' do + expect(model).to receive(:with_lock_retries).and_call_original expect(model).to receive(:disable_statement_timeout).and_call_original expect(model).to receive(:execute).with(/statement_timeout/) expect(model).to receive(:execute).ordered.with(/NOT VALID/) @@ -314,6 +319,7 @@ describe Gitlab::Database::MigrationHelpers do context 'when the supplied key name is different from the existing foreign key name' do it 'creates a new foreign key' do + expect(model).to receive(:with_lock_retries).and_call_original expect(model).to receive(:disable_statement_timeout).and_call_original expect(model).to receive(:execute).with(/statement_timeout/) expect(model).to receive(:execute).ordered.with(/NOT VALID/) diff --git a/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb b/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb index 33d4994f5db..e275ebef2c9 100644 --- a/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb +++ b/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb @@ -9,7 +9,8 @@ describe Gitlab::Diff::Formatters::TextFormatter do start_sha: 456, head_sha: 789, old_path: 'old_path.txt', - new_path: 'new_path.txt' + new_path: 'new_path.txt', + line_range: nil } end diff --git a/spec/lib/gitlab/diff/highlight_cache_spec.rb b/spec/lib/gitlab/diff/highlight_cache_spec.rb index a16e5e185bb..3c128aad976 100644 --- a/spec/lib/gitlab/diff/highlight_cache_spec.rb +++ b/spec/lib/gitlab/diff/highlight_cache_spec.rb @@ -113,7 +113,7 @@ describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do allow(redis).to receive(:info).and_return({ "redis_version" => "3.0.0" }) expect(described_class.gitlab_redis_diff_caching_memory_usage_bytes) - .not_to receive(:observe).and_call_original + .not_to receive(:observe) cache.send(:write_to_redis_hash, diff_hash) end @@ -163,6 +163,56 @@ describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do end end + describe "GZip usage" do + let(:diff_file) do + diffs = merge_request.diffs + raw_diff = diffs.diffable.raw_diffs(diffs.diff_options.merge(paths: ['CHANGELOG'])).first + Gitlab::Diff::File.new(raw_diff, + repository: diffs.project.repository, + diff_refs: diffs.diff_refs, + fallback_diff_refs: diffs.fallback_diff_refs) + end + + context "feature flag :gzip_diff_cache disabled" do + before do + stub_feature_flags(gzip_diff_cache: true) + end + + it "uses ActiveSupport::Gzip when reading from the cache" do + expect(ActiveSupport::Gzip).to receive(:decompress).at_least(:once).and_call_original + + cache.write_if_empty + cache.decorate(diff_file) + end + + it "uses ActiveSupport::Gzip to compress data when writing to cache" do + expect(ActiveSupport::Gzip).to receive(:compress).and_call_original + + cache.send(:write_to_redis_hash, diff_hash) + end + end + + context "feature flag :gzip_diff_cache disabled" do + before do + stub_feature_flags(gzip_diff_cache: false) + end + + it "doesn't use ActiveSupport::Gzip when reading from the cache" do + expect(ActiveSupport::Gzip).not_to receive(:decompress) + + cache.write_if_empty + cache.decorate(diff_file) + end + + it "doesn't use ActiveSupport::Gzip to compress data when writing to cache" do + expect(ActiveSupport::Gzip).not_to receive(:compress) + + expect { cache.send(:write_to_redis_hash, diff_hash) } + .to change { Gitlab::Redis::Cache.with { |r| r.hgetall(cache_key) } } + end + end + end + describe 'metrics' do it 'defines :gitlab_redis_diff_caching_memory_usage_bytes histogram' do expect(described_class).to respond_to(:gitlab_redis_diff_caching_memory_usage_bytes) diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb index 4b11ff16c38..a83c0f35d92 100644 --- a/spec/lib/gitlab/diff/position_spec.rb +++ b/spec/lib/gitlab/diff/position_spec.rb @@ -28,6 +28,7 @@ describe Gitlab::Diff::Position do new_path: "files/ruby/popen.rb", old_line: nil, new_line: 14, + line_range: nil, base_sha: nil, head_sha: nil, start_sha: nil, diff --git a/spec/lib/gitlab/elasticsearch/logs_spec.rb b/spec/lib/gitlab/elasticsearch/logs/lines_spec.rb index 6b9d1dbef99..8b6a19fa2c5 100644 --- a/spec/lib/gitlab/elasticsearch/logs_spec.rb +++ b/spec/lib/gitlab/elasticsearch/logs/lines_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe Gitlab::Elasticsearch::Logs do +describe Gitlab::Elasticsearch::Logs::Lines do let(:client) { Elasticsearch::Transport::Client } let(:es_message_1) { { timestamp: "2019-12-13T14:35:34.034Z", pod: "production-6866bc8974-m4sk4", message: "10.8.2.1 - - [25/Oct/2019:08:03:22 UTC] \"GET / HTTP/1.1\" 200 13" } } diff --git a/spec/lib/gitlab/elasticsearch/logs/pods_spec.rb b/spec/lib/gitlab/elasticsearch/logs/pods_spec.rb new file mode 100644 index 00000000000..0a4ab0780c5 --- /dev/null +++ b/spec/lib/gitlab/elasticsearch/logs/pods_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Elasticsearch::Logs::Pods do + let(:client) { Elasticsearch::Transport::Client } + + let(:es_query) { JSON.parse(fixture_file('lib/elasticsearch/pods_query.json'), symbolize_names: true) } + let(:es_response) { JSON.parse(fixture_file('lib/elasticsearch/pods_response.json')) } + let(:namespace) { "autodevops-deploy-9-production" } + + subject { described_class.new(client) } + + describe '#pods' do + it 'returns the pods' do + expect(client).to receive(:search).with(body: es_query).and_return(es_response) + + result = subject.pods(namespace) + expect(result).to eq([ + { + name: "runner-gitlab-runner-7bbfb5dcb5-p6smb", + container_names: %w[runner-gitlab-runner] + }, + { + name: "elastic-stack-elasticsearch-master-1", + container_names: %w[elasticsearch chown sysctl] + }, + { + name: "ingress-nginx-ingress-controller-76449bcc8d-8qgl6", + container_names: %w[nginx-ingress-controller] + } + ]) + end + end +end diff --git a/spec/lib/gitlab/file_hook_spec.rb b/spec/lib/gitlab/file_hook_spec.rb index d184eb483d4..fda3583289b 100644 --- a/spec/lib/gitlab/file_hook_spec.rb +++ b/spec/lib/gitlab/file_hook_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::FileHook do - let(:file_hook) { Rails.root.join('plugins', 'test.rb') } + let(:file_hook) { Rails.root.join('file_hooks', 'test.rb') } let(:tmp_file) { Tempfile.new('file_hook-dump') } let(:file_hook_source) do diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb index b03c1feb429..2c6aee58326 100644 --- a/spec/lib/gitlab/gitaly_client_spec.rb +++ b/spec/lib/gitlab/gitaly_client_spec.rb @@ -19,6 +19,15 @@ describe Gitlab::GitalyClient do }) end + describe '.query_time', :request_store do + it 'increments query times' do + subject.query_time += 0.451 + subject.query_time += 0.322 + + expect(subject.query_time).to eq(0.773) + end + end + describe '.long_timeout' do context 'default case' do it { expect(subject.long_timeout).to eq(6.hours) } diff --git a/spec/lib/gitlab/grape_logging/loggers/perf_logger_spec.rb b/spec/lib/gitlab/grape_logging/loggers/perf_logger_spec.rb index 6f20b8877e0..09ba4b89a1a 100644 --- a/spec/lib/gitlab/grape_logging/loggers/perf_logger_spec.rb +++ b/spec/lib/gitlab/grape_logging/loggers/perf_logger_spec.rb @@ -21,7 +21,7 @@ describe Gitlab::GrapeLogging::Loggers::PerfLogger do payload = subject.parameters(mock_request, nil) expect(payload[:redis_calls]).to eq(1) - expect(payload[:redis_duration_ms]).to be >= 0 + expect(payload[:redis_duration_s]).to be >= 0 end end end diff --git a/spec/lib/gitlab/grape_logging/loggers/queue_duration_logger_spec.rb b/spec/lib/gitlab/grape_logging/loggers/queue_duration_logger_spec.rb index c0762e9892b..17c0659327d 100644 --- a/spec/lib/gitlab/grape_logging/loggers/queue_duration_logger_spec.rb +++ b/spec/lib/gitlab/grape_logging/loggers/queue_duration_logger_spec.rb @@ -25,11 +25,11 @@ describe Gitlab::GrapeLogging::Loggers::QueueDurationLogger do ) end - it 'returns the correct duration in ms' do + it 'returns the correct duration in seconds' do Timecop.freeze(start_time) do subject.before - expect(subject.parameters(mock_request, nil)).to eq( { 'queue_duration': 1.hour.to_f * 1000 }) + expect(subject.parameters(mock_request, nil)).to eq( { 'queue_duration_s': 1.hour.to_f }) end end end diff --git a/spec/lib/gitlab/import_export/group/tree_restorer_spec.rb b/spec/lib/gitlab/import_export/group/legacy_tree_restorer_spec.rb index fc7e4737d13..3030cdf4cf8 100644 --- a/spec/lib/gitlab/import_export/group/tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/group/legacy_tree_restorer_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe Gitlab::ImportExport::Group::TreeRestorer do +describe Gitlab::ImportExport::Group::LegacyTreeRestorer do include ImportExport::CommonUtil let(:shared) { Gitlab::ImportExport::Shared.new(group) } diff --git a/spec/lib/gitlab/import_export/project/import_task_spec.rb b/spec/lib/gitlab/import_export/project/import_task_spec.rb index f7b9cbaa095..4f4fcd3ad8a 100644 --- a/spec/lib/gitlab/import_export/project/import_task_spec.rb +++ b/spec/lib/gitlab/import_export/project/import_task_spec.rb @@ -2,7 +2,7 @@ require 'rake_helper' -describe Gitlab::ImportExport::Project::ImportTask do +describe Gitlab::ImportExport::Project::ImportTask, :request_store do let(:username) { 'root' } let(:namespace_path) { username } let!(:user) { create(:user, username: username) } diff --git a/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb index 80ae9a08257..04e8bd05666 100644 --- a/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb @@ -6,7 +6,7 @@ def match_mr1_note(content_regex) MergeRequest.find_by(title: 'MR1').notes.select { |n| n.note.match(/#{content_regex}/)}.first end -describe Gitlab::ImportExport::Project::TreeRestorer, quarantine: { flaky: 'https://gitlab.com/gitlab-org/gitlab/-/issues/213793' } do +describe Gitlab::ImportExport::Project::TreeRestorer do include ImportExport::CommonUtil let(:shared) { project.import_export_shared } diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 55b907fff7c..88d7fdaef36 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -595,6 +595,7 @@ ProjectFeature: - builds_access_level - repository_access_level - pages_access_level +- metrics_dashboard_access_level - created_at - updated_at ProtectedBranch::MergeAccessLevel: @@ -811,6 +812,7 @@ ContainerExpirationPolicy: - next_run_at - project_id - name_regex +- name_regex_keep - cadence - older_than - keep_n diff --git a/spec/lib/gitlab/instrumentation_helper_spec.rb b/spec/lib/gitlab/instrumentation_helper_spec.rb index 9788c9f4a3c..858fa044a52 100644 --- a/spec/lib/gitlab/instrumentation_helper_spec.rb +++ b/spec/lib/gitlab/instrumentation_helper_spec.rb @@ -26,7 +26,7 @@ describe Gitlab::InstrumentationHelper do subject expect(payload[:gitaly_calls]).to eq(1) - expect(payload[:gitaly_duration]).to be >= 0 + expect(payload[:gitaly_duration_s]).to be >= 0 expect(payload[:redis_calls]).to be_nil expect(payload[:redis_duration_ms]).to be_nil end @@ -39,7 +39,7 @@ describe Gitlab::InstrumentationHelper do subject expect(payload[:redis_calls]).to eq(1) - expect(payload[:redis_duration_ms]).to be >= 0 + expect(payload[:redis_duration_s]).to be >= 0 expect(payload[:gitaly_calls]).to be_nil expect(payload[:gitaly_duration]).to be_nil end @@ -49,12 +49,12 @@ describe Gitlab::InstrumentationHelper do describe '.queue_duration_for_job' do where(:enqueued_at, :created_at, :time_now, :expected_duration) do "2019-06-01T00:00:00.000+0000" | nil | "2019-06-01T02:00:00.000+0000" | 2.hours.to_f - "2019-06-01T02:00:00.000+0000" | nil | "2019-06-01T02:00:00.001+0000" | 0.001 + "2019-06-01T02:00:00.000+0000" | nil | "2019-06-01T02:00:00.001+0000" | 0.0 "2019-06-01T02:00:00.000+0000" | "2019-05-01T02:00:00.000+0000" | "2019-06-01T02:00:01.000+0000" | 1 - nil | "2019-06-01T02:00:00.000+0000" | "2019-06-01T02:00:00.001+0000" | 0.001 + nil | "2019-06-01T02:00:00.000+0000" | "2019-06-01T02:00:00.001+0000" | 0.0 nil | nil | "2019-06-01T02:00:00.001+0000" | nil "2019-06-01T02:00:00.000+0200" | nil | "2019-06-01T02:00:00.000-0200" | 4.hours.to_f - 1571825569.998168 | nil | "2019-10-23T12:13:16.000+0200" | 26.001832 + 1571825569.998168 | nil | "2019-10-23T12:13:16.000+0200" | 26.00 1571825569 | nil | "2019-10-23T12:13:16.000+0200" | 27 "invalid_date" | nil | "2019-10-23T12:13:16.000+0200" | nil "" | nil | "2019-10-23T12:13:16.000+0200" | nil diff --git a/spec/lib/gitlab/json_spec.rb b/spec/lib/gitlab/json_spec.rb new file mode 100644 index 00000000000..5186ab041da --- /dev/null +++ b/spec/lib/gitlab/json_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Gitlab::Json do + describe ".parse" do + it "parses an object" do + expect(subject.parse('{ "foo": "bar" }')).to eq({ "foo" => "bar" }) + end + + it "parses an array" do + expect(subject.parse('[{ "foo": "bar" }]')).to eq([{ "foo" => "bar" }]) + end + + it "raises an error on a string" do + expect { subject.parse('"foo"') }.to raise_error(JSON::ParserError) + end + + it "raises an error on a true bool" do + expect { subject.parse("true") }.to raise_error(JSON::ParserError) + end + + it "raises an error on a false bool" do + expect { subject.parse("false") }.to raise_error(JSON::ParserError) + end + end + + describe ".parse!" do + it "parses an object" do + expect(subject.parse!('{ "foo": "bar" }')).to eq({ "foo" => "bar" }) + end + + it "parses an array" do + expect(subject.parse!('[{ "foo": "bar" }]')).to eq([{ "foo" => "bar" }]) + end + + it "raises an error on a string" do + expect { subject.parse!('"foo"') }.to raise_error(JSON::ParserError) + end + + it "raises an error on a true bool" do + expect { subject.parse!("true") }.to raise_error(JSON::ParserError) + end + + it "raises an error on a false bool" do + expect { subject.parse!("false") }.to raise_error(JSON::ParserError) + end + end + + describe ".dump" do + it "dumps an object" do + expect(subject.dump({ "foo" => "bar" })).to eq('{"foo":"bar"}') + end + + it "dumps an array" do + expect(subject.dump([{ "foo" => "bar" }])).to eq('[{"foo":"bar"}]') + end + + it "dumps a string" do + expect(subject.dump("foo")).to eq('"foo"') + end + + it "dumps a true bool" do + expect(subject.dump(true)).to eq("true") + end + + it "dumps a false bool" do + expect(subject.dump(false)).to eq("false") + end + end + + describe ".generate" do + it "delegates to the adapter" do + args = [{ foo: "bar" }] + + expect(JSON).to receive(:generate).with(*args) + + subject.generate(*args) + end + end + + describe ".pretty_generate" do + it "delegates to the adapter" do + args = [{ foo: "bar" }] + + expect(JSON).to receive(:pretty_generate).with(*args) + + subject.pretty_generate(*args) + end + end +end diff --git a/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb index c59078449b8..a11a9d08503 100644 --- a/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb @@ -61,4 +61,56 @@ describe Gitlab::Kubernetes::Helm::BaseCommand do it { is_expected.to eq('install-test-class-name') } end + + describe '#service_account_resource' do + let(:resource) do + Kubeclient::Resource.new(metadata: { name: 'tiller', namespace: 'gitlab-managed-apps' }) + end + + subject { base_command.service_account_resource } + + context 'rbac is enabled' do + let(:rbac) { true } + + it 'generates a Kubeclient resource for the tiller ServiceAccount' do + is_expected.to eq(resource) + end + end + + context 'rbac is not enabled' do + let(:rbac) { false } + + it 'generates nothing' do + is_expected.to be_nil + end + end + end + + describe '#cluster_role_binding_resource' do + let(:resource) do + Kubeclient::Resource.new( + metadata: { name: 'tiller-admin' }, + roleRef: { apiGroup: 'rbac.authorization.k8s.io', kind: 'ClusterRole', name: 'cluster-admin' }, + subjects: [{ kind: 'ServiceAccount', name: 'tiller', namespace: 'gitlab-managed-apps' }] + ) + end + + subject { base_command.cluster_role_binding_resource } + + context 'rbac is enabled' do + let(:rbac) { true } + + it 'generates a Kubeclient resource for the ClusterRoleBinding for tiller' do + is_expected.to eq(resource) + end + end + + context 'rbac is not enabled' do + let(:rbac) { false } + + it 'generates nothing' do + is_expected.to be_nil + end + end + end end diff --git a/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb index f87ceb45766..13021a08f9f 100644 --- a/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb @@ -83,56 +83,4 @@ describe Gitlab::Kubernetes::Helm::InitCommand do end end end - - describe '#service_account_resource' do - let(:resource) do - Kubeclient::Resource.new(metadata: { name: 'tiller', namespace: 'gitlab-managed-apps' }) - end - - subject { init_command.service_account_resource } - - context 'rbac is enabled' do - let(:rbac) { true } - - it 'generates a Kubeclient resource for the tiller ServiceAccount' do - is_expected.to eq(resource) - end - end - - context 'rbac is not enabled' do - let(:rbac) { false } - - it 'generates nothing' do - is_expected.to be_nil - end - end - end - - describe '#cluster_role_binding_resource' do - let(:resource) do - Kubeclient::Resource.new( - metadata: { name: 'tiller-admin' }, - roleRef: { apiGroup: 'rbac.authorization.k8s.io', kind: 'ClusterRole', name: 'cluster-admin' }, - subjects: [{ kind: 'ServiceAccount', name: 'tiller', namespace: 'gitlab-managed-apps' }] - ) - end - - subject { init_command.cluster_role_binding_resource } - - context 'rbac is enabled' do - let(:rbac) { true } - - it 'generates a Kubeclient resource for the ClusterRoleBinding for tiller' do - is_expected.to eq(resource) - end - end - - context 'rbac is not enabled' do - let(:rbac) { false } - - it 'generates nothing' do - is_expected.to be_nil - end - end - end end diff --git a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb index f94ceae362a..a5ed8f57bf3 100644 --- a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb @@ -305,20 +305,4 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do is_expected.to eq(resource) end end - - describe '#service_account_resource' do - subject { install_command.service_account_resource } - - it 'returns nothing' do - is_expected.to be_nil - end - end - - describe '#cluster_role_binding_resource' do - subject { install_command.cluster_role_binding_resource } - - it 'returns nothing' do - is_expected.to be_nil - end - end end diff --git a/spec/lib/gitlab/kubernetes/helm/patch_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/patch_command_spec.rb index 064efebdb96..e69570f5371 100644 --- a/spec/lib/gitlab/kubernetes/helm/patch_command_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/patch_command_spec.rb @@ -199,20 +199,4 @@ describe Gitlab::Kubernetes::Helm::PatchCommand do is_expected.to eq(resource) end end - - describe '#service_account_resource' do - subject { patch_command.service_account_resource } - - it 'returns nothing' do - is_expected.to be_nil - end - end - - describe '#cluster_role_binding_resource' do - subject { patch_command.cluster_role_binding_resource } - - it 'returns nothing' do - is_expected.to be_nil - end - end end diff --git a/spec/lib/gitlab/project_template_spec.rb b/spec/lib/gitlab/project_template_spec.rb index ddc41e64147..aa18a1a843c 100644 --- a/spec/lib/gitlab/project_template_spec.rb +++ b/spec/lib/gitlab/project_template_spec.rb @@ -19,6 +19,7 @@ describe Gitlab::ProjectTemplate do described_class.new('plainhtml', 'Pages/Plain HTML', 'Everything you need to get started using a plain HTML Pages site.', 'https://gitlab.com/pages/plain-html'), described_class.new('gitbook', 'Pages/GitBook', 'Everything you need to get started using a GitBook Pages site.', 'https://gitlab.com/pages/gitbook'), described_class.new('hexo', 'Pages/Hexo', 'Everything you need to get started using a Hexo Pages site.', 'https://gitlab.com/pages/hexo'), + described_class.new('sse_middleman', 'Static Site Editor/Middleman', _('Middleman project with Static Site Editor support'), 'https://gitlab.com/gitlab-org/project-templates/static-site-editor-middleman'), described_class.new('nfhugo', 'Netlify/Hugo', _('A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfhugo'), described_class.new('nfjekyll', 'Netlify/Jekyll', _('A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfjekyll'), described_class.new('nfplainhtml', 'Netlify/Plain HTML', _('A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfplain-html'), diff --git a/spec/lib/gitlab/prometheus/adapter_spec.rb b/spec/lib/gitlab/prometheus/adapter_spec.rb index 202bf65f92b..afee95467fa 100644 --- a/spec/lib/gitlab/prometheus/adapter_spec.rb +++ b/spec/lib/gitlab/prometheus/adapter_spec.rb @@ -19,6 +19,14 @@ describe Gitlab::Prometheus::Adapter do it 'return prometheus service as prometheus adapter' do expect(subject.prometheus_adapter).to eq(prometheus_service) end + + context 'with cluster with prometheus available' do + let!(:prometheus) { create(:clusters_applications_prometheus, :installed, cluster: cluster) } + + it 'returns prometheus service' do + expect(subject.prometheus_adapter).to eq(prometheus_service) + end + end end context "prometheus service can't execute queries" do diff --git a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb index db7c5f771b7..f4b939c3013 100644 --- a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb +++ b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb @@ -42,11 +42,10 @@ describe Gitlab::SidekiqLogging::StructuredLogger do start_payload.merge( 'message' => 'TestWorker JID-da883554ee4fe414012f5f42: done: 0.0 sec', 'job_status' => 'done', - 'duration' => 0.0, + 'duration_s' => 0.0, 'completed_at' => timestamp.to_f, - 'cpu_s' => 1.111112, - 'db_duration' => 0, - 'db_duration_s' => 0 + 'cpu_s' => 1.11, + 'db_duration_s' => 0.0 ) end let(:exception_payload) do @@ -160,11 +159,11 @@ describe Gitlab::SidekiqLogging::StructuredLogger do let(:timing_data) do { gitaly_calls: 10, - gitaly_duration: 10000, + gitaly_duration_s: 10000, rugged_calls: 1, - rugged_duration_ms: 5000, + rugged_duration_s: 5000, redis_calls: 3, - redis_duration_ms: 1234 + redis_duration_s: 1234 } end @@ -193,12 +192,11 @@ describe Gitlab::SidekiqLogging::StructuredLogger do let(:expected_start_payload) { start_payload.except('args') } let(:expected_end_payload) do - end_payload.except('args').merge('cpu_s' => a_value > 0) + end_payload.except('args').merge('cpu_s' => a_value >= 0) end let(:expected_end_payload_with_db) do expected_end_payload.merge( - 'db_duration' => a_value >= 100, 'db_duration_s' => a_value >= 0.1 ) end @@ -226,7 +224,7 @@ describe Gitlab::SidekiqLogging::StructuredLogger do let(:time) { { duration: 0.1231234, cputime: 1.2342345 } } let(:payload) { { 'class' => 'my-class', 'message' => 'my-message', 'job_status' => 'my-job-status' } } let(:current_utc_time) { Time.now.utc } - let(:payload_with_time_keys) { { 'class' => 'my-class', 'message' => 'my-message', 'job_status' => 'my-job-status', 'duration' => 0.123123, 'cpu_s' => 1.234235, 'completed_at' => current_utc_time.to_f } } + let(:payload_with_time_keys) { { 'class' => 'my-class', 'message' => 'my-message', 'job_status' => 'my-job-status', 'duration_s' => 0.12, 'cpu_s' => 1.23, 'completed_at' => current_utc_time.to_f } } subject { described_class.new } diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/server_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/server_spec.rb index 0ea248fbcf1..312ebd30a76 100644 --- a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/server_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/server_spec.rb @@ -21,18 +21,9 @@ describe Gitlab::SidekiqMiddleware::DuplicateJobs::Server, :clean_gitlab_redis_q end around do |example| - Sidekiq::Testing.inline! { example.run } - end - - before(:context) do - Sidekiq::Testing.server_middleware do |chain| + with_sidekiq_server_middleware do |chain| chain.add described_class - end - end - - after(:context) do - Sidekiq::Testing.server_middleware do |chain| - chain.remove described_class + Sidekiq::Testing.inline! { example.run } end end diff --git a/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb b/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb index f64ebece930..fdf643a8ad1 100644 --- a/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb @@ -41,18 +41,9 @@ describe Gitlab::SidekiqMiddleware::WorkerContext::Server do end around do |example| - Sidekiq::Testing.inline! { example.run } - end - - before(:context) do - Sidekiq::Testing.server_middleware do |chain| + with_sidekiq_server_middleware do |chain| chain.add described_class - end - end - - after(:context) do - Sidekiq::Testing.server_middleware do |chain| - chain.remove described_class + Sidekiq::Testing.inline! { example.run } end end diff --git a/spec/lib/gitlab/sidekiq_middleware_spec.rb b/spec/lib/gitlab/sidekiq_middleware_spec.rb index 32c1807ba6e..752ec6a0a3f 100644 --- a/spec/lib/gitlab/sidekiq_middleware_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware_spec.rb @@ -28,11 +28,16 @@ describe Gitlab::SidekiqMiddleware do # 2) yielding exactly once describe '.server_configurator' do around do |example| - original = Sidekiq::Testing.server_middleware.dup - - example.run - - Sidekiq::Testing.instance_variable_set :@server_chain, original + with_sidekiq_server_middleware do |chain| + described_class.server_configurator( + metrics: metrics, + arguments_logger: arguments_logger, + memory_killer: memory_killer, + request_store: request_store + ).call(chain) + + example.run + end end let(:middleware_expected_args) { [a_kind_of(worker_class), hash_including({ 'args' => job_args }), anything] } @@ -54,21 +59,17 @@ describe Gitlab::SidekiqMiddleware do end let(:enabled_sidekiq_middlewares) { all_sidekiq_middlewares - disabled_sidekiq_middlewares } - before do - Sidekiq::Testing.server_middleware.clear - Sidekiq::Testing.server_middleware(&described_class.server_configurator( - metrics: metrics, - arguments_logger: arguments_logger, - memory_killer: memory_killer, - request_store: request_store - )) - - enabled_sidekiq_middlewares.each do |middleware| - expect_any_instance_of(middleware).to receive(:call).with(*middleware_expected_args).once.and_call_original - end + shared_examples "a server middleware chain" do + it "passes through the right server middlewares" do + enabled_sidekiq_middlewares.each do |middleware| + expect_any_instance_of(middleware).to receive(:call).with(*middleware_expected_args).once.and_call_original + end - disabled_sidekiq_middlewares.each do |middleware| - expect_any_instance_of(Gitlab::SidekiqMiddleware::ArgumentsLogger).not_to receive(:call) + disabled_sidekiq_middlewares.each do |middleware| + expect_any_instance_of(middleware).not_to receive(:call) + end + + worker_class.perform_async(*job_args) end end @@ -86,9 +87,7 @@ describe Gitlab::SidekiqMiddleware do ] end - it "passes through server middlewares" do - worker_class.perform_async(*job_args) - end + it_behaves_like "a server middleware chain" end context "all optional middlewares on" do @@ -98,9 +97,7 @@ describe Gitlab::SidekiqMiddleware do let(:request_store) { true } let(:disabled_sidekiq_middlewares) { [] } - it "passes through server middlewares" do - worker_class.perform_async(*job_args) - end + it_behaves_like "a server middleware chain" context "server metrics" do let(:gitaly_histogram) { double(:gitaly_histogram) } diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb index 56d6bf1c788..47b9a67f54f 100644 --- a/spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb +++ b/spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb @@ -3,7 +3,8 @@ require 'spec_helper' describe Gitlab::SlashCommands::Presenters::IssueShow do - let(:project) { create(:project) } + let(:user) { create(:user, :with_avatar) } + let(:project) { create(:project, creator: user) } let(:issue) { create(:issue, project: project) } let(:attachment) { subject[:attachments].first } @@ -15,6 +16,7 @@ describe Gitlab::SlashCommands::Presenters::IssueShow do expect(subject[:response_type]).to be(:in_channel) expect(subject).to have_key(:attachments) expect(attachment[:title]).to start_with(issue.title) + expect(attachment[:author_icon]).to eq(user.avatar_url(only_path: false)) end context 'with upvotes' do diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb index d3780d22241..e34367cbbf9 100644 --- a/spec/lib/gitlab/utils_spec.rb +++ b/spec/lib/gitlab/utils_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' describe Gitlab::Utils do delegate :to_boolean, :boolean_to_yes_no, :slugify, :random_string, :which, :ensure_array_from_string, :to_exclusive_sentence, :bytes_to_megabytes, - :append_path, :check_path_traversal!, to: :described_class + :append_path, :check_path_traversal!, :ms_to_round_sec, to: :described_class describe '.check_path_traversal!' do it 'detects path traversal at the start of the string' do @@ -55,6 +55,22 @@ describe Gitlab::Utils do end end + describe '.ms_to_round_sec' do + using RSpec::Parameterized::TableSyntax + + where(:original, :expected) do + 1999.8999 | 2 + 12384 | 12.38 + 333 | 0.33 + end + + with_them do + it "returns rounded seconds" do + expect(ms_to_round_sec(original)).to eq(expected) + end + end + end + describe '.to_exclusive_sentence' do it 'calls #to_sentence on the array' do array = double diff --git a/spec/lib/marginalia_spec.rb b/spec/lib/marginalia_spec.rb index d4b84c5cdc4..2f446694083 100644 --- a/spec/lib/marginalia_spec.rb +++ b/spec/lib/marginalia_spec.rb @@ -24,20 +24,6 @@ describe 'Marginalia spec' do end end - def add_sidekiq_middleware - # Reference: https://github.com/mperham/sidekiq/wiki/Testing#testing-server-middlewaresidekiq - # Sidekiq test harness fakes worker without its server middlewares, so include instrumentation to 'Sidekiq::Testing' server middleware. - Sidekiq::Testing.server_middleware do |chain| - chain.add Marginalia::SidekiqInstrumentation::Middleware - end - end - - def remove_sidekiq_middleware - Sidekiq::Testing.server_middleware do |chain| - chain.remove Marginalia::SidekiqInstrumentation::Middleware - end - end - def stub_feature(value) allow(Gitlab::Marginalia).to receive(:cached_feature_enabled?).and_return(value) end @@ -88,20 +74,16 @@ describe 'Marginalia spec' do end describe 'for Sidekiq worker jobs' do - before(:all) do - add_sidekiq_middleware - - # Because of faking, 'Sidekiq.server?' does not work so implicitly set application name which is done in config/initializers/0_marginalia.rb - Marginalia.application_name = "sidekiq" + around do |example| + with_sidekiq_server_middleware do |chain| + chain.add Marginalia::SidekiqInstrumentation::Middleware + Marginalia.application_name = "sidekiq" + example.run + end end after(:all) do MarginaliaTestJob.clear - remove_sidekiq_middleware - end - - around do |example| - Sidekiq::Testing.fake! { example.run } end before do |