diff options
Diffstat (limited to 'spec/models/concerns')
-rw-r--r-- | spec/models/concerns/group_descendant_spec.rb | 4 | ||||
-rw-r--r-- | spec/models/concerns/ignorable_columns_spec.rb | 88 | ||||
-rw-r--r-- | spec/models/concerns/issuable_spec.rb | 89 | ||||
-rw-r--r-- | spec/models/concerns/issuable_states_spec.rb | 30 | ||||
-rw-r--r-- | spec/models/concerns/mentionable_spec.rb | 67 | ||||
-rw-r--r-- | spec/models/concerns/milestoneish_spec.rb | 17 | ||||
-rw-r--r-- | spec/models/concerns/reactive_caching_spec.rb | 12 | ||||
-rw-r--r-- | spec/models/concerns/safe_url_spec.rb | 52 | ||||
-rw-r--r-- | spec/models/concerns/sha256_attribute_spec.rb | 91 |
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 |