diff options
Diffstat (limited to 'spec/models')
-rw-r--r-- | spec/models/ci/build_spec.rb | 52 | ||||
-rw-r--r-- | spec/models/ci/pipeline_spec.rb | 55 | ||||
-rw-r--r-- | spec/models/clusters/applications/prometheus_spec.rb | 10 | ||||
-rw-r--r-- | spec/models/commit_spec.rb | 84 | ||||
-rw-r--r-- | spec/models/commit_status_spec.rb | 6 | ||||
-rw-r--r-- | spec/models/concerns/issuable_spec.rb | 13 | ||||
-rw-r--r-- | spec/models/concerns/redis_cacheable_spec.rb | 83 | ||||
-rw-r--r-- | spec/models/concerns/sortable_spec.rb | 108 | ||||
-rw-r--r-- | spec/models/generic_commit_status_spec.rb | 6 | ||||
-rw-r--r-- | spec/models/repository_spec.rb | 14 |
10 files changed, 411 insertions, 20 deletions
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index dc810489011..96173889ccd 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -629,6 +629,14 @@ describe Ci::Build do it { is_expected.to eq('review/host') } end + + context 'when using persisted variables' do + let(:build) do + create(:ci_build, environment: 'review/x$CI_BUILD_ID') + end + + it { is_expected.to eq('review/x') } + end end describe '#starts_environment?' do @@ -1270,6 +1278,46 @@ describe Ci::Build do end end + describe '#playable?' do + context 'when build is a manual action' do + context 'when build has been skipped' do + subject { build_stubbed(:ci_build, :manual, status: :skipped) } + + it { is_expected.not_to be_playable } + end + + context 'when build has been canceled' do + subject { build_stubbed(:ci_build, :manual, status: :canceled) } + + it { is_expected.to be_playable } + end + + context 'when build is successful' do + subject { build_stubbed(:ci_build, :manual, status: :success) } + + it { is_expected.to be_playable } + end + + context 'when build has failed' do + subject { build_stubbed(:ci_build, :manual, status: :failed) } + + it { is_expected.to be_playable } + end + + context 'when build is a manual untriggered action' do + subject { build_stubbed(:ci_build, :manual, status: :manual) } + + it { is_expected.to be_playable } + end + end + + context 'when build is not a manual action' do + subject { build_stubbed(:ci_build, :success) } + + it { is_expected.not_to be_playable } + end + end + describe 'project settings' do describe '#allow_git_fetch' do it 'return project allow_git_fetch configuration' do @@ -1485,6 +1533,7 @@ describe Ci::Build do let(:container_registry_enabled) { false } let(:predefined_variables) do [ + { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true }, { key: 'CI_JOB_ID', value: build.id.to_s, public: true }, { key: 'CI_JOB_TOKEN', value: build.token, public: false }, { key: 'CI_BUILD_ID', value: build.id.to_s, public: true }, @@ -1516,7 +1565,6 @@ describe Ci::Build do { key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true }, { key: 'CI_PROJECT_URL', value: project.web_url, public: true }, { key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true }, - { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true }, { key: 'CI_CONFIG_PATH', value: pipeline.ci_yaml_file_path, public: true }, { key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true }, { key: 'CI_COMMIT_MESSAGE', value: pipeline.git_commit_message, public: true }, @@ -2044,7 +2092,7 @@ describe Ci::Build do let(:deploy_token_variables) do [ - { key: 'CI_DEPLOY_USER', value: deploy_token.name, public: true }, + { key: 'CI_DEPLOY_USER', value: deploy_token.username, public: true }, { key: 'CI_DEPLOY_PASSWORD', value: deploy_token.token, public: false } ] end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index ddd66a6be87..e4f4c62bd22 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -167,13 +167,39 @@ describe Ci::Pipeline, :mailer do end end + describe '#persisted_variables' do + context 'when pipeline is not persisted yet' do + subject { build(:ci_pipeline).persisted_variables } + + it 'does not contain some variables' do + keys = subject.map { |variable| variable[:key] } + + expect(keys).not_to include 'CI_PIPELINE_ID' + end + end + + context 'when pipeline is persisted' do + subject { build_stubbed(:ci_pipeline).persisted_variables } + + it 'does contains persisted variables' do + keys = subject.map { |variable| variable[:key] } + + expect(keys).to eq %w[CI_PIPELINE_ID] + end + end + end + describe '#predefined_variables' do subject { pipeline.predefined_variables } it 'includes all predefined variables in a valid order' do keys = subject.map { |variable| variable[:key] } - expect(keys).to eq %w[CI_PIPELINE_ID CI_CONFIG_PATH CI_PIPELINE_SOURCE CI_COMMIT_MESSAGE CI_COMMIT_TITLE CI_COMMIT_DESCRIPTION] + expect(keys).to eq %w[CI_CONFIG_PATH + CI_PIPELINE_SOURCE + CI_COMMIT_MESSAGE + CI_COMMIT_TITLE + CI_COMMIT_DESCRIPTION] end end @@ -774,6 +800,33 @@ describe Ci::Pipeline, :mailer do end end + describe '#number_of_warnings' do + it 'returns the number of warnings' do + create(:ci_build, :allowed_to_fail, :failed, pipeline: pipeline, name: 'rubocop') + + expect(pipeline.number_of_warnings).to eq(1) + end + + it 'supports eager loading of the number of warnings' do + pipeline2 = create(:ci_empty_pipeline, status: :created, project: project) + + create(:ci_build, :allowed_to_fail, :failed, pipeline: pipeline, name: 'rubocop') + create(:ci_build, :allowed_to_fail, :failed, pipeline: pipeline2, name: 'rubocop') + + pipelines = project.pipelines.to_a + + pipelines.each(&:number_of_warnings) + + # To run the queries we need to actually use the lazy objects, which we do + # by just sending "to_i" to them. + amount = ActiveRecord::QueryRecorder + .new { pipelines.each { |p| p.number_of_warnings.to_i } } + .count + + expect(amount).to eq(1) + end + end + shared_context 'with some outdated pipelines' do before do create_pipeline(:canceled, 'ref', 'A', project) diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb index aeca6ee903a..407e2fc598a 100644 --- a/spec/models/clusters/applications/prometheus_spec.rb +++ b/spec/models/clusters/applications/prometheus_spec.rb @@ -85,6 +85,16 @@ describe Clusters::Applications::Prometheus do it 'copies options and headers from kube client to proxy client' do expect(subject.prometheus_client.options).to eq(kube_client.rest_client.options.merge(headers: kube_client.headers)) end + + context 'when cluster is not reachable' do + before do + allow(kube_client).to receive(:proxy_url).and_raise(Kubeclient::HttpError.new(401, 'Unauthorized', nil)) + end + + it 'returns nil' do + expect(subject.prometheus_client).to be_nil + end + end end end diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 448b813c2e1..090f91168ad 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -52,22 +52,98 @@ describe Commit do end end - describe '#author' do + describe '#author', :request_store do it 'looks up the author in a case-insensitive way' do user = create(:user, email: commit.author_email.upcase) expect(commit.author).to eq(user) end - it 'caches the author', :request_store do + it 'caches the author' do user = create(:user, email: commit.author_email) - expect(User).to receive(:find_by_any_email).and_call_original expect(commit.author).to eq(user) + key = "Commit:author:#{commit.author_email.downcase}" - expect(RequestStore.store[key]).to eq(user) + expect(RequestStore.store[key]).to eq(user) expect(commit.author).to eq(user) end + + context 'using eager loading' do + let!(:alice) { create(:user, email: 'alice@example.com') } + let!(:bob) { create(:user, email: 'hunter2@example.com') } + + let(:alice_commit) do + described_class.new(RepoHelpers.sample_commit, project).tap do |c| + c.author_email = 'alice@example.com' + end + end + + let(:bob_commit) do + # The commit for Bob uses one of his alternative Emails, instead of the + # primary one. + described_class.new(RepoHelpers.sample_commit, project).tap do |c| + c.author_email = 'bob@example.com' + end + end + + let(:eve_commit) do + described_class.new(RepoHelpers.sample_commit, project).tap do |c| + c.author_email = 'eve@example.com' + end + end + + let!(:commits) { [alice_commit, bob_commit, eve_commit] } + + before do + create(:email, user: bob, email: 'bob@example.com') + end + + it 'executes only two SQL queries' do + recorder = ActiveRecord::QueryRecorder.new do + # Running this first ensures we don't run one query for every + # commit. + commits.each(&:lazy_author) + + # This forces the execution of the SQL queries necessary to load the + # data. + commits.each { |c| c.author.try(:id) } + end + + expect(recorder.count).to eq(2) + end + + it "preloads the authors for Commits matching a user's primary Email" do + commits.each(&:lazy_author) + + expect(alice_commit.author).to eq(alice) + end + + it "preloads the authors for Commits using a User's alternative Email" do + commits.each(&:lazy_author) + + expect(bob_commit.author).to eq(bob) + end + + it 'sets the author to Nil if an author could not be found for a Commit' do + commits.each(&:lazy_author) + + expect(eve_commit.author).to be_nil + end + + it 'does not execute SQL queries once the authors are preloaded' do + commits.each(&:lazy_author) + commits.each { |c| c.author.try(:id) } + + recorder = ActiveRecord::QueryRecorder.new do + alice_commit.author + bob_commit.author + eve_commit.author + end + + expect(recorder.count).to be_zero + end + end end describe '#to_reference' do diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 2ed29052dc1..f3f2bc28d2c 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -565,4 +565,10 @@ describe CommitStatus do it_behaves_like 'commit status enqueued' end end + + describe '#present' do + subject { commit_status.present } + + it { is_expected.to be_a(CommitStatusPresenter) } + end end diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 3d3092b8ac9..bd6bf5b0712 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -266,6 +266,19 @@ describe Issuable do end end + describe '#time_estimate=' do + it 'coerces the value below Gitlab::Database::MAX_INT_VALUE' do + expect { issue.time_estimate = 100 }.to change { issue.time_estimate }.to(100) + expect { issue.time_estimate = Gitlab::Database::MAX_INT_VALUE + 100 }.to change { issue.time_estimate }.to(Gitlab::Database::MAX_INT_VALUE) + end + + it 'skips coercion for not Integer values' do + expect { issue.time_estimate = nil }.to change { issue.time_estimate }.to(nil) + expect { issue.time_estimate = 'invalid time' }.not_to raise_error(StandardError) + expect { issue.time_estimate = 22.33 }.not_to raise_error(StandardError) + end + end + describe '#to_hook_data' do let(:builder) { double } diff --git a/spec/models/concerns/redis_cacheable_spec.rb b/spec/models/concerns/redis_cacheable_spec.rb index 3d7963120b6..23c6c6233e9 100644 --- a/spec/models/concerns/redis_cacheable_spec.rb +++ b/spec/models/concerns/redis_cacheable_spec.rb @@ -1,21 +1,36 @@ require 'spec_helper' describe RedisCacheable do - let(:model) { double } + let(:model) do + Struct.new(:id, :attributes) do + def read_attribute(attribute) + attributes[attribute] + end + + def cast_value_from_cache(attribute, cached_value) + cached_value + end + + def has_attribute?(attribute) + attributes.has_key?(attribute) + end + end + end + + let(:payload) { { name: 'value', time: Time.zone.now } } + let(:instance) { model.new(1, payload) } + let(:cache_key) { instance.__send__(:cache_attribute_key) } before do - model.extend(described_class) - allow(model).to receive(:cache_attribute_key).and_return('key') + model.include(described_class) end describe '#cached_attribute' do - let(:payload) { { attribute: 'value' } } - - subject { model.cached_attribute(payload.keys.first) } + subject { instance.cached_attribute(payload.keys.first) } it 'gets the cache attribute' do Gitlab::Redis::SharedState.with do |redis| - expect(redis).to receive(:get).with('key') + expect(redis).to receive(:get).with(cache_key) .and_return(payload.to_json) end @@ -24,16 +39,62 @@ describe RedisCacheable do end describe '#cache_attributes' do - let(:values) { { name: 'new_name' } } - - subject { model.cache_attributes(values) } + subject { instance.cache_attributes(payload) } it 'sets the cache attributes' do Gitlab::Redis::SharedState.with do |redis| - expect(redis).to receive(:set).with('key', values.to_json, anything) + expect(redis).to receive(:set).with(cache_key, payload.to_json, anything) end subject end end + + describe '#cached_attr_reader', :clean_gitlab_redis_shared_state do + subject { instance.name } + + before do + model.cached_attr_reader(:name) + end + + context 'when there is no cached value' do + it 'reads the attribute' do + expect(instance).to receive(:read_attribute).and_call_original + + expect(subject).to eq(payload[:name]) + end + end + + context 'when there is a cached value' do + it 'reads the cached value' do + expect(instance).not_to receive(:read_attribute) + + instance.cache_attributes(payload) + + expect(subject).to eq(payload[:name]) + end + end + + it 'always returns the latest values' do + expect(instance.name).to eq(payload[:name]) + + instance.cache_attributes(name: 'new_value') + + expect(instance.name).to eq('new_value') + end + end + + describe '#cast_value_from_cache' do + subject { instance.__send__(:cast_value_from_cache, attribute, value) } + + context 'with runner contacted_at' do + let(:instance) { Ci::Runner.new } + let(:attribute) { :contacted_at } + let(:value) { '2018-05-07 13:53:08 UTC' } + + it 'converts cache string to appropriate type' do + expect(subject).to be_an_instance_of(ActiveSupport::TimeWithZone) + end + end + end end diff --git a/spec/models/concerns/sortable_spec.rb b/spec/models/concerns/sortable_spec.rb new file mode 100644 index 00000000000..b821a84d5e0 --- /dev/null +++ b/spec/models/concerns/sortable_spec.rb @@ -0,0 +1,108 @@ +require 'spec_helper' + +describe Sortable do + describe '.order_by' do + let(:relation) { Group.all } + + describe 'ordering by id' do + it 'ascending' do + expect(relation).to receive(:reorder).with(id: :asc) + + relation.order_by('id_asc') + end + + it 'descending' do + expect(relation).to receive(:reorder).with(id: :desc) + + relation.order_by('id_desc') + end + end + + describe 'ordering by created day' do + it 'ascending' do + expect(relation).to receive(:reorder).with(created_at: :asc) + + relation.order_by('created_asc') + end + + it 'descending' do + expect(relation).to receive(:reorder).with(created_at: :desc) + + relation.order_by('created_desc') + end + + it 'order by "date"' do + expect(relation).to receive(:reorder).with(created_at: :desc) + + relation.order_by('created_date') + end + end + + describe 'ordering by name' do + it 'ascending' do + expect(relation).to receive(:reorder).with("lower(name) asc") + + relation.order_by('name_asc') + end + + it 'descending' do + expect(relation).to receive(:reorder).with("lower(name) desc") + + relation.order_by('name_desc') + end + end + + describe 'ordering by Updated Time' do + it 'ascending' do + expect(relation).to receive(:reorder).with(updated_at: :asc) + + relation.order_by('updated_asc') + end + + it 'descending' do + expect(relation).to receive(:reorder).with(updated_at: :desc) + + relation.order_by('updated_desc') + end + end + + it 'does not call reorder in case of unrecognized ordering' do + expect(relation).not_to receive(:reorder) + + relation.order_by('random_ordering') + end + end + + describe 'sorting groups' do + def ordered_group_names(order) + Group.all.order_by(order).map(&:name) + end + + let!(:ref_time) { Time.parse('2018-05-01 00:00:00') } + let!(:group1) { create(:group, name: 'aa', id: 1, created_at: ref_time - 15.seconds, updated_at: ref_time) } + let!(:group2) { create(:group, name: 'AAA', id: 2, created_at: ref_time - 10.seconds, updated_at: ref_time - 5.seconds) } + let!(:group3) { create(:group, name: 'BB', id: 3, created_at: ref_time - 5.seconds, updated_at: ref_time - 10.seconds) } + let!(:group4) { create(:group, name: 'bbb', id: 4, created_at: ref_time, updated_at: ref_time - 15.seconds) } + + it 'sorts groups by id' do + expect(ordered_group_names('id_asc')).to eq(%w(aa AAA BB bbb)) + expect(ordered_group_names('id_desc')).to eq(%w(bbb BB AAA aa)) + end + + it 'sorts groups by name via case-insentitive comparision' do + expect(ordered_group_names('name_asc')).to eq(%w(aa AAA BB bbb)) + expect(ordered_group_names('name_desc')).to eq(%w(bbb BB AAA aa)) + end + + it 'sorts groups by created_at' do + expect(ordered_group_names('created_asc')).to eq(%w(aa AAA BB bbb)) + expect(ordered_group_names('created_desc')).to eq(%w(bbb BB AAA aa)) + expect(ordered_group_names('created_date')).to eq(%w(bbb BB AAA aa)) + end + + it 'sorts groups by updated_at' do + expect(ordered_group_names('updated_asc')).to eq(%w(bbb BB AAA aa)) + expect(ordered_group_names('updated_desc')).to eq(%w(aa AAA BB bbb)) + end + end +end diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb index 673049d1cc4..a3e68d2e646 100644 --- a/spec/models/generic_commit_status_spec.rb +++ b/spec/models/generic_commit_status_spec.rb @@ -78,4 +78,10 @@ describe GenericCommitStatus do it { is_expected.not_to be_nil } end end + + describe '#present' do + subject { generic_commit_status.present } + + it { is_expected.to be_a(GenericCommitStatusPresenter) } + end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index ac8d9a32d4e..6bc148a1392 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -671,7 +671,7 @@ describe Repository do end end - describe "search_files_by_content" do + shared_examples "search_files_by_content" do let(:results) { repository.search_files_by_content('feature', 'master') } subject { results } @@ -718,7 +718,7 @@ describe Repository do end end - describe "search_files_by_name" do + shared_examples "search_files_by_name" do let(:results) { repository.search_files_by_name('files', 'master') } it 'returns result' do @@ -758,6 +758,16 @@ describe Repository do end end + describe 'with gitaly enabled' do + it_behaves_like 'search_files_by_content' + it_behaves_like 'search_files_by_name' + end + + describe 'with gitaly disabled', :disable_gitaly do + it_behaves_like 'search_files_by_content' + it_behaves_like 'search_files_by_name' + end + describe '#async_remove_remote' do before do masterrev = repository.find_branch('master').dereferenced_target |