summaryrefslogtreecommitdiff
path: root/spec/models/concerns
diff options
context:
space:
mode:
Diffstat (limited to 'spec/models/concerns')
-rw-r--r--spec/models/concerns/group_descendant_spec.rb4
-rw-r--r--spec/models/concerns/ignorable_columns_spec.rb88
-rw-r--r--spec/models/concerns/issuable_spec.rb89
-rw-r--r--spec/models/concerns/issuable_states_spec.rb30
-rw-r--r--spec/models/concerns/mentionable_spec.rb67
-rw-r--r--spec/models/concerns/milestoneish_spec.rb17
-rw-r--r--spec/models/concerns/reactive_caching_spec.rb12
-rw-r--r--spec/models/concerns/safe_url_spec.rb52
-rw-r--r--spec/models/concerns/sha256_attribute_spec.rb91
9 files changed, 418 insertions, 32 deletions
diff --git a/spec/models/concerns/group_descendant_spec.rb b/spec/models/concerns/group_descendant_spec.rb
index 192e884f3e8..47419770d0f 100644
--- a/spec/models/concerns/group_descendant_spec.rb
+++ b/spec/models/concerns/group_descendant_spec.rb
@@ -82,7 +82,7 @@ describe GroupDescendant do
end
it 'tracks the exception when a parent was not preloaded' do
- expect(Gitlab::Sentry).to receive(:track_exception).and_call_original
+ expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).and_call_original
expect { described_class.build_hierarchy([subsub_group]) }.to raise_error(ArgumentError)
end
@@ -91,7 +91,7 @@ describe GroupDescendant do
expected_hierarchy = { parent => { subgroup => subsub_group } }
# this does not raise in production, so stubbing it here.
- allow(Gitlab::Sentry).to receive(:track_exception)
+ allow(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception)
expect(described_class.build_hierarchy([subsub_group])).to eq(expected_hierarchy)
end
diff --git a/spec/models/concerns/ignorable_columns_spec.rb b/spec/models/concerns/ignorable_columns_spec.rb
new file mode 100644
index 00000000000..55efa1b5fda
--- /dev/null
+++ b/spec/models/concerns/ignorable_columns_spec.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe IgnorableColumns do
+ let(:record_class) do
+ Class.new(ApplicationRecord) do
+ include IgnorableColumns
+ end
+ end
+
+ subject { record_class }
+
+ it 'adds columns to ignored_columns' do
+ expect do
+ subject.ignore_columns(:name, :created_at, remove_after: '2019-12-01', remove_with: '12.6')
+ end.to change { subject.ignored_columns }.from([]).to(%w(name created_at))
+ end
+
+ it 'adds columns to ignored_columns (array version)' do
+ expect do
+ subject.ignore_columns(%i[name created_at], remove_after: '2019-12-01', remove_with: '12.6')
+ end.to change { subject.ignored_columns }.from([]).to(%w(name created_at))
+ end
+
+ it 'requires remove_after attribute to be set' do
+ expect { subject.ignore_columns(:name, remove_after: nil, remove_with: 12.6) }.to raise_error(ArgumentError, /Please indicate/)
+ end
+
+ it 'requires remove_after attribute to be set' do
+ expect { subject.ignore_columns(:name, remove_after: "not a date", remove_with: 12.6) }.to raise_error(ArgumentError, /Please indicate/)
+ end
+
+ it 'requires remove_with attribute to be set' do
+ expect { subject.ignore_columns(:name, remove_after: '2019-12-01', remove_with: nil) }.to raise_error(ArgumentError, /Please indicate/)
+ end
+
+ describe '.ignored_columns_details' do
+ shared_examples_for 'storing removal information' do
+ it 'storing removal information' do
+ subject.ignore_column(columns, remove_after: '2019-12-01', remove_with: '12.6')
+
+ [columns].flatten.each do |column|
+ expect(subject.ignored_columns_details[column].remove_after).to eq(Date.parse('2019-12-01'))
+ expect(subject.ignored_columns_details[column].remove_with).to eq('12.6')
+ end
+ end
+ end
+
+ context 'with single column' do
+ let(:columns) { :name }
+ it_behaves_like 'storing removal information'
+ end
+
+ context 'with array column' do
+ let(:columns) { %i[name created_at] }
+ it_behaves_like 'storing removal information'
+ end
+
+ it 'defaults to empty Hash' do
+ expect(subject.ignored_columns_details).to eq({})
+ end
+ end
+
+ describe IgnorableColumns::ColumnIgnore do
+ subject { described_class.new(remove_after, remove_with) }
+
+ let(:remove_with) { double }
+
+ describe '#safe_to_remove?' do
+ context 'after remove_after date has passed' do
+ let(:remove_after) { Date.parse('2019-01-10') }
+
+ it 'returns true (safe to remove)' do
+ expect(subject.safe_to_remove?).to be_truthy
+ end
+ end
+
+ context 'before remove_after date has passed' do
+ let(:remove_after) { Date.today }
+
+ it 'returns false (not safe to remove)' do
+ expect(subject.safe_to_remove?).to be_falsey
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index f7bef9e71e2..76a3a825978 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
describe Issuable do
+ include ProjectForksHelper
+
let(:issuable_class) { Issue }
let(:issue) { create(:issue, title: 'An issue', description: 'A description') }
let(:user) { create(:user) }
@@ -852,4 +854,91 @@ describe Issuable do
it_behaves_like 'matches_cross_reference_regex? fails fast'
end
end
+
+ describe 'release scopes' do
+ let_it_be(:project) { create(:project) }
+ let(:forked_project) { fork_project(project) }
+
+ let_it_be(:release_1) { create(:release, tag: 'v1.0', project: project) }
+ let_it_be(:release_2) { create(:release, tag: 'v2.0', project: project) }
+ let_it_be(:release_3) { create(:release, tag: 'v3.0', project: project) }
+ let_it_be(:release_4) { create(:release, tag: 'v4.0', project: project) }
+
+ let_it_be(:milestone_1) { create(:milestone, releases: [release_1], title: 'm1', project: project) }
+ let_it_be(:milestone_2) { create(:milestone, releases: [release_1, release_2], title: 'm2', project: project) }
+ let_it_be(:milestone_3) { create(:milestone, releases: [release_2, release_4], title: 'm3', project: project) }
+ let_it_be(:milestone_4) { create(:milestone, releases: [release_3], title: 'm4', project: project) }
+ let_it_be(:milestone_5) { create(:milestone, releases: [release_3], title: 'm5', project: project) }
+ let_it_be(:milestone_6) { create(:milestone, title: 'm6', project: project) }
+
+ let_it_be(:issue_1) { create(:issue, milestone: milestone_1, project: project) }
+ let_it_be(:issue_2) { create(:issue, milestone: milestone_1, project: project) }
+ let_it_be(:issue_3) { create(:issue, milestone: milestone_2, project: project) }
+ let_it_be(:issue_4) { create(:issue, milestone: milestone_5, project: project) }
+ let_it_be(:issue_5) { create(:issue, milestone: milestone_6, project: project) }
+ let_it_be(:issue_6) { create(:issue, project: project) }
+
+ let(:mr_1) { create(:merge_request, milestone: milestone_1, target_project: project, source_project: project) }
+ let(:mr_2) { create(:merge_request, milestone: milestone_3, target_project: project, source_project: forked_project) }
+ let(:mr_3) { create(:merge_request, source_project: project) }
+
+ let_it_be(:issue_items) { Issue.all }
+ let(:mr_items) { MergeRequest.all }
+
+ describe '#without_release' do
+ it 'returns the issues or mrs not tied to any milestone and the ones tied to milestone with no release' do
+ expect(issue_items.without_release).to contain_exactly(issue_5, issue_6)
+ expect(mr_items.without_release).to contain_exactly(mr_3)
+ end
+ end
+
+ describe '#any_release' do
+ it 'returns all issues or all mrs tied to a release' do
+ expect(issue_items.any_release).to contain_exactly(issue_1, issue_2, issue_3, issue_4)
+ expect(mr_items.any_release).to contain_exactly(mr_1, mr_2)
+ end
+ end
+
+ describe '#with_release' do
+ it 'returns the issues tied to a specfic release' do
+ expect(issue_items.with_release('v1.0', project.id)).to contain_exactly(issue_1, issue_2, issue_3)
+ end
+
+ it 'returns the mrs tied to a specific release' do
+ expect(mr_items.with_release('v1.0', project.id)).to contain_exactly(mr_1)
+ end
+
+ context 'when a release has a milestone with one issue and another one with no issue' do
+ it 'returns that one issue' do
+ expect(issue_items.with_release('v2.0', project.id)).to contain_exactly(issue_3)
+ end
+
+ context 'when the milestone with no issue is added as a filter' do
+ it 'returns an empty list' do
+ expect(issue_items.with_release('v2.0', project.id).with_milestone('m3')).to be_empty
+ end
+ end
+
+ context 'when the milestone with the issue is added as a filter' do
+ it 'returns this issue' do
+ expect(issue_items.with_release('v2.0', project.id).with_milestone('m2')).to contain_exactly(issue_3)
+ end
+ end
+ end
+
+ context 'when there is no issue or mr under a specific release' do
+ it 'returns no issue or no mr' do
+ expect(issue_items.with_release('v4.0', project.id)).to be_empty
+ expect(mr_items.with_release('v4.0', project.id)).to be_empty
+ end
+ end
+
+ context 'when a non-existent release tag is passed in' do
+ it 'returns no issue or no mr' do
+ expect(issue_items.with_release('v999.0', project.id)).to be_empty
+ expect(mr_items.with_release('v999.0', project.id)).to be_empty
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/concerns/issuable_states_spec.rb b/spec/models/concerns/issuable_states_spec.rb
deleted file mode 100644
index a5e19cdfc4f..00000000000
--- a/spec/models/concerns/issuable_states_spec.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-# This spec checks if state_id column of issues and merge requests
-# are being synced on every save.
-# It can be removed in the next release. Check https://gitlab.com/gitlab-org/gitlab-foss/issues/51789 for more information.
-describe IssuableStates do
- [Issue, MergeRequest].each do |klass|
- it "syncs state_id column when #{klass.model_name.human} gets created" do
- klass.available_states.each do |state, state_id|
- issuable = build(klass.model_name.param_key, state: state.to_s)
-
- issuable.save!
-
- expect(issuable.state_id).to eq(state_id)
- end
- end
-
- it "syncs state_id column when #{klass.model_name.human} gets updated" do
- klass.available_states.each do |state, state_id|
- issuable = create(klass.model_name.param_key, state: state.to_s)
-
- issuable.update(state: state)
-
- expect(issuable.state_id).to eq(state_id)
- end
- end
- end
-end
diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb
index 6034344d034..883f678b8f5 100644
--- a/spec/models/concerns/mentionable_spec.rb
+++ b/spec/models/concerns/mentionable_spec.rb
@@ -166,6 +166,21 @@ describe Issue, "Mentionable" do
create(:issue, project: project, description: description, author: author)
end
end
+
+ describe '#store_mentions!' do
+ it_behaves_like 'mentions in description', :issue
+ it_behaves_like 'mentions in notes', :issue do
+ let(:note) { create(:note_on_issue) }
+ let(:mentionable) { note.noteable }
+ end
+ end
+
+ describe 'load mentions' do
+ it_behaves_like 'load mentions from DB', :issue do
+ let(:note) { create(:note_on_issue) }
+ let(:mentionable) { note.noteable }
+ end
+ end
end
describe Commit, 'Mentionable' do
@@ -221,4 +236,56 @@ describe Commit, 'Mentionable' do
end
end
end
+
+ describe '#store_mentions!' do
+ it_behaves_like 'mentions in notes', :commit do
+ let(:note) { create(:note_on_commit) }
+ let(:mentionable) { note.noteable }
+ end
+ end
+
+ describe 'load mentions' do
+ it_behaves_like 'load mentions from DB', :commit do
+ let(:note) { create(:note_on_commit) }
+ let(:mentionable) { note.noteable }
+ end
+ end
+end
+
+describe MergeRequest, 'Mentionable' do
+ describe '#store_mentions!' do
+ it_behaves_like 'mentions in description', :merge_request
+ it_behaves_like 'mentions in notes', :merge_request do
+ let(:project) { create(:project) }
+ let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
+ let(:note) { create(:note_on_merge_request, noteable: merge_request, project: merge_request.project) }
+ let(:mentionable) { note.noteable }
+ end
+ end
+
+ describe 'load mentions' do
+ it_behaves_like 'load mentions from DB', :merge_request do
+ let(:project) { create(:project) }
+ let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
+ let(:note) { create(:note_on_merge_request, noteable: merge_request, project: merge_request.project) }
+ let(:mentionable) { note.noteable }
+ end
+ end
+end
+
+describe Snippet, 'Mentionable' do
+ describe '#store_mentions!' do
+ it_behaves_like 'mentions in description', :project_snippet
+ it_behaves_like 'mentions in notes', :project_snippet do
+ let(:note) { create(:note_on_project_snippet) }
+ let(:mentionable) { note.noteable }
+ end
+ end
+
+ describe 'load mentions' do
+ it_behaves_like 'load mentions from DB', :project_snippet do
+ let(:note) { create(:note_on_project_snippet) }
+ let(:mentionable) { note.noteable }
+ end
+ end
end
diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb
index 7e9a8306612..d46c9747845 100644
--- a/spec/models/concerns/milestoneish_spec.rb
+++ b/spec/models/concerns/milestoneish_spec.rb
@@ -192,6 +192,23 @@ describe Milestone, 'Milestoneish' do
end
end
end
+
+ context 'when milestone is at parent level group' do
+ let(:parent_group) { create(:group) }
+ let(:group) { create(:group, parent: parent_group) }
+ let(:project) { create(:project, namespace: group) }
+ let(:milestone) { create(:milestone, group: parent_group) }
+
+ it 'does not return any merge request for a non member' do
+ merge_requests = milestone.merge_requests_visible_to_user(non_member)
+ expect(merge_requests).to be_empty
+ end
+
+ it 'returns milestone merge requests for a member' do
+ merge_requests = milestone.merge_requests_visible_to_user(member)
+ expect(merge_requests).to contain_exactly(merge_request)
+ end
+ end
end
describe '#complete?' do
diff --git a/spec/models/concerns/reactive_caching_spec.rb b/spec/models/concerns/reactive_caching_spec.rb
index 3d026932f59..4af6906ce2c 100644
--- a/spec/models/concerns/reactive_caching_spec.rb
+++ b/spec/models/concerns/reactive_caching_spec.rb
@@ -196,6 +196,12 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do
2.times { instance.exclusively_update_reactive_cache! }
end
+ it 'does not delete the value key' do
+ expect(Rails.cache).to receive(:delete).with(cache_key).never
+
+ go!
+ end
+
context 'and #calculate_reactive_cache raises an exception' do
before do
stub_reactive_cache(instance, "preexisting")
@@ -223,6 +229,12 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do
go!
end
+
+ it 'deletes the value key' do
+ expect(Rails.cache).to receive(:delete).with(cache_key).once
+
+ go!
+ end
end
context 'when the lease is already taken' do
diff --git a/spec/models/concerns/safe_url_spec.rb b/spec/models/concerns/safe_url_spec.rb
new file mode 100644
index 00000000000..0ad26660a60
--- /dev/null
+++ b/spec/models/concerns/safe_url_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe SafeUrl do
+ describe '#safe_url' do
+ class SafeUrlTestClass
+ include SafeUrl
+
+ attr_reader :url
+
+ def initialize(url)
+ @url = url
+ end
+ end
+
+ let(:test_class) { SafeUrlTestClass.new(url) }
+ let(:url) { 'http://example.com' }
+
+ subject { test_class.safe_url }
+
+ it { is_expected.to eq(url) }
+
+ context 'when URL contains credentials' do
+ let(:url) { 'http://foo:bar@example.com' }
+
+ it { is_expected.to eq('http://*****:*****@example.com')}
+
+ context 'when username is whitelisted' do
+ subject { test_class.safe_url(usernames_whitelist: usernames_whitelist) }
+
+ let(:usernames_whitelist) { %w[foo] }
+
+ it 'does expect the whitelisted username not to be masked' do
+ is_expected.to eq('http://foo:*****@example.com')
+ end
+ end
+ end
+
+ context 'when URL is empty' do
+ let(:url) { nil }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when URI raises an error' do
+ let(:url) { 123 }
+
+ it { is_expected.to be_nil }
+ end
+ end
+end
diff --git a/spec/models/concerns/sha256_attribute_spec.rb b/spec/models/concerns/sha256_attribute_spec.rb
new file mode 100644
index 00000000000..213723c2dcb
--- /dev/null
+++ b/spec/models/concerns/sha256_attribute_spec.rb
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Sha256Attribute do
+ let(:model) { Class.new { include Sha256Attribute } }
+
+ before do
+ columns = [
+ double(:column, name: 'name', type: :text),
+ double(:column, name: 'sha256', type: :binary)
+ ]
+
+ allow(model).to receive(:columns).and_return(columns)
+ end
+
+ describe '#sha_attribute' do
+ context 'when in non-production' do
+ before do
+ stub_rails_env('development')
+ end
+
+ context 'when the table exists' do
+ before do
+ allow(model).to receive(:table_exists?).and_return(true)
+ end
+
+ it 'defines a SHA attribute for a binary column' do
+ expect(model).to receive(:attribute)
+ .with(:sha256, an_instance_of(Gitlab::Database::Sha256Attribute))
+
+ model.sha256_attribute(:sha256)
+ end
+
+ it 'raises ArgumentError when the column type is not :binary' do
+ expect { model.sha256_attribute(:name) }.to raise_error(ArgumentError)
+ end
+ end
+
+ context 'when the table does not exist' do
+ it 'allows the attribute to be added and issues a warning' do
+ allow(model).to receive(:table_exists?).and_return(false)
+
+ expect(model).not_to receive(:columns)
+ expect(model).to receive(:attribute)
+ expect(model).to receive(:warn)
+
+ model.sha256_attribute(:name)
+ end
+ end
+
+ context 'when the column does not exist' do
+ it 'allows the attribute to be added and issues a warning' do
+ allow(model).to receive(:table_exists?).and_return(true)
+
+ expect(model).to receive(:columns)
+ expect(model).to receive(:attribute)
+ expect(model).to receive(:warn)
+
+ model.sha256_attribute(:no_name)
+ end
+ end
+
+ context 'when other execeptions are raised' do
+ it 'logs and re-rasises the error' do
+ allow(model).to receive(:table_exists?).and_raise(ActiveRecord::NoDatabaseError.new('does not exist'))
+
+ expect(model).not_to receive(:columns)
+ expect(model).not_to receive(:attribute)
+ expect(Gitlab::AppLogger).to receive(:error)
+
+ expect { model.sha256_attribute(:name) }.to raise_error(ActiveRecord::NoDatabaseError)
+ end
+ end
+ end
+
+ context 'when in production' do
+ before do
+ stub_rails_env('production')
+ end
+
+ it 'defines a SHA attribute' do
+ expect(model).not_to receive(:table_exists?)
+ expect(model).not_to receive(:columns)
+ expect(model).to receive(:attribute).with(:sha256, an_instance_of(Gitlab::Database::Sha256Attribute))
+
+ model.sha256_attribute(:sha256)
+ end
+ end
+ end
+end