diff options
Diffstat (limited to 'spec/models')
22 files changed, 564 insertions, 312 deletions
diff --git a/spec/models/application_record_spec.rb b/spec/models/application_record_spec.rb index 68aed387bfc..fd25132ed3a 100644 --- a/spec/models/application_record_spec.rb +++ b/spec/models/application_record_spec.rb @@ -10,4 +10,27 @@ describe ApplicationRecord do expect(User.id_in(records.last(2).map(&:id))).to eq(records.last(2)) end end + + describe '.safe_find_or_create_by' do + it 'creates the user avoiding race conditions' do + expect(Suggestion).to receive(:find_or_create_by).and_raise(ActiveRecord::RecordNotUnique) + allow(Suggestion).to receive(:find_or_create_by).and_call_original + + expect { Suggestion.safe_find_or_create_by(build(:suggestion).attributes) } + .to change { Suggestion.count }.by(1) + end + end + + describe '.safe_find_or_create_by!' do + it 'creates a record using safe_find_or_create_by' do + expect(Suggestion).to receive(:find_or_create_by).and_call_original + + expect(Suggestion.safe_find_or_create_by!(build(:suggestion).attributes)) + .to be_a(Suggestion) + end + + it 'raises a validation error if the record was not persisted' do + expect { Suggestion.find_or_create_by!(note: nil) }.to raise_error(ActiveRecord::RecordInvalid) + end + end end diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 96aa9a82b71..789e14e8a20 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -70,6 +70,13 @@ describe ApplicationSetting do .is_greater_than(0) end + it do + is_expected.to validate_numericality_of(:local_markdown_version) + .only_integer + .is_greater_than_or_equal_to(0) + .is_less_than(65536) + end + context 'key restrictions' do it 'supports all key types' do expect(described_class::SUPPORTED_KEY_TYPES).to contain_exactly(:rsa, :dsa, :ecdsa, :ed25519) diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 8a1bbb26e57..47865e4d08f 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1844,6 +1844,26 @@ describe Ci::Build do context 'when there is no environment' do it { is_expected.to be_nil } end + + context 'when build has a start environment' do + let(:build) { create(:ci_build, :deploy_to_production, pipeline: pipeline) } + + it 'does not expand environment name' do + expect(build).not_to receive(:expanded_environment_name) + + subject + end + end + + context 'when build has a stop environment' do + let(:build) { create(:ci_build, :stop_review_app, pipeline: pipeline) } + + it 'expands environment name' do + expect(build).to receive(:expanded_environment_name) + + subject + end + end end describe '#play' do diff --git a/spec/models/clusters/applications/cert_manager_spec.rb b/spec/models/clusters/applications/cert_manager_spec.rb index 8e14abe098d..79a06c35459 100644 --- a/spec/models/clusters/applications/cert_manager_spec.rb +++ b/spec/models/clusters/applications/cert_manager_spec.rb @@ -4,20 +4,8 @@ describe Clusters::Applications::CertManager do let(:cert_manager) { create(:clusters_applications_cert_managers) } include_examples 'cluster application core specs', :clusters_applications_cert_managers - - describe '#make_installing!' do - before do - application.make_installing! - end - - context 'application install previously errored with older version' do - let(:application) { create(:clusters_applications_cert_managers, :scheduled, version: 'v0.4.0') } - - it 'updates the application version' do - expect(application.reload.version).to eq('v0.5.2') - end - end - end + include_examples 'cluster application status specs', :clusters_applications_cert_managers + include_examples 'cluster application initial status specs' describe '#install_command' do let(:cluster_issuer_file) { { "cluster_issuer.yaml": "---\napiVersion: certmanager.k8s.io/v1alpha1\nkind: ClusterIssuer\nmetadata:\n name: letsencrypt-prod\nspec:\n acme:\n server: https://acme-v02.api.letsencrypt.org/directory\n email: admin@example.com\n privateKeySecretRef:\n name: letsencrypt-prod\n http01: {}\n" } } diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb index 52c347229c6..6d48131d1cc 100644 --- a/spec/models/clusters/applications/ingress_spec.rb +++ b/spec/models/clusters/applications/ingress_spec.rb @@ -8,6 +8,7 @@ describe Clusters::Applications::Ingress do include_examples 'cluster application core specs', :clusters_applications_ingress include_examples 'cluster application status specs', :clusters_applications_ingress include_examples 'cluster application helm specs', :clusters_applications_ingress + include_examples 'cluster application initial status specs' before do allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_in) @@ -26,20 +27,6 @@ describe Clusters::Applications::Ingress do it { is_expected.to contain_exactly(cluster) } end - describe '#make_installing!' do - before do - application.make_installing! - end - - context 'application install previously errored with older version' do - let(:application) { create(:clusters_applications_ingress, :scheduled, version: '0.22.0') } - - it 'updates the application version' do - expect(application.reload.version).to eq('1.1.2') - end - end - end - describe '#make_installed!' do before do application.make_installed! diff --git a/spec/models/clusters/applications/jupyter_spec.rb b/spec/models/clusters/applications/jupyter_spec.rb index 391e5425384..b73a243f6e0 100644 --- a/spec/models/clusters/applications/jupyter_spec.rb +++ b/spec/models/clusters/applications/jupyter_spec.rb @@ -2,6 +2,7 @@ require 'rails_helper' describe Clusters::Applications::Jupyter do include_examples 'cluster application core specs', :clusters_applications_jupyter + include_examples 'cluster application status specs', :clusters_applications_jupyter include_examples 'cluster application helm specs', :clusters_applications_jupyter it { is_expected.to belong_to(:oauth_application) } @@ -26,20 +27,6 @@ describe Clusters::Applications::Jupyter do end end - describe '#make_installing!' do - before do - application.make_installing! - end - - context 'application install previously errored with older version' do - let(:application) { create(:clusters_applications_jupyter, :scheduled, version: 'v0.5') } - - it 'updates the application version' do - expect(application.reload.version).to eq('v0.6') - end - end - end - describe '#install_command' do let!(:ingress) { create(:clusters_applications_ingress, :installed, external_ip: '127.0.0.1') } let!(:jupyter) { create(:clusters_applications_jupyter, cluster: ingress.cluster) } diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb index 35818be8deb..5519615d52d 100644 --- a/spec/models/clusters/applications/knative_spec.rb +++ b/spec/models/clusters/applications/knative_spec.rb @@ -9,6 +9,7 @@ describe Clusters::Applications::Knative do include_examples 'cluster application core specs', :clusters_applications_knative include_examples 'cluster application status specs', :clusters_applications_knative include_examples 'cluster application helm specs', :clusters_applications_knative + include_examples 'cluster application initial status specs' before do allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_in) @@ -34,20 +35,6 @@ describe Clusters::Applications::Knative do it { is_expected.to contain_exactly(cluster) } end - describe '#make_installing!' do - before do - application.make_installing! - end - - context 'application install previously errored with older version' do - let(:application) { create(:clusters_applications_knative, :scheduled, version: '0.2.2') } - - it 'updates the application version' do - expect(application.reload.version).to eq('0.2.2') - end - end - end - describe '#make_installed' do subject { described_class.installed } diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb index e50ba67c493..073fbded8ac 100644 --- a/spec/models/clusters/applications/prometheus_spec.rb +++ b/spec/models/clusters/applications/prometheus_spec.rb @@ -6,6 +6,7 @@ describe Clusters::Applications::Prometheus do include_examples 'cluster application core specs', :clusters_applications_prometheus include_examples 'cluster application status specs', :clusters_applications_prometheus include_examples 'cluster application helm specs', :clusters_applications_prometheus + include_examples 'cluster application initial status specs' describe '.installed' do subject { described_class.installed } @@ -19,20 +20,6 @@ describe Clusters::Applications::Prometheus do it { is_expected.to contain_exactly(cluster) } end - describe '#make_installing!' do - before do - application.make_installing! - end - - context 'application install previously errored with older version' do - let(:application) { create(:clusters_applications_prometheus, :scheduled, version: '6.7.2') } - - it 'updates the application version' do - expect(application.reload.version).to eq('6.7.3') - end - end - end - describe 'transition to installed' do let(:project) { create(:project) } let(:cluster) { create(:cluster, :with_installed_helm, projects: [project]) } diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb index 8ad41e997c2..96b7b02dbaf 100644 --- a/spec/models/clusters/applications/runner_spec.rb +++ b/spec/models/clusters/applications/runner_spec.rb @@ -6,23 +6,10 @@ describe Clusters::Applications::Runner do include_examples 'cluster application core specs', :clusters_applications_runner include_examples 'cluster application status specs', :clusters_applications_runner include_examples 'cluster application helm specs', :clusters_applications_runner + include_examples 'cluster application initial status specs' it { is_expected.to belong_to(:runner) } - describe '#make_installing!' do - before do - application.make_installing! - end - - context 'application install previously errored with older version' do - let(:application) { create(:clusters_applications_runner, :scheduled, version: '0.1.30') } - - it 'updates the application version' do - expect(application.reload.version).to eq('0.1.45') - end - end - end - describe '.installed' do subject { described_class.installed } diff --git a/spec/models/commit_collection_spec.rb b/spec/models/commit_collection_spec.rb index 005005b236b..12e59b35428 100644 --- a/spec/models/commit_collection_spec.rb +++ b/spec/models/commit_collection_spec.rb @@ -35,6 +35,17 @@ describe CommitCollection do end end + describe '#without_merge_commits' do + it 'returns all commits except merge commits' do + collection = described_class.new(project, [ + build(:commit), + build(:commit, :merge_commit) + ]) + + expect(collection.without_merge_commits.size).to eq(1) + end + end + describe '#with_pipeline_status' do it 'sets the pipeline status for every commit so no additional queries are necessary' do create( diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb index 925e2ab0955..447279f19a8 100644 --- a/spec/models/concerns/cache_markdown_field_spec.rb +++ b/spec/models/concerns/cache_markdown_field_spec.rb @@ -60,6 +60,10 @@ describe CacheMarkdownField do changes_applied end end + + def has_attribute?(attr_name) + attribute_names.include?(attr_name) + end end def thing_subclass(new_attr) @@ -72,7 +76,8 @@ describe CacheMarkdownField do let(:updated_markdown) { '`Bar`' } let(:updated_html) { '<p dir="auto"><code>Bar</code></p>' } - let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION) } + let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) } + let(:cache_version) { CacheMarkdownField::CACHE_COMMONMARK_VERSION << 16 } before do stub_commonmark_sourcepos_disabled @@ -93,24 +98,19 @@ describe CacheMarkdownField do it { expect(thing.foo).to eq(markdown) } it { expect(thing.foo_html).to eq(html) } it { expect(thing.foo_html_changed?).not_to be_truthy } - it { expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION) } + it { expect(thing.cached_markdown_version).to eq(cache_version) } end context 'a changed markdown field' do - shared_examples 'with cache version' do |cache_version| - let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) } - - before do - thing.foo = updated_markdown - thing.save - end + let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version - 1) } - it { expect(thing.foo_html).to eq(updated_html) } - it { expect(thing.cached_markdown_version).to eq(cache_version) } + before do + thing.foo = updated_markdown + thing.save end - it_behaves_like 'with cache version', CacheMarkdownField::CACHE_REDCARPET_VERSION - it_behaves_like 'with cache version', CacheMarkdownField::CACHE_COMMONMARK_VERSION + it { expect(thing.foo_html).to eq(updated_html) } + it { expect(thing.cached_markdown_version).to eq(cache_version) } end context 'when a markdown field is set repeatedly to an empty string' do @@ -143,22 +143,17 @@ describe CacheMarkdownField do end context 'a non-markdown field changed' do - shared_examples 'with cache version' do |cache_version| - let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) } + let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version - 1) } - before do - thing.bar = 'OK' - thing.save - end - - it { expect(thing.bar).to eq('OK') } - it { expect(thing.foo).to eq(markdown) } - it { expect(thing.foo_html).to eq(html) } - it { expect(thing.cached_markdown_version).to eq(cache_version) } + before do + thing.bar = 'OK' + thing.save end - it_behaves_like 'with cache version', CacheMarkdownField::CACHE_REDCARPET_VERSION - it_behaves_like 'with cache version', CacheMarkdownField::CACHE_COMMONMARK_VERSION + it { expect(thing.bar).to eq('OK') } + it { expect(thing.foo).to eq(markdown) } + it { expect(thing.foo_html).to eq(html) } + it { expect(thing.cached_markdown_version).to eq(cache_version) } end context 'version is out of date' do @@ -169,109 +164,84 @@ describe CacheMarkdownField do end it { expect(thing.foo_html).to eq(updated_html) } - it { expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION) } + it { expect(thing.cached_markdown_version).to eq(cache_version) } end describe '#cached_html_up_to_date?' do - shared_examples 'with cache version' do |cache_version| - let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) } - - subject { thing.cached_html_up_to_date?(:foo) } - - it 'returns false when the version is absent' do - thing.cached_markdown_version = nil - - is_expected.to be_falsy - end - - it 'returns false when the version is too early' do - thing.cached_markdown_version -= 1 + let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) } - is_expected.to be_falsy - end + subject { thing.cached_html_up_to_date?(:foo) } - it 'returns false when the version is too late' do - thing.cached_markdown_version += 1 + it 'returns false when the version is absent' do + thing.cached_markdown_version = nil - is_expected.to be_falsy - end + is_expected.to be_falsy + end - it 'returns true when the version is just right' do - thing.cached_markdown_version = cache_version + it 'returns false when the cached version is too old' do + thing.cached_markdown_version = cache_version - 1 - is_expected.to be_truthy - end + is_expected.to be_falsy + end - it 'returns false if markdown has been changed but html has not' do - thing.foo = updated_html + it 'returns false when the cached version is in future' do + thing.cached_markdown_version = cache_version + 1 - is_expected.to be_falsy - end + is_expected.to be_falsy + end - it 'returns true if markdown has not been changed but html has' do - thing.foo_html = updated_html + it 'returns false when the local version was bumped' do + allow(Gitlab::CurrentSettings.current_application_settings).to receive(:local_markdown_version).and_return(2) + thing.cached_markdown_version = cache_version - is_expected.to be_truthy - end + is_expected.to be_falsy + end - it 'returns true if markdown and html have both been changed' do - thing.foo = updated_markdown - thing.foo_html = updated_html + it 'returns true when the local version is default' do + thing.cached_markdown_version = cache_version - is_expected.to be_truthy - end + is_expected.to be_truthy + end - it 'returns false if the markdown field is set but the html is not' do - thing.foo_html = nil + it 'returns true when the cached version is just right' do + allow(Gitlab::CurrentSettings.current_application_settings).to receive(:local_markdown_version).and_return(2) + thing.cached_markdown_version = cache_version + 2 - is_expected.to be_falsy - end + is_expected.to be_truthy end - it_behaves_like 'with cache version', CacheMarkdownField::CACHE_REDCARPET_VERSION - it_behaves_like 'with cache version', CacheMarkdownField::CACHE_COMMONMARK_VERSION - end - - describe '#latest_cached_markdown_version' do - subject { thing.latest_cached_markdown_version } + it 'returns false if markdown has been changed but html has not' do + thing.foo = updated_html - it 'returns redcarpet version' do - thing.cached_markdown_version = CacheMarkdownField::CACHE_COMMONMARK_VERSION_START - 1 - is_expected.to eq(CacheMarkdownField::CACHE_REDCARPET_VERSION) + is_expected.to be_falsy end - it 'returns commonmark version' do - thing.cached_markdown_version = CacheMarkdownField::CACHE_COMMONMARK_VERSION_START + 1 - is_expected.to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION) - end + it 'returns true if markdown has not been changed but html has' do + thing.foo_html = updated_html - it 'returns default version when version is nil' do - thing.cached_markdown_version = nil - is_expected.to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION) + is_expected.to be_truthy end - end - describe '#legacy_markdown?' do - subject { thing.legacy_markdown? } + it 'returns true if markdown and html have both been changed' do + thing.foo = updated_markdown + thing.foo_html = updated_html - it 'returns true for redcarpet versions' do - thing.cached_markdown_version = CacheMarkdownField::CACHE_COMMONMARK_VERSION_START - 1 is_expected.to be_truthy end - it 'returns false for commonmark versions' do - thing.cached_markdown_version = CacheMarkdownField::CACHE_COMMONMARK_VERSION_START - is_expected.to be_falsey - end + it 'returns false if the markdown field is set but the html is not' do + thing.foo_html = nil - it 'returns false if nil' do - thing.cached_markdown_version = nil - is_expected.to be_falsey + is_expected.to be_falsy end + end - it 'returns false if 0' do - thing.cached_markdown_version = 0 - is_expected.to be_falsey + describe '#latest_cached_markdown_version' do + subject { thing.latest_cached_markdown_version } + + it 'returns default version' do + thing.cached_markdown_version = nil + is_expected.to eq(cache_version) end end @@ -298,44 +268,39 @@ describe CacheMarkdownField do thing.cached_markdown_version = nil thing.refresh_markdown_cache - expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION) + expect(thing.cached_markdown_version).to eq(cache_version) end end describe '#refresh_markdown_cache!' do - shared_examples 'with cache version' do |cache_version| - let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) } + let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) } - before do - thing.foo = updated_markdown - end + before do + thing.foo = updated_markdown + end - it 'fills all html fields' do - thing.refresh_markdown_cache! + it 'fills all html fields' do + thing.refresh_markdown_cache! - expect(thing.foo_html).to eq(updated_html) - expect(thing.foo_html_changed?).to be_truthy - expect(thing.baz_html_changed?).to be_truthy - end + expect(thing.foo_html).to eq(updated_html) + expect(thing.foo_html_changed?).to be_truthy + expect(thing.baz_html_changed?).to be_truthy + end - it 'skips saving if not persisted' do - expect(thing).to receive(:persisted?).and_return(false) - expect(thing).not_to receive(:update_columns) + it 'skips saving if not persisted' do + expect(thing).to receive(:persisted?).and_return(false) + expect(thing).not_to receive(:update_columns) - thing.refresh_markdown_cache! - end + thing.refresh_markdown_cache! + end - it 'saves the changes using #update_columns' do - expect(thing).to receive(:persisted?).and_return(true) - expect(thing).to receive(:update_columns) - .with("foo_html" => updated_html, "baz_html" => "", "cached_markdown_version" => cache_version) + it 'saves the changes using #update_columns' do + expect(thing).to receive(:persisted?).and_return(true) + expect(thing).to receive(:update_columns) + .with("foo_html" => updated_html, "baz_html" => "", "cached_markdown_version" => cache_version) - thing.refresh_markdown_cache! - end + thing.refresh_markdown_cache! end - - it_behaves_like 'with cache version', CacheMarkdownField::CACHE_REDCARPET_VERSION - it_behaves_like 'with cache version', CacheMarkdownField::CACHE_COMMONMARK_VERSION end describe '#banzai_render_context' do @@ -384,7 +349,7 @@ describe CacheMarkdownField do expect(thing.foo_html).to eq(updated_html) expect(thing.baz_html).to eq(updated_html) - expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION) + expect(thing.cached_markdown_version).to eq(cache_version) end end @@ -404,24 +369,8 @@ describe CacheMarkdownField do expect(thing.foo_html).to eq(updated_html) expect(thing.baz_html).to eq(updated_html) - expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION) + expect(thing.cached_markdown_version).to eq(cache_version) end end end - - describe CacheMarkdownField::MarkdownEngine do - subject { lambda { |version| CacheMarkdownField::MarkdownEngine.from_version(version) } } - - it 'returns :common_mark as a default' do - expect(subject.call(nil)).to eq :common_mark - end - - it 'returns :common_mark' do - expect(subject.call(CacheMarkdownField::CACHE_COMMONMARK_VERSION)).to eq :common_mark - end - - it 'returns :redcarpet' do - expect(subject.call(CacheMarkdownField::CACHE_REDCARPET_VERSION)).to eq :redcarpet - end - end end diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 5753c646106..41159348e04 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -139,6 +139,78 @@ describe Issuable do it 'returns issues with a matching description for a query shorter than 3 chars' do expect(issuable_class.full_search(searchable_issue2.description.downcase)).to eq([searchable_issue2]) end + + context 'when matching columns is "title"' do + it 'returns issues with a matching title' do + expect(issuable_class.full_search(searchable_issue.title, matched_columns: 'title')) + .to eq([searchable_issue]) + end + + it 'returns no issues with a matching description' do + expect(issuable_class.full_search(searchable_issue.description, matched_columns: 'title')) + .to be_empty + end + end + + context 'when matching columns is "description"' do + it 'returns no issues with a matching title' do + expect(issuable_class.full_search(searchable_issue.title, matched_columns: 'description')) + .to be_empty + end + + it 'returns issues with a matching description' do + expect(issuable_class.full_search(searchable_issue.description, matched_columns: 'description')) + .to eq([searchable_issue]) + end + end + + context 'when matching columns is "title,description"' do + it 'returns issues with a matching title' do + expect(issuable_class.full_search(searchable_issue.title, matched_columns: 'title,description')) + .to eq([searchable_issue]) + end + + it 'returns issues with a matching description' do + expect(issuable_class.full_search(searchable_issue.description, matched_columns: 'title,description')) + .to eq([searchable_issue]) + end + end + + context 'when matching columns is nil"' do + it 'returns issues with a matching title' do + expect(issuable_class.full_search(searchable_issue.title, matched_columns: nil)) + .to eq([searchable_issue]) + end + + it 'returns issues with a matching description' do + expect(issuable_class.full_search(searchable_issue.description, matched_columns: nil)) + .to eq([searchable_issue]) + end + end + + context 'when matching columns is "invalid"' do + it 'returns issues with a matching title' do + expect(issuable_class.full_search(searchable_issue.title, matched_columns: 'invalid')) + .to eq([searchable_issue]) + end + + it 'returns issues with a matching description' do + expect(issuable_class.full_search(searchable_issue.description, matched_columns: 'invalid')) + .to eq([searchable_issue]) + end + end + + context 'when matching columns is "title,invalid"' do + it 'returns issues with a matching title' do + expect(issuable_class.full_search(searchable_issue.title, matched_columns: 'title,invalid')) + .to eq([searchable_issue]) + end + + it 'returns no issues with a matching description' do + expect(issuable_class.full_search(searchable_issue.description, matched_columns: 'title,invalid')) + .to be_empty + end + end end describe '.to_ability_name' do diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 9a3f1f1c5a1..2d554326f05 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -41,6 +41,76 @@ describe Environment do end end + describe '.for_name_like' do + subject { project.environments.for_name_like(query, limit: limit) } + + let!(:environment) { create(:environment, name: 'production', project: project) } + let(:query) { 'pro' } + let(:limit) { 5 } + + it 'returns a found name' do + is_expected.to include(environment) + end + + context 'when query is production' do + let(:query) { 'production' } + + it 'returns a found name' do + is_expected.to include(environment) + end + end + + context 'when query is productionA' do + let(:query) { 'productionA' } + + it 'returns empty array' do + is_expected.to be_empty + end + end + + context 'when query is empty' do + let(:query) { '' } + + it 'returns a found name' do + is_expected.to include(environment) + end + end + + context 'when query is nil' do + let(:query) { } + + it 'raises an error' do + expect { subject }.to raise_error(NoMethodError) + end + end + + context 'when query is partially matched in the middle of environment name' do + let(:query) { 'duction' } + + it 'returns empty array' do + is_expected.to be_empty + end + end + + context 'when query contains a wildcard character' do + let(:query) { 'produc%' } + + it 'prevents wildcard injection' do + is_expected.to be_empty + end + end + end + + describe '.pluck_names' do + subject { described_class.pluck_names } + + let!(:environment) { create(:environment, name: 'production', project: project) } + + it 'plucks names' do + is_expected.to eq(%w[production]) + end + end + describe '#expire_etag_cache' do let(:store) { Gitlab::EtagCaching::Store.new } diff --git a/spec/models/gpg_signature_spec.rb b/spec/models/gpg_signature_spec.rb index cdd7dea2064..e90319c39b1 100644 --- a/spec/models/gpg_signature_spec.rb +++ b/spec/models/gpg_signature_spec.rb @@ -23,6 +23,41 @@ RSpec.describe GpgSignature do it { is_expected.to validate_presence_of(:gpg_key_primary_keyid) } end + describe '.safe_create!' do + let(:attributes) do + { + commit_sha: commit_sha, + project: project, + gpg_key_primary_keyid: gpg_key.keyid + } + end + + it 'finds a signature by commit sha if it existed' do + gpg_signature + + expect(described_class.safe_create!(commit_sha: commit_sha)).to eq(gpg_signature) + end + + it 'creates a new signature if it was not found' do + expect { described_class.safe_create!(attributes) }.to change { described_class.count }.by(1) + end + + it 'assigns the correct attributes when creating' do + signature = described_class.safe_create!(attributes) + + expect(signature.project).to eq(project) + expect(signature.commit_sha).to eq(commit_sha) + expect(signature.gpg_key_primary_keyid).to eq(gpg_key.keyid) + end + + it 'does not raise an error in case of a race condition' do + expect(described_class).to receive(:find_or_create_by).and_raise(ActiveRecord::RecordNotUnique) + allow(described_class).to receive(:find_or_create_by).and_call_original + + described_class.safe_create!(attributes) + end + end + describe '#commit' do it 'fetches the commit through the project' do expect_any_instance_of(Project).to receive(:commit).with(commit_sha).and_return(commit) diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index 33e984dc399..1849d3bac12 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -46,7 +46,7 @@ describe MergeRequestDiff do it { expect(first_diff.reload).not_to be_latest } end - describe '#diffs' do + shared_examples_for 'merge request diffs' do let(:merge_request) { create(:merge_request, :with_diffs) } let!(:diff) { merge_request.merge_request_diff.reload } @@ -91,98 +91,110 @@ describe MergeRequestDiff do diff.diffs.diff_files end end - end - describe '#raw_diffs' do - context 'when the :ignore_whitespace_change option is set' do - it 'creates a new compare object instead of loading from the DB' do - expect(diff_with_commits).not_to receive(:load_diffs) - expect(diff_with_commits.compare).to receive(:diffs).and_call_original + describe '#raw_diffs' do + context 'when the :ignore_whitespace_change option is set' do + it 'creates a new compare object instead of using preprocessed data' do + expect(diff_with_commits).not_to receive(:load_diffs) + expect(diff_with_commits.compare).to receive(:diffs).and_call_original - diff_with_commits.raw_diffs(ignore_whitespace_change: true) + diff_with_commits.raw_diffs(ignore_whitespace_change: true) + end end - end - context 'when the raw diffs are empty' do - before do - MergeRequestDiffFile.where(merge_request_diff_id: diff_with_commits.id).delete_all - end + context 'when the raw diffs are empty' do + before do + MergeRequestDiffFile.where(merge_request_diff_id: diff_with_commits.id).delete_all + end - it 'returns an empty DiffCollection' do - expect(diff_with_commits.raw_diffs).to be_a(Gitlab::Git::DiffCollection) - expect(diff_with_commits.raw_diffs).to be_empty + it 'returns an empty DiffCollection' do + expect(diff_with_commits.raw_diffs).to be_a(Gitlab::Git::DiffCollection) + expect(diff_with_commits.raw_diffs).to be_empty + end end - end - context 'when the raw diffs exist' do - it 'returns the diffs' do - expect(diff_with_commits.raw_diffs).to be_a(Gitlab::Git::DiffCollection) - expect(diff_with_commits.raw_diffs).not_to be_empty - end + context 'when the raw diffs exist' do + it 'returns the diffs' do + expect(diff_with_commits.raw_diffs).to be_a(Gitlab::Git::DiffCollection) + expect(diff_with_commits.raw_diffs).not_to be_empty + end - context 'when the :paths option is set' do - let(:diffs) { diff_with_commits.raw_diffs(paths: ['files/ruby/popen.rb', 'files/ruby/popen.rb']) } + context 'when the :paths option is set' do + let(:diffs) { diff_with_commits.raw_diffs(paths: ['files/ruby/popen.rb', 'files/ruby/popen.rb']) } - it 'only returns diffs that match the (old path, new path) given' do - expect(diffs.map(&:new_path)).to contain_exactly('files/ruby/popen.rb') - end + it 'only returns diffs that match the (old path, new path) given' do + expect(diffs.map(&:new_path)).to contain_exactly('files/ruby/popen.rb') + end - it 'only serializes diff files found by query' do - expect(diff_with_commits.merge_request_diff_files.count).to be > 10 - expect_any_instance_of(MergeRequestDiffFile).to receive(:to_hash).once + it 'only serializes diff files found by query' do + expect(diff_with_commits.merge_request_diff_files.count).to be > 10 + expect_any_instance_of(MergeRequestDiffFile).to receive(:to_hash).once - diffs - end + diffs + end - it 'uses the diffs from the DB' do - expect(diff_with_commits).to receive(:load_diffs) + it 'uses the preprocessed diffs' do + expect(diff_with_commits).to receive(:load_diffs) - diffs + diffs + end end end end - end - describe '#save_diffs' do - it 'saves collected state' do - mr_diff = create(:merge_request).merge_request_diff + describe '#save_diffs' do + it 'saves collected state' do + mr_diff = create(:merge_request).merge_request_diff - expect(mr_diff.collected?).to be_truthy - end + expect(mr_diff.collected?).to be_truthy + end - it 'saves overflow state' do - allow(Commit).to receive(:max_diff_options) - .and_return(max_lines: 0, max_files: 0) + it 'saves overflow state' do + allow(Commit).to receive(:max_diff_options) + .and_return(max_lines: 0, max_files: 0) - mr_diff = create(:merge_request).merge_request_diff + mr_diff = create(:merge_request).merge_request_diff - expect(mr_diff.overflow?).to be_truthy - end + expect(mr_diff.overflow?).to be_truthy + end - it 'saves empty state' do - allow_any_instance_of(described_class).to receive_message_chain(:compare, :commits) - .and_return([]) + it 'saves empty state' do + allow_any_instance_of(described_class).to receive_message_chain(:compare, :commits) + .and_return([]) - mr_diff = create(:merge_request).merge_request_diff + mr_diff = create(:merge_request).merge_request_diff - expect(mr_diff.empty?).to be_truthy - end + expect(mr_diff.empty?).to be_truthy + end - it 'expands collapsed diffs before saving' do - mr_diff = create(:merge_request, source_branch: 'expand-collapse-lines', target_branch: 'master').merge_request_diff - diff_file = mr_diff.merge_request_diff_files.find_by(new_path: 'expand-collapse/file-5.txt') + it 'expands collapsed diffs before saving' do + mr_diff = create(:merge_request, source_branch: 'expand-collapse-lines', target_branch: 'master').merge_request_diff + diff_file = mr_diff.merge_request_diff_files.find_by(new_path: 'expand-collapse/file-5.txt') - expect(diff_file.diff).not_to be_empty + expect(diff_file.diff).not_to be_empty + end + + it 'saves binary diffs correctly' do + path = 'files/images/icn-time-tracking.pdf' + mr_diff = create(:merge_request, source_branch: 'add-pdf-text-binary', target_branch: 'master').merge_request_diff + diff_file = mr_diff.merge_request_diff_files.find_by(new_path: path) + + expect(diff_file).to be_binary + expect(diff_file.diff).to eq(mr_diff.compare.diffs(paths: [path]).to_a.first.diff) + end end + end - it 'saves binary diffs correctly' do - path = 'files/images/icn-time-tracking.pdf' - mr_diff = create(:merge_request, source_branch: 'add-pdf-text-binary', target_branch: 'master').merge_request_diff - diff_file = mr_diff.merge_request_diff_files.find_by(new_path: path) + describe 'internal diffs configured' do + include_examples 'merge request diffs' + end - expect(diff_file).to be_binary - expect(diff_file.diff).to eq(mr_diff.compare.diffs(paths: [path]).to_a.first.diff) + describe 'external diffs configured' do + before do + stub_external_diffs_setting(enabled: true) end + + include_examples 'merge request diffs' end describe '#commit_shas' do @@ -245,4 +257,55 @@ describe MergeRequestDiff do expect(subject.modified_paths).to eq(%w{foo bar baz}) end end + + describe '#opening_external_diff' do + subject(:diff) { diff_with_commits } + + context 'external diffs disabled' do + it { expect(diff.external_diff).not_to be_exists } + + it 'yields nil' do + expect { |b| diff.opening_external_diff(&b) }.to yield_with_args(nil) + end + end + + context 'external diffs enabled' do + let(:test_dir) { 'tmp/tests/external-diffs' } + + around do |example| + FileUtils.mkdir_p(test_dir) + + begin + example.run + ensure + FileUtils.rm_rf(test_dir) + end + end + + before do + stub_external_diffs_setting(enabled: true, storage_path: test_dir) + end + + it { expect(diff.external_diff).to be_exists } + + it 'yields an open file' do + expect { |b| diff.opening_external_diff(&b) }.to yield_with_args(File) + end + + it 'is re-entrant' do + outer_file_a = + diff.opening_external_diff do |outer_file| + diff.opening_external_diff do |inner_file| + expect(outer_file).to eq(inner_file) + end + + outer_file + end + + diff.opening_external_diff do |outer_file_b| + expect(outer_file_a).not_to eq(outer_file_b) + end + end + end + end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index b62f973ad1e..afa87b8a62d 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -82,6 +82,38 @@ describe MergeRequest do end end + describe '#default_squash_commit_message' do + let(:project) { subject.project } + + def commit_collection(commit_hashes) + raw_commits = commit_hashes.map { |raw| Commit.from_hash(raw, project) } + + CommitCollection.new(project, raw_commits) + end + + it 'returns the oldest multiline commit message' do + commits = commit_collection([ + { message: 'Singleline', parent_ids: [] }, + { message: "Second multiline\nCommit message", parent_ids: [] }, + { message: "First multiline\nCommit message", parent_ids: [] } + ]) + + expect(subject).to receive(:commits).and_return(commits) + + expect(subject.default_squash_commit_message).to eq("First multiline\nCommit message") + end + + it 'returns the merge request title if there are no multiline commits' do + commits = commit_collection([ + { message: 'Singleline', parent_ids: [] } + ]) + + expect(subject).to receive(:commits).and_return(commits) + + expect(subject.default_squash_commit_message).to eq(subject.title) + end + end + describe 'modules' do subject { described_class } @@ -920,18 +952,18 @@ describe MergeRequest do end end - describe '#merge_commit_message' do + describe '#default_merge_commit_message' do it 'includes merge information as the title' do request = build(:merge_request, source_branch: 'source', target_branch: 'target') - expect(request.merge_commit_message) + expect(request.default_merge_commit_message) .to match("Merge branch 'source' into 'target'\n\n") end it 'includes its title in the body' do request = build(:merge_request, title: 'Remove all technical debt') - expect(request.merge_commit_message) + expect(request.default_merge_commit_message) .to match("Remove all technical debt\n\n") end @@ -943,34 +975,34 @@ describe MergeRequest do allow(subject.project).to receive(:default_branch).and_return(subject.target_branch) subject.cache_merge_request_closes_issues! - expect(subject.merge_commit_message) + expect(subject.default_merge_commit_message) .to match("Closes #{issue.to_reference}") end it 'includes its reference in the body' do request = build_stubbed(:merge_request) - expect(request.merge_commit_message) + expect(request.default_merge_commit_message) .to match("See merge request #{request.to_reference(full: true)}") end it 'excludes multiple linebreak runs when description is blank' do request = build(:merge_request, title: 'Title', description: nil) - expect(request.merge_commit_message).not_to match("Title\n\n\n\n") + expect(request.default_merge_commit_message).not_to match("Title\n\n\n\n") end it 'includes its description in the body' do request = build(:merge_request, description: 'By removing all code') - expect(request.merge_commit_message(include_description: true)) + expect(request.default_merge_commit_message(include_description: true)) .to match("By removing all code\n\n") end it 'does not includes its description in the body' do request = build(:merge_request, description: 'By removing all code') - expect(request.merge_commit_message) + expect(request.default_merge_commit_message) .not_to match("By removing all code\n\n") end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index ae137aa7b78..c1767ed0535 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1765,7 +1765,7 @@ describe Project do context 'using a regular repository' do it 'creates the repository' do expect(shell).to receive(:create_repository) - .with(project.repository_storage, project.disk_path) + .with(project.repository_storage, project.disk_path, project.full_path) .and_return(true) expect(project.repository).to receive(:after_create) @@ -1775,7 +1775,7 @@ describe Project do it 'adds an error if the repository could not be created' do expect(shell).to receive(:create_repository) - .with(project.repository_storage, project.disk_path) + .with(project.repository_storage, project.disk_path, project.full_path) .and_return(false) expect(project.repository).not_to receive(:after_create) @@ -1808,7 +1808,7 @@ describe Project do .and_return(false) allow(shell).to receive(:create_repository) - .with(project.repository_storage, project.disk_path) + .with(project.repository_storage, project.disk_path, project.full_path) .and_return(true) expect(project).to receive(:create_repository).with(force: true) @@ -1832,7 +1832,7 @@ describe Project do .and_return(false) expect(shell).to receive(:create_repository) - .with(project.repository_storage, project.disk_path) + .with(project.repository_storage, project.disk_path, project.full_path) .and_return(true) project.ensure_repository diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb index 48a43801b9f..3ccc706edf2 100644 --- a/spec/models/project_wiki_spec.rb +++ b/spec/models/project_wiki_spec.rb @@ -7,7 +7,7 @@ describe ProjectWiki do let(:repository) { project.repository } let(:gitlab_shell) { Gitlab::Shell.new } let(:project_wiki) { described_class.new(project, user) } - let(:raw_repository) { Gitlab::Git::Repository.new(project.repository_storage, subject.disk_path + '.git', 'foo') } + let(:raw_repository) { Gitlab::Git::Repository.new(project.repository_storage, subject.disk_path + '.git', 'foo', 'group/project.wiki') } let(:commit) { project_wiki.repository.head_commit } subject { project_wiki } @@ -75,7 +75,7 @@ describe ProjectWiki do # Create a fresh project which will not have a wiki project_wiki = described_class.new(create(:project), user) gitlab_shell = double(:gitlab_shell) - allow(gitlab_shell).to receive(:create_repository) + allow(gitlab_shell).to receive(:create_wiki_repository) allow(project_wiki).to receive(:gitlab_shell).and_return(gitlab_shell) expect { project_wiki.send(:wiki) }.to raise_exception(ProjectWiki::CouldNotCreateWikiError) diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 4978c43c9b5..f78760bf567 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -2291,6 +2291,7 @@ describe Repository do expect(subject).to be_a(Gitlab::Git::Repository) expect(subject.relative_path).to eq(project.disk_path + '.git') expect(subject.gl_repository).to eq("project-#{project.id}") + expect(subject.gl_project_path).to eq(project.full_path) end context 'with a wiki repository' do @@ -2300,6 +2301,7 @@ describe Repository do expect(subject).to be_a(Gitlab::Git::Repository) expect(subject.relative_path).to eq(project.disk_path + '.wiki.git') expect(subject.gl_repository).to eq("wiki-#{project.id}") + expect(subject.gl_project_path).to eq(project.full_path) end end end diff --git a/spec/models/resource_label_event_spec.rb b/spec/models/resource_label_event_spec.rb index 3797960ac3d..7eeb2fae57d 100644 --- a/spec/models/resource_label_event_spec.rb +++ b/spec/models/resource_label_event_spec.rb @@ -81,14 +81,14 @@ RSpec.describe ResourceLabelEvent, type: :model do expect(subject.outdated_markdown?).to be true end - it 'returns true markdown is outdated' do - subject.attributes = { cached_markdown_version: 0 } + it 'returns true if markdown is outdated' do + subject.attributes = { cached_markdown_version: ((CacheMarkdownField::CACHE_COMMONMARK_VERSION - 1) << 16) | 0 } expect(subject.outdated_markdown?).to be true end it 'returns false if label and reference are set' do - subject.attributes = { reference: 'whatever', cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION } + subject.attributes = { reference: 'whatever', cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION << 16 } expect(subject.outdated_markdown?).to be false end diff --git a/spec/models/sent_notification_spec.rb b/spec/models/sent_notification_spec.rb index 677613b7980..6c35ed8f649 100644 --- a/spec/models/sent_notification_spec.rb +++ b/spec/models/sent_notification_spec.rb @@ -36,19 +36,41 @@ describe SentNotification do end end + shared_examples 'a successful sent notification' do + it 'creates a new SentNotification' do + expect { subject }.to change { described_class.count }.by(1) + end + end + describe '.record' do let(:issue) { create(:issue) } - it 'creates a new SentNotification' do - expect { described_class.record(issue, user.id) }.to change { described_class.count }.by(1) - end + subject { described_class.record(issue, user.id) } + + it_behaves_like 'a successful sent notification' end describe '.record_note' do - let(:note) { create(:diff_note_on_merge_request) } + subject { described_class.record_note(note, note.author.id) } - it 'creates a new SentNotification' do - expect { described_class.record_note(note, note.author.id) }.to change { described_class.count }.by(1) + context 'for a discussion note' do + let(:note) { create(:diff_note_on_merge_request) } + + it_behaves_like 'a successful sent notification' + + it 'sets in_reply_to_discussion_id' do + expect(subject.in_reply_to_discussion_id).to eq(note.discussion_id) + end + end + + context 'for an individual note' do + let(:note) { create(:note_on_merge_request) } + + it_behaves_like 'a successful sent notification' + + it 'does not set in_reply_to_discussion_id' do + expect(subject.in_reply_to_discussion_id).to be_nil + end end end diff --git a/spec/models/ssh_host_key_spec.rb b/spec/models/ssh_host_key_spec.rb index 23a94334172..4c677569561 100644 --- a/spec/models/ssh_host_key_spec.rb +++ b/spec/models/ssh_host_key_spec.rb @@ -56,6 +56,29 @@ describe SshHostKey do end end + describe '.find_by' do + let(:project) { create(:project) } + let(:url) { 'ssh://invalid.invalid:2222' } + + let(:finding_id) { [project.id, url].join(':') } + + it 'accepts a string key' do + result = described_class.find_by('id' => finding_id) + + expect(result).to be_a(described_class) + expect(result.project).to eq(project) + expect(result.url.to_s).to eq(url) + end + + it 'accepts a symbol key' do + result = described_class.find_by(id: finding_id) + + expect(result).to be_a(described_class) + expect(result.project).to eq(project) + expect(result.url.to_s).to eq(url) + end + end + describe '#fingerprints', :use_clean_rails_memory_store_caching do it 'returns an array of indexed fingerprints when the cache is filled' do stub_reactive_cache(ssh_host_key, known_hosts: known_hosts) |