diff options
Diffstat (limited to 'spec/models')
122 files changed, 2625 insertions, 1862 deletions
diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index 4bfa953df40..e131661602e 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -328,6 +328,69 @@ RSpec.describe Ability do end end + describe '.feature_flags_readable_by_user' do + context 'without a user' do + it 'returns no feature flags' do + feature_flag_1 = build(:operations_feature_flag) + feature_flag_2 = build(:operations_feature_flag, project: build(:project, :public)) + + feature_flags = described_class + .feature_flags_readable_by_user([feature_flag_1, feature_flag_2]) + + expect(feature_flags).to eq([]) + end + end + + context 'with a user' do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:feature_flag) { create(:operations_feature_flag, project: project) } + let(:cross_project) { create(:project) } + let(:cross_project_feature_flag) { create(:operations_feature_flag, project: cross_project) } + + let(:other_feature_flag) { create(:operations_feature_flag) } + let(:all_feature_flags) do + [feature_flag, cross_project_feature_flag, other_feature_flag] + end + + subject(:readable_feature_flags) do + described_class.feature_flags_readable_by_user(all_feature_flags, user) + end + + before do + project.add_developer(user) + cross_project.add_developer(user) + end + + it 'returns feature flags visible to the user' do + expect(readable_feature_flags).to contain_exactly(feature_flag, cross_project_feature_flag) + end + + context 'when a user cannot read cross project and a filter is passed' do + before do + allow(described_class).to receive(:allowed?).and_call_original + expect(described_class).to receive(:allowed?).with(user, :read_cross_project) { false } + end + + subject(:readable_feature_flags) do + read_cross_project_filter = -> (feature_flags) do + feature_flags.select { |flag| flag.project == project } + end + described_class.feature_flags_readable_by_user( + all_feature_flags, user, + filters: { read_cross_project: read_cross_project_filter } + ) + end + + it 'returns only feature flags of the specified project without checking access on others' do + expect(described_class).not_to receive(:allowed?).with(user, :read_feature_flag, cross_project_feature_flag) + + expect(readable_feature_flags).to contain_exactly(feature_flag) + end + end + end + end + describe '.project_disabled_features_rules' do let(:project) { create(:project, :wiki_disabled) } diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb index a97574fa524..e87996fc1f0 100644 --- a/spec/models/abuse_report_spec.rb +++ b/spec/models/abuse_report_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' RSpec.describe AbuseReport do let_it_be(:report, reload: true) { create(:abuse_report) } let_it_be(:user, reload: true) { create(:admin) } + subject { report } it { expect(subject).to be_valid } diff --git a/spec/models/alert_management/alert_spec.rb b/spec/models/alert_management/alert_spec.rb index 80a45b1c1be..18d486740b8 100644 --- a/spec/models/alert_management/alert_spec.rb +++ b/spec/models/alert_management/alert_spec.rb @@ -100,6 +100,7 @@ RSpec.describe AlertManagement::Alert do describe 'fingerprint' do let_it_be(:fingerprint) { 'fingerprint' } let_it_be(:project3, refind: true) { create(:project) } + let(:new_alert) { build(:alert_management_alert, fingerprint: fingerprint, project: project3) } subject { new_alert } diff --git a/spec/models/application_setting/term_spec.rb b/spec/models/application_setting/term_spec.rb index 51a6027698f..d9efa597352 100644 --- a/spec/models/application_setting/term_spec.rb +++ b/spec/models/application_setting/term_spec.rb @@ -3,9 +3,7 @@ require 'spec_helper' RSpec.describe ApplicationSetting::Term do - describe 'validations' do - it { is_expected.to validate_presence_of(:terms) } - end + it { is_expected.to nullify_if_blank(:terms) } describe '.latest' do it 'finds the latest terms' do diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 4e72d558b52..80471a09bbd 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -134,6 +134,14 @@ RSpec.describe ApplicationSetting do it { is_expected.to allow_value('disabled').for(:whats_new_variant) } it { is_expected.not_to allow_value(nil).for(:whats_new_variant) } + it { is_expected.not_to allow_value(['']).for(:valid_runner_registrars) } + it { is_expected.not_to allow_value(['OBVIOUSLY_WRONG']).for(:valid_runner_registrars) } + it { is_expected.not_to allow_value(%w(project project)).for(:valid_runner_registrars) } + it { is_expected.not_to allow_value([nil]).for(:valid_runner_registrars) } + it { is_expected.not_to allow_value(nil).for(:valid_runner_registrars) } + it { is_expected.to allow_value([]).for(:valid_runner_registrars) } + it { is_expected.to allow_value(%w(project group)).for(:valid_runner_registrars) } + context 'help_page_documentation_base_url validations' do it { is_expected.to allow_value(nil).for(:help_page_documentation_base_url) } it { is_expected.to allow_value('https://docs.gitlab.com').for(:help_page_documentation_base_url) } @@ -250,6 +258,19 @@ RSpec.describe ApplicationSetting do it { is_expected.to allow_value(nil).for(:snowplow_collector_hostname) } end + context 'when mailgun_events_enabled is enabled' do + before do + setting.mailgun_events_enabled = true + end + + it { is_expected.to validate_presence_of(:mailgun_signing_key) } + it { is_expected.to validate_length_of(:mailgun_signing_key).is_at_most(255) } + end + + context 'when mailgun_events_enabled is not enabled' do + it { is_expected.not_to validate_presence_of(:mailgun_signing_key) } + end + context "when user accepted let's encrypt terms of service" do before do expect do diff --git a/spec/models/audit_event_spec.rb b/spec/models/audit_event_spec.rb index bc603bc5ab6..4fba5fddc92 100644 --- a/spec/models/audit_event_spec.rb +++ b/spec/models/audit_event_spec.rb @@ -10,6 +10,71 @@ RSpec.describe AuditEvent do end end + describe 'callbacks' do + describe '#parallel_persist' do + shared_examples 'a parallel persisted field' do + using RSpec::Parameterized::TableSyntax + + where(:column, :details, :expected_value) do + :value | nil | :value + nil | :value | :value + :value | :another_value | :value + nil | nil | nil + end + + with_them do + let(:values) { { value: value, another_value: "#{value}88" } } + + let(:audit_event) do + build(:audit_event, name => values[column], details: { name => values[details] }) + end + + it 'sets both values to be the same', :aggregate_failures do + audit_event.validate + + expect(audit_event[name]).to eq(values[expected_value]) + expect(audit_event.details[name]).to eq(values[expected_value]) + end + end + end + + context 'wih author_name' do + let(:name) { :author_name } + let(:value) { 'Mary Poppins' } + + it_behaves_like 'a parallel persisted field' + end + + context 'with entity_path' do + let(:name) { :entity_path } + let(:value) { 'gitlab-org' } + + it_behaves_like 'a parallel persisted field' + end + + context 'with target_details' do + let(:name) { :target_details } + let(:value) { 'gitlab-org/gitlab' } + + it_behaves_like 'a parallel persisted field' + end + + context 'with target_type' do + let(:name) { :target_type } + let(:value) { 'Project' } + + it_behaves_like 'a parallel persisted field' + end + + context 'with target_id' do + let(:name) { :target_id } + let(:value) { 8 } + + it_behaves_like 'a parallel persisted field' + end + end + end + it 'sanitizes custom_message in the details hash' do audit_event = create(:project_audit_event, details: { target_id: 678, custom_message: '<strong>Arnold</strong>' }) diff --git a/spec/models/award_emoji_spec.rb b/spec/models/award_emoji_spec.rb index f268408c095..ebd1441f901 100644 --- a/spec/models/award_emoji_spec.rb +++ b/spec/models/award_emoji_spec.rb @@ -119,6 +119,36 @@ RSpec.describe AwardEmoji do end end + describe 'bumping updated at' do + let(:note) { create(:note_on_issue) } + let(:award_emoji) { build(:award_emoji, user: build(:user), awardable: note) } + + it 'calls bump_updated_at on the note when saved' do + expect(note).to receive(:bump_updated_at) + + award_emoji.save! + end + + it 'calls bump_updated_at on the note when destroyed' do + expect(note).to receive(:bump_updated_at) + + award_emoji.destroy! + end + + context 'on another awardable' do + let(:issue) { create(:issue) } + let(:award_emoji) { build(:award_emoji, user: build(:user), awardable: issue) } + + it 'does not error out when saved' do + expect { award_emoji.save! }.not_to raise_error + end + + it 'does not error out when destroy' do + expect { award_emoji.destroy! }.not_to raise_error + end + end + end + describe '.award_counts_for_user' do let(:user) { create(:user) } @@ -141,4 +171,43 @@ RSpec.describe AwardEmoji do expect(awards).to eq('thumbsup' => 2) end end + + describe 'updating upvotes_count' do + context 'on an issue' do + let(:issue) { create(:issue) } + let(:upvote) { build(:award_emoji, :upvote, user: build(:user), awardable: issue) } + let(:downvote) { build(:award_emoji, :downvote, user: build(:user), awardable: issue) } + + it 'updates upvotes_count on the issue when saved' do + expect(issue).to receive(:update_column).with(:upvotes_count, 1).once + + upvote.save! + downvote.save! + end + + it 'updates upvotes_count on the issue when destroyed' do + expect(issue).to receive(:update_column).with(:upvotes_count, 0).once + + upvote.destroy! + downvote.destroy! + end + end + + context 'on another awardable' do + let(:merge_request) { create(:merge_request) } + let(:award_emoji) { build(:award_emoji, user: build(:user), awardable: merge_request) } + + it 'does not update upvotes_count on the merge_request when saved' do + expect(merge_request).not_to receive(:update_column) + + award_emoji.save! + end + + it 'does not update upvotes_count on the merge_request when destroyed' do + expect(merge_request).not_to receive(:update_column) + + award_emoji.destroy! + end + end + end end diff --git a/spec/models/blob_viewer/markup_spec.rb b/spec/models/blob_viewer/markup_spec.rb index 13b040d62d0..dae1b79dda2 100644 --- a/spec/models/blob_viewer/markup_spec.rb +++ b/spec/models/blob_viewer/markup_spec.rb @@ -24,15 +24,5 @@ RSpec.describe BlobViewer::Markup do expect(subject.banzai_render_context.keys).to include(:rendered) end end - - context 'when cached_markdown_blob feature flag is disabled' do - before do - stub_feature_flags(cached_markdown_blob: false) - end - - it 'does not set cache_key key' do - expect(subject.banzai_render_context.keys).not_to include(:cache_key) - end - end end end diff --git a/spec/models/bulk_import_spec.rb b/spec/models/bulk_import_spec.rb index 1a7e1ed8119..4cfec6b20b7 100644 --- a/spec/models/bulk_import_spec.rb +++ b/spec/models/bulk_import_spec.rb @@ -15,4 +15,10 @@ RSpec.describe BulkImport, type: :model do it { is_expected.to define_enum_for(:source_type).with_values(%i[gitlab]) } end + + describe '.all_human_statuses' do + it 'returns all human readable entity statuses' do + expect(described_class.all_human_statuses).to contain_exactly('created', 'started', 'finished', 'failed') + end + end end diff --git a/spec/models/bulk_imports/entity_spec.rb b/spec/models/bulk_imports/entity_spec.rb index d1b7125a6e6..11a3e53dd16 100644 --- a/spec/models/bulk_imports/entity_spec.rb +++ b/spec/models/bulk_imports/entity_spec.rb @@ -134,4 +134,24 @@ RSpec.describe BulkImports::Entity, type: :model do expect(entity.encoded_source_full_path).to eq(expected) end end + + describe 'scopes' do + describe '.by_user_id' do + it 'returns entities associated with specified user' do + user = create(:user) + import = create(:bulk_import, user: user) + entity_1 = create(:bulk_import_entity, bulk_import: import) + entity_2 = create(:bulk_import_entity, bulk_import: import) + create(:bulk_import_entity) + + expect(described_class.by_user_id(user.id)).to contain_exactly(entity_1, entity_2) + end + end + end + + describe '.all_human_statuses' do + it 'returns all human readable entity statuses' do + expect(described_class.all_human_statuses).to contain_exactly('created', 'started', 'finished', 'failed') + end + end end diff --git a/spec/models/bulk_imports/file_transfer/group_config_spec.rb b/spec/models/bulk_imports/file_transfer/group_config_spec.rb index 4611a00b0cc..1e566a7b042 100644 --- a/spec/models/bulk_imports/file_transfer/group_config_spec.rb +++ b/spec/models/bulk_imports/file_transfer/group_config_spec.rb @@ -34,6 +34,10 @@ RSpec.describe BulkImports::FileTransfer::GroupConfig do it 'returns a list of top level exportable relations' do expect(subject.portable_relations).to include('milestones', 'badges', 'boards', 'labels') end + + it 'does not include skipped relations' do + expect(subject.portable_relations).not_to include('members') + end end describe '#top_relation_tree' do diff --git a/spec/models/bulk_imports/file_transfer/project_config_spec.rb b/spec/models/bulk_imports/file_transfer/project_config_spec.rb index 2995556a58d..db037528ec1 100644 --- a/spec/models/bulk_imports/file_transfer/project_config_spec.rb +++ b/spec/models/bulk_imports/file_transfer/project_config_spec.rb @@ -34,6 +34,10 @@ RSpec.describe BulkImports::FileTransfer::ProjectConfig do it 'returns a list of top level exportable relations' do expect(subject.portable_relations).to include('issues', 'labels', 'milestones', 'merge_requests') end + + it 'does not include skipped relations' do + expect(subject.portable_relations).not_to include('project_members', 'group_members') + end end describe '#top_relation_tree' do diff --git a/spec/models/chat_name_spec.rb b/spec/models/chat_name_spec.rb index 4d77bd53158..9ed00003ac1 100644 --- a/spec/models/chat_name_spec.rb +++ b/spec/models/chat_name_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe ChatName do let_it_be(:chat_name) { create(:chat_name) } + subject { chat_name } it { is_expected.to belong_to(:integration) } diff --git a/spec/models/chat_team_spec.rb b/spec/models/chat_team_spec.rb index 08fd05324aa..2e8cdb7a316 100644 --- a/spec/models/chat_team_spec.rb +++ b/spec/models/chat_team_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe ChatTeam do let_it_be(:chat_team) { create(:chat_team) } + subject { chat_team } # Associations diff --git a/spec/models/ci/build_dependencies_spec.rb b/spec/models/ci/build_dependencies_spec.rb index 331ba9953ca..cd330324840 100644 --- a/spec/models/ci/build_dependencies_spec.rb +++ b/spec/models/ci/build_dependencies_spec.rb @@ -55,6 +55,24 @@ RSpec.describe Ci::BuildDependencies do end end end + + context 'when needs refer to jobs from the same stage' do + let(:job) do + create(:ci_build, + pipeline: pipeline, + name: 'dag_job', + scheduling_type: :dag, + stage_idx: 2, + stage: 'deploy' + ) + end + + before do + create(:ci_build_need, build: job, name: 'staging', artifacts: true) + end + + it { is_expected.to contain_exactly(staging) } + end end describe 'jobs from specified dependencies' do diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 62dec522161..0c344270e0b 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -39,6 +39,34 @@ RSpec.describe Ci::Build do it { is_expected.to delegate_method(:merge_request_ref?).to(:pipeline) } it { is_expected.to delegate_method(:legacy_detached_merge_request_pipeline?).to(:pipeline) } + shared_examples 'calling proper BuildFinishedWorker' do + context 'when ci_build_finished_worker_namespace_changed feature flag enabled' do + before do + stub_feature_flags(ci_build_finished_worker_namespace_changed: build.project) + end + + it 'calls Ci::BuildFinishedWorker' do + expect(Ci::BuildFinishedWorker).to receive(:perform_async) + expect(::BuildFinishedWorker).not_to receive(:perform_async) + + subject + end + end + + context 'when ci_build_finished_worker_namespace_changed feature flag disabled' do + before do + stub_feature_flags(ci_build_finished_worker_namespace_changed: false) + end + + it 'calls ::BuildFinishedWorker' do + expect(::BuildFinishedWorker).to receive(:perform_async) + expect(Ci::BuildFinishedWorker).not_to receive(:perform_async) + + subject + end + end + end + describe 'associations' do it 'has a bidirectional relationship with projects' do expect(described_class.reflect_on_association(:project).has_inverse?).to eq(:builds) @@ -384,7 +412,7 @@ RSpec.describe Ci::Build do context 'when there is a queuing entry already present' do before do - ::Ci::PendingBuild.create!(build: build, project: build.project) + create(:ci_pending_build, build: build, project: build.project) end it 'does not raise an error' do @@ -396,7 +424,7 @@ RSpec.describe Ci::Build do context 'when both failure scenario happen at the same time' do before do ::Ci::Build.find(build.id).update_column(:lock_version, 100) - ::Ci::PendingBuild.create!(build: build, project: build.project) + create(:ci_pending_build, build: build, project: build.project) end it 'raises stale object error exception' do @@ -478,7 +506,7 @@ RSpec.describe Ci::Build do let(:build) { create(:ci_build, :pending) } before do - ::Ci::PendingBuild.create!(build: build, project: build.project) + create(:ci_pending_build, build: build, project: build.project) ::Ci::Build.find(build.id).update_column(:lock_version, 100) end @@ -1323,6 +1351,7 @@ RSpec.describe Ci::Build do end it_behaves_like 'avoid deadlock' + it_behaves_like 'calling proper BuildFinishedWorker' it 'transits deployment status to success' do subject @@ -1335,6 +1364,7 @@ RSpec.describe Ci::Build do let(:event) { :drop! } it_behaves_like 'avoid deadlock' + it_behaves_like 'calling proper BuildFinishedWorker' it 'transits deployment status to failed' do subject @@ -1359,6 +1389,7 @@ RSpec.describe Ci::Build do let(:event) { :cancel! } it_behaves_like 'avoid deadlock' + it_behaves_like 'calling proper BuildFinishedWorker' it 'transits deployment status to canceled' do subject @@ -1966,6 +1997,23 @@ RSpec.describe Ci::Build do end end + describe '#tag_list' do + let_it_be(:build) { create(:ci_build, tag_list: ['tag']) } + + context 'when tags are preloaded' do + it 'does not trigger queries' do + build_with_tags = described_class.eager_load_tags.id_in([build]).to_a.first + + expect { build_with_tags.tag_list }.not_to exceed_all_query_limit(0) + expect(build_with_tags.tag_list).to eq(['tag']) + end + end + + context 'when tags are not preloaded' do + it { expect(described_class.find(build.id).tag_list).to eq(['tag']) } + end + end + describe '#has_tags?' do context 'when build has tags' do subject { create(:ci_build, tag_list: ['tag']) } @@ -2155,15 +2203,15 @@ RSpec.describe Ci::Build do end it 'contains options' do - expect(build.options).to eq(options.stringify_keys) + expect(build.options).to eq(options.symbolize_keys) end - it 'allows to access with keys' do + it 'allows to access with symbolized keys' do expect(build.options[:image]).to eq('ruby:2.7') end - it 'allows to access with strings' do - expect(build.options['image']).to eq('ruby:2.7') + it 'rejects access with string keys' do + expect(build.options['image']).to be_nil end context 'when ci_build_metadata_config is set' do @@ -2172,7 +2220,7 @@ RSpec.describe Ci::Build do end it 'persist data in build metadata' do - expect(build.metadata.read_attribute(:config_options)).to eq(options.stringify_keys) + expect(build.metadata.read_attribute(:config_options)).to eq(options.symbolize_keys) end it 'does not persist data in build' do @@ -4476,26 +4524,12 @@ RSpec.describe Ci::Build do it { is_expected.to include(:upload_multiple_artifacts) } end - context 'when artifacts exclude is defined and the is feature enabled' do + context 'when artifacts exclude is defined' do let(:options) do { artifacts: { exclude: %w[something] } } end - context 'when a feature flag is enabled' do - before do - stub_feature_flags(ci_artifacts_exclude: true) - end - - it { is_expected.to include(:artifacts_exclude) } - end - - context 'when a feature flag is disabled' do - before do - stub_feature_flags(ci_artifacts_exclude: false) - end - - it { is_expected.not_to include(:artifacts_exclude) } - end + it { is_expected.to include(:artifacts_exclude) } end end @@ -4712,9 +4746,9 @@ RSpec.describe Ci::Build do describe '#read_metadata_attribute' do let(:build) { create(:ci_build, :degenerated) } - let(:build_options) { { "key" => "build" } } - let(:metadata_options) { { "key" => "metadata" } } - let(:default_options) { { "key" => "default" } } + let(:build_options) { { key: "build" } } + let(:metadata_options) { { key: "metadata" } } + let(:default_options) { { key: "default" } } subject { build.send(:read_metadata_attribute, :options, :config_options, default_options) } @@ -4749,8 +4783,8 @@ RSpec.describe Ci::Build do describe '#write_metadata_attribute' do let(:build) { create(:ci_build, :degenerated) } - let(:options) { { "key" => "new options" } } - let(:existing_options) { { "key" => "existing options" } } + let(:options) { { key: "new options" } } + let(:existing_options) { { key: "existing options" } } subject { build.send(:write_metadata_attribute, :options, :config_options, options) } diff --git a/spec/models/ci/build_trace_chunk_spec.rb b/spec/models/ci/build_trace_chunk_spec.rb index a16453f3d01..b6e128c317c 100644 --- a/spec/models/ci/build_trace_chunk_spec.rb +++ b/spec/models/ci/build_trace_chunk_spec.rb @@ -152,14 +152,6 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state, :clean_git context 'default value' do it { expect(subject).to eq('redis_trace_chunks') } - - context 'when dedicated_redis_trace_chunks is disabled' do - before do - stub_feature_flags(dedicated_redis_trace_chunks: false) - end - - it { expect(subject).to eq('redis') } - end end end diff --git a/spec/models/ci/build_trace_chunks/fog_spec.rb b/spec/models/ci/build_trace_chunks/fog_spec.rb index d9e9533fb26..21dab6fad60 100644 --- a/spec/models/ci/build_trace_chunks/fog_spec.rb +++ b/spec/models/ci/build_trace_chunks/fog_spec.rb @@ -102,6 +102,57 @@ RSpec.describe Ci::BuildTraceChunks::Fog do end end + describe '#append_data' do + let(:initial_data) { (+'😺').force_encoding(Encoding::ASCII_8BIT) } + let(:model) { create(:ci_build_trace_chunk, :fog_with_data, initial_data: initial_data) } + let(:data) { data_store.data(model) } + + context 'when ci_job_trace_force_encode is enabled' do + it 'appends ASCII data' do + data_store.append_data(model, +'hello world', 4) + + expect(data.encoding).to eq(Encoding::ASCII_8BIT) + expect(data.force_encoding(Encoding::UTF_8)).to eq('😺hello world') + end + + it 'appends UTF-8 data' do + data_store.append_data(model, +'Résumé', 4) + + expect(data.encoding).to eq(Encoding::ASCII_8BIT) + expect(data.force_encoding(Encoding::UTF_8)).to eq("😺Résumé") + end + + context 'when initial data is UTF-8' do + let(:initial_data) { +'😺' } + + it 'appends ASCII data' do + data_store.append_data(model, +'hello world', 4) + + expect(data.encoding).to eq(Encoding::ASCII_8BIT) + expect(data.force_encoding(Encoding::UTF_8)).to eq('😺hello world') + end + end + end + + context 'when ci_job_trace_force_encode is disabled' do + before do + stub_feature_flags(ci_job_trace_force_encode: false) + end + + it 'appends ASCII data' do + data_store.append_data(model, +'hello world', 4) + + expect(data.encoding).to eq(Encoding::ASCII_8BIT) + expect(data.force_encoding(Encoding::UTF_8)).to eq('😺hello world') + end + + it 'throws an exception when appending UTF-8 data' do + expect(Gitlab::ErrorTracking).to receive(:track_and_raise_exception).and_call_original + expect { data_store.append_data(model, +'Résumé', 4) }.to raise_exception(Encoding::CompatibilityError) + end + end + end + describe '#delete_data' do subject { data_store.delete_data(model) } diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb index 582639b105e..a94a1dd284a 100644 --- a/spec/models/ci/job_artifact_spec.rb +++ b/spec/models/ci/job_artifact_spec.rb @@ -268,6 +268,29 @@ RSpec.describe Ci::JobArtifact do end end + describe '.for_project' do + it 'returns artifacts only for given project(s)', :aggregate_failures do + artifact1 = create(:ci_job_artifact) + artifact2 = create(:ci_job_artifact) + create(:ci_job_artifact) + + expect(described_class.for_project(artifact1.project)).to match_array([artifact1]) + expect(described_class.for_project([artifact1.project, artifact2.project])).to match_array([artifact1, artifact2]) + end + end + + describe 'created_in_time_range' do + it 'returns artifacts created in given time range', :aggregate_failures do + artifact1 = create(:ci_job_artifact, created_at: 1.day.ago) + artifact2 = create(:ci_job_artifact, created_at: 1.month.ago) + artifact3 = create(:ci_job_artifact, created_at: 1.year.ago) + + expect(described_class.created_in_time_range(from: 1.week.ago)).to match_array([artifact1]) + expect(described_class.created_in_time_range(to: 1.week.ago)).to match_array([artifact2, artifact3]) + expect(described_class.created_in_time_range(from: 2.months.ago, to: 1.week.ago)).to match_array([artifact2]) + end + end + describe 'callbacks' do describe '#schedule_background_upload' do subject { create(:ci_job_artifact, :archive) } diff --git a/spec/models/ci/job_token/project_scope_link_spec.rb b/spec/models/ci/job_token/project_scope_link_spec.rb index d18495b9312..dd6a75dfd89 100644 --- a/spec/models/ci/job_token/project_scope_link_spec.rb +++ b/spec/models/ci/job_token/project_scope_link_spec.rb @@ -65,4 +65,22 @@ RSpec.describe Ci::JobToken::ProjectScopeLink do expect(subject).to contain_exactly(target_link) end end + + describe '.for_source_and_target' do + let_it_be(:link) { create(:ci_job_token_project_scope_link, source_project: project) } + + subject { described_class.for_source_and_target(project, target_project) } + + context 'when link is found' do + let(:target_project) { link.target_project } + + it { is_expected.to eq(link) } + end + + context 'when link is not found' do + let(:target_project) { create(:project) } + + it { is_expected.to be_nil } + end + end end diff --git a/spec/models/ci/job_token/scope_spec.rb b/spec/models/ci/job_token/scope_spec.rb index c731a2634f5..4b95adf8476 100644 --- a/spec/models/ci/job_token/scope_spec.rb +++ b/spec/models/ci/job_token/scope_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Ci::JobToken::Scope do - let_it_be(:project) { create(:project) } + let_it_be(:project) { create(:project, ci_job_token_scope_enabled: true).tap(&:save!) } let(:scope) { described_class.new(project) } @@ -29,7 +29,7 @@ RSpec.describe Ci::JobToken::Scope do end end - describe 'includes?' do + describe '#includes?' do subject { scope.includes?(target_project) } context 'when param is the project defining the scope' do diff --git a/spec/models/ci/pending_build_spec.rb b/spec/models/ci/pending_build_spec.rb index c1d4f4b0a5e..b64f3999232 100644 --- a/spec/models/ci/pending_build_spec.rb +++ b/spec/models/ci/pending_build_spec.rb @@ -20,7 +20,7 @@ RSpec.describe Ci::PendingBuild do context 'when another queuing entry exists for given build' do before do - described_class.create!(build: build, project: project, protected: false) + create(:ci_pending_build, build: build, project: project) end it 'returns a build id as a result' do @@ -29,5 +29,61 @@ RSpec.describe Ci::PendingBuild do expect(result.rows.dig(0, 0)).to eq build.id end end + + context 'when project does not have shared runner' do + it 'sets instance_runners_enabled to false' do + described_class.upsert_from_build!(build) + + expect(described_class.last.instance_runners_enabled).to be_falsey + end + end + + context 'when project has shared runner' do + let_it_be(:runner) { create(:ci_runner, :instance) } + + context 'when ci_pending_builds_maintain_shared_runners_data is enabled' do + it 'sets instance_runners_enabled to true' do + described_class.upsert_from_build!(build) + + expect(described_class.last.instance_runners_enabled).to be_truthy + end + + context 'when project is about to be deleted' do + before do + build.project.update!(pending_delete: true) + end + + it 'sets instance_runners_enabled to false' do + described_class.upsert_from_build!(build) + + expect(described_class.last.instance_runners_enabled).to be_falsey + end + end + + context 'when builds are disabled' do + before do + build.project.project_feature.update!(builds_access_level: false) + end + + it 'sets instance_runners_enabled to false' do + described_class.upsert_from_build!(build) + + expect(described_class.last.instance_runners_enabled).to be_falsey + end + end + end + + context 'when ci_pending_builds_maintain_shared_runners_data is disabled' do + before do + stub_feature_flags(ci_pending_builds_maintain_shared_runners_data: false) + end + + it 'sets instance_runners_enabled to false' do + described_class.upsert_from_build!(build) + + expect(described_class.last.instance_runners_enabled).to be_falsey + end + end + end end end diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb index cf73460bf1e..8de3ebb18b9 100644 --- a/spec/models/ci/pipeline_schedule_spec.rb +++ b/spec/models/ci/pipeline_schedule_spec.rb @@ -123,8 +123,15 @@ RSpec.describe Ci::PipelineSchedule do '*/5 * * * *' | '0 * * * *' | (1.day.in_minutes / 1.hour.in_minutes).to_i | true | Time.zone.local(2021, 5, 27, 11, 0) | Time.zone.local(2021, 5, 27, 12, 0) '*/5 * * * *' | '0 * * * *' | (1.day.in_minutes / 2.hours.in_minutes).to_i | true | Time.zone.local(2021, 5, 27, 11, 0) | Time.zone.local(2021, 5, 27, 12, 5) '*/5 * * * *' | '0 1 * * *' | (1.day.in_minutes / 1.hour.in_minutes).to_i | true | Time.zone.local(2021, 5, 27, 1, 0) | Time.zone.local(2021, 5, 28, 1, 0) - '*/5 * * * *' | '0 1 * * *' | (1.day.in_minutes / 1.hour.in_minutes).to_i | true | Time.zone.local(2021, 5, 27, 1, 0) | Time.zone.local(2021, 5, 28, 1, 0) + '*/5 * * * *' | '0 1 * * *' | (1.day.in_minutes / 10).to_i | true | Time.zone.local(2021, 5, 27, 1, 0) | Time.zone.local(2021, 5, 28, 1, 0) + '*/5 * * * *' | '0 1 * * *' | (1.day.in_minutes / 8).to_i | true | Time.zone.local(2021, 5, 27, 1, 0) | Time.zone.local(2021, 5, 28, 1, 0) '*/5 * * * *' | '0 1 1 * *' | (1.day.in_minutes / 1.hour.in_minutes).to_i | true | Time.zone.local(2021, 5, 1, 1, 0) | Time.zone.local(2021, 6, 1, 1, 0) + '*/9 * * * *' | '0 1 1 * *' | (1.day.in_minutes / 1.hour.in_minutes).to_i | true | Time.zone.local(2021, 5, 1, 1, 9) | Time.zone.local(2021, 6, 1, 1, 0) + '*/9 * * * *' | '0 1 1 * *' | (1.day.in_minutes / 1.hour.in_minutes).to_i | false | Time.zone.local(2021, 5, 1, 1, 9) | Time.zone.local(2021, 6, 1, 1, 9) + '*/5 * * * *' | '59 14 * * *' | (1.day.in_minutes / 1.hour.in_minutes).to_i | true | Time.zone.local(2021, 5, 1, 15, 0) | Time.zone.local(2021, 5, 2, 15, 0) + '*/5 * * * *' | '59 14 * * *' | (1.day.in_minutes / 1.hour.in_minutes).to_i | false | Time.zone.local(2021, 5, 1, 15, 0) | Time.zone.local(2021, 5, 2, 15, 0) + '*/5 * * * *' | '45 21 1 2 *' | (1.day.in_minutes / 5).to_i | true | Time.zone.local(2021, 2, 1, 21, 45) | Time.zone.local(2022, 2, 1, 21, 45) + '*/5 * * * *' | '45 21 1 2 *' | (1.day.in_minutes / 5).to_i | false | Time.zone.local(2021, 2, 1, 21, 45) | Time.zone.local(2022, 2, 1, 21, 50) end with_them do diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 26fc4b140c1..74a476a6422 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -11,6 +11,10 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do let_it_be(:namespace) { create_default(:namespace).freeze } let_it_be(:project) { create_default(:project, :repository).freeze } + it 'paginates 15 pipeleines per page' do + expect(described_class.default_per_page).to eq(15) + end + it_behaves_like 'having unique enum values' it { is_expected.to belong_to(:project) } @@ -2768,6 +2772,41 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do expect(control2.count).to eq(control1.count + extra_update_queries + extra_generic_commit_status_validation_queries) end end + + context 'when the first try cannot get an exclusive lock' do + let(:retries) { 1 } + + subject(:cancel_running) { pipeline.cancel_running(retries: retries) } + + before do + build = create(:ci_build, :running, pipeline: pipeline) + + allow(pipeline.cancelable_statuses).to receive(:find_in_batches).and_yield([build]) + + call_count = 0 + allow(build).to receive(:cancel).and_wrap_original do |original, *args| + call_count >= retries ? raise(ActiveRecord::StaleObjectError) : original.call(*args) + + call_count += 1 + end + end + + it 'retries again and cancels the build' do + cancel_running + + expect(latest_status).to contain_exactly('canceled') + end + + context 'when the retries parameter is 0' do + let(:retries) { 0 } + + it 'raises error' do + expect do + cancel_running + end.to raise_error(ActiveRecord::StaleObjectError) + end + end + end end describe '#retry_failed' do @@ -2854,7 +2893,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end it 'builds hook data once' do - create(:pipelines_email_service) + create(:pipelines_email_integration) expect(Gitlab::DataBuilder::Pipeline).to receive(:build).once.and_call_original @@ -3772,16 +3811,6 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do it 'can generate a codequality report' do expect(subject).to be_truthy end - - context 'when feature is disabled' do - before do - stub_feature_flags(codequality_mr_diff: false) - end - - it 'can not generate a codequality report' do - expect(subject).to be_falsey - end - end end end @@ -4355,16 +4384,14 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end end - describe '#base_and_ancestors' do - subject { pipeline.base_and_ancestors(same_project: same_project) } + describe '#self_and_upstreams' do + subject(:self_and_upstreams) { pipeline.self_and_upstreams } let_it_be(:pipeline) { create(:ci_pipeline, :created) } - let(:same_project) { false } - context 'when pipeline is not child nor parent' do it 'returns just the pipeline itself' do - expect(subject).to contain_exactly(pipeline) + expect(self_and_upstreams).to contain_exactly(pipeline) end end @@ -4378,7 +4405,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end it 'returns parent and self' do - expect(subject).to contain_exactly(parent, pipeline) + expect(self_and_upstreams).to contain_exactly(parent, pipeline) end end @@ -4390,7 +4417,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end it 'returns self' do - expect(subject).to contain_exactly(pipeline) + expect(self_and_upstreams).to contain_exactly(pipeline) end end @@ -4406,11 +4433,11 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end it 'returns self, parent and ancestor' do - expect(subject).to contain_exactly(ancestor, parent, pipeline) + expect(self_and_upstreams).to contain_exactly(ancestor, parent, pipeline) end end - context 'when pipeline is a triggered pipeline' do + context 'when pipeline is a triggered pipeline from a different project' do let_it_be(:pipeline) { create(:ci_pipeline, :created) } let(:upstream) { create(:ci_pipeline, project: create(:project)) } @@ -4419,18 +4446,41 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do create_source_pipeline(upstream, pipeline) end - context 'same_project: false' do - it 'returns upstream and self' do - expect(subject).to contain_exactly(pipeline, upstream) - end + it 'returns upstream and self' do + expect(self_and_upstreams).to contain_exactly(pipeline, upstream) end + end + end - context 'same_project: true' do - let(:same_project) { true } + describe '#self_and_ancestors' do + subject(:self_and_ancestors) { pipeline.self_and_ancestors } - it 'returns self' do - expect(subject).to contain_exactly(pipeline) - end + context 'when pipeline is child' do + let(:pipeline) { create(:ci_pipeline, :created) } + let(:parent) { create(:ci_pipeline) } + let(:sibling) { create(:ci_pipeline) } + + before do + create_source_pipeline(parent, pipeline) + create_source_pipeline(parent, sibling) + end + + it 'returns parent and self' do + expect(self_and_ancestors).to contain_exactly(parent, pipeline) + end + end + + context 'when pipeline is a triggered pipeline from a different project' do + let_it_be(:pipeline) { create(:ci_pipeline, :created) } + + let(:upstream) { create(:ci_pipeline, project: create(:project)) } + + before do + create_source_pipeline(upstream, pipeline) + end + + it 'returns only self' do + expect(self_and_ancestors).to contain_exactly(pipeline) end end end @@ -4468,15 +4518,18 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end context 'when the parent pipeline has a dependent upstream pipeline' do - let!(:upstream_bridge) do - create_bridge(create(:ci_pipeline, project: create(:project)), parent_pipeline, true) - end + let(:upstream_pipeline) { create(:ci_pipeline, project: create(:project)) } + let!(:upstream_bridge) { create_bridge(upstream_pipeline, parent_pipeline, true) } + + let(:upstream_upstream_pipeline) { create(:ci_pipeline, project: create(:project)) } + let!(:upstream_upstream_bridge) { create_bridge(upstream_upstream_pipeline, upstream_pipeline, true) } it 'marks all source bridges as pending' do reset_bridge expect(bridge.reload).to be_pending expect(upstream_bridge.reload).to be_pending + expect(upstream_upstream_bridge.reload).to be_pending end end end diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index 61f80bd43b1..ffc8ab4cf8b 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -1086,6 +1086,18 @@ RSpec.describe Ci::Runner do expect(matchers.map(&:tag_list)).to match_array([%w[tag1 tag2], %w[tag3 tag4]]) end end + + context 'with runner_ids' do + before do + create_list(:ci_runner, 2) + end + + it 'includes runner_ids' do + expect(matchers.size).to eq(1) + + expect(matchers.first.runner_ids).to match_array(described_class.all.pluck(:id)) + end + end end describe '#runner_matcher' do @@ -1095,6 +1107,8 @@ RSpec.describe Ci::Runner do subject(:matcher) { runner.runner_matcher } + it { expect(matcher.runner_ids).to eq([runner.id]) } + it { expect(matcher.runner_type).to eq(runner.runner_type) } it { expect(matcher.public_projects_minutes_cost_factor).to eq(runner.public_projects_minutes_cost_factor) } diff --git a/spec/models/ci/running_build_spec.rb b/spec/models/ci/running_build_spec.rb index 589e5a86f4d..629861e35b8 100644 --- a/spec/models/ci/running_build_spec.rb +++ b/spec/models/ci/running_build_spec.rb @@ -21,10 +21,7 @@ RSpec.describe Ci::RunningBuild do context 'when another queuing entry exists for given build' do before do - described_class.create!(build: build, - project: project, - runner: runner, - runner_type: runner.runner_type) + create(:ci_running_build, build: build, project: project, runner: runner) end it 'returns a build id as a result' do diff --git a/spec/models/clusters/integrations/prometheus_spec.rb b/spec/models/clusters/integrations/prometheus_spec.rb index 680786189ad..e529c751889 100644 --- a/spec/models/clusters/integrations/prometheus_spec.rb +++ b/spec/models/clusters/integrations/prometheus_spec.rb @@ -20,7 +20,7 @@ RSpec.describe Clusters::Integrations::Prometheus do let(:cluster) { create(:cluster, :with_installed_helm) } - it 'deactivates prometheus_service' do + it 'deactivates prometheus_integration' do expect(Clusters::Applications::DeactivateServiceWorker) .to receive(:perform_async).with(cluster.id, 'prometheus') @@ -35,7 +35,7 @@ RSpec.describe Clusters::Integrations::Prometheus do let(:enabled) { true } context 'when no change to enabled status' do - it 'does not touch project services' do + it 'does not touch project integrations' do integration # ensure integration exists before we set the expectations expect(Clusters::Applications::DeactivateServiceWorker) @@ -51,7 +51,7 @@ RSpec.describe Clusters::Integrations::Prometheus do context 'when enabling' do let(:enabled) { false } - it 'deactivates prometheus_service' do + it 'deactivates prometheus_integration' do expect(Clusters::Applications::ActivateServiceWorker) .to receive(:perform_async).with(cluster.id, 'prometheus') @@ -62,7 +62,7 @@ RSpec.describe Clusters::Integrations::Prometheus do context 'when disabling' do let(:enabled) { true } - it 'activates prometheus_service' do + it 'activates prometheus_integration' do expect(Clusters::Applications::DeactivateServiceWorker) .to receive(:perform_async).with(cluster.id, 'prometheus') diff --git a/spec/models/clusters/kubernetes_namespace_spec.rb b/spec/models/clusters/kubernetes_namespace_spec.rb index 3b903fe34f9..e70cd15baca 100644 --- a/spec/models/clusters/kubernetes_namespace_spec.rb +++ b/spec/models/clusters/kubernetes_namespace_spec.rb @@ -62,6 +62,7 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do describe 'namespace uniqueness validation' do let_it_be(:cluster) { create(:cluster, :project, :provided_by_gcp) } + let(:kubernetes_namespace) { build(:cluster_kubernetes_namespace, cluster: cluster, namespace: 'my-namespace') } subject { kubernetes_namespace } diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 8ffc198fc4d..63fe6923630 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -6,6 +6,7 @@ RSpec.describe Commit do let_it_be(:project) { create(:project, :public, :repository) } let_it_be(:personal_snippet) { create(:personal_snippet, :repository) } let_it_be(:project_snippet) { create(:project_snippet, :repository) } + let(:commit) { project.commit } describe 'modules' do diff --git a/spec/models/compare_spec.rb b/spec/models/compare_spec.rb index d395aa359e5..86bab569ab0 100644 --- a/spec/models/compare_spec.rb +++ b/spec/models/compare_spec.rb @@ -13,7 +13,15 @@ RSpec.describe Compare do let(:raw_compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, start_commit.id, head_commit.id) } - subject { described_class.new(raw_compare, project) } + subject(:compare) { described_class.new(raw_compare, project) } + + describe '#cache_key' do + subject { compare.cache_key } + + it { is_expected.to include(project) } + it { is_expected.to include(:compare) } + it { is_expected.to include(compare.diff_refs.hash) } + end describe '#start_commit' do it 'returns raw compare base commit' do diff --git a/spec/models/concerns/approvable_base_spec.rb b/spec/models/concerns/approvable_base_spec.rb index a9e944cf220..c7ea2631a24 100644 --- a/spec/models/concerns/approvable_base_spec.rb +++ b/spec/models/concerns/approvable_base_spec.rb @@ -59,4 +59,25 @@ RSpec.describe ApprovableBase do end end end + + describe '.not_approved_by_users_with_usernames' do + subject { MergeRequest.not_approved_by_users_with_usernames([user.username, user2.username]) } + + let!(:merge_request2) { create(:merge_request) } + let!(:merge_request3) { create(:merge_request) } + let!(:merge_request4) { create(:merge_request) } + let(:user2) { create(:user) } + let(:user3) { create(:user) } + + before do + create(:approval, merge_request: merge_request, user: user) + create(:approval, merge_request: merge_request2, user: user2) + create(:approval, merge_request: merge_request2, user: user3) + create(:approval, merge_request: merge_request4, user: user3) + end + + it 'has the merge request that is not approved at all and not approved by either user' do + expect(subject).to contain_exactly(merge_request3, merge_request4) + end + end end diff --git a/spec/models/concerns/atomic_internal_id_spec.rb b/spec/models/concerns/atomic_internal_id_spec.rb index 35b0f107676..b803e699b25 100644 --- a/spec/models/concerns/atomic_internal_id_spec.rb +++ b/spec/models/concerns/atomic_internal_id_spec.rb @@ -240,18 +240,12 @@ RSpec.describe AtomicInternalId do end describe '.with_project_iid_supply' do - let(:iid) { 100 } - - it 'wraps generate and track_greatest in a concurrency-safe lock' do - expect_next_instance_of(InternalId::InternalIdGenerator) do |g| - expect(g).to receive(:with_lock).and_call_original - expect(g.record).to receive(:last_value).and_return(iid) - expect(g).to receive(:track_greatest).with(iid + 4) - end - - ::Milestone.with_project_iid_supply(milestone.project) do |supply| - 4.times { supply.next_value } - end + it 'supplies a stream of iid values' do + expect do + ::Milestone.with_project_iid_supply(milestone.project) do |supply| + 4.times { supply.next_value } + end + end.to change { InternalId.find_by(project: milestone.project, usage: :milestones)&.last_value.to_i }.by(4) end end end diff --git a/spec/models/concerns/awardable_spec.rb b/spec/models/concerns/awardable_spec.rb index b80b6ec95e2..fcd0d0c05f4 100644 --- a/spec/models/concerns/awardable_spec.rb +++ b/spec/models/concerns/awardable_spec.rb @@ -3,64 +3,64 @@ require 'spec_helper' RSpec.describe Awardable do - let!(:issue) { create(:issue) } - let!(:award_emoji) { create(:award_emoji, :downvote, awardable: issue) } + let!(:note) { create(:note) } + let!(:award_emoji) { create(:award_emoji, :downvote, awardable: note) } describe "Associations" do - subject { build(:issue) } + subject { build(:note) } it { is_expected.to have_many(:award_emoji).dependent(:destroy) } end describe "ClassMethods" do - let!(:issue2) { create(:issue) } - let!(:award_emoji2) { create(:award_emoji, awardable: issue2) } + let!(:note2) { create(:note) } + let!(:award_emoji2) { create(:award_emoji, awardable: note2) } describe "orders" do it "orders on upvotes" do - expect(Issue.order_upvotes_desc.to_a).to eq [issue2, issue] + expect(Note.order_upvotes_desc.to_a).to eq [note2, note] end it "orders on downvotes" do - expect(Issue.order_downvotes_desc.to_a).to eq [issue, issue2] + expect(Note.order_downvotes_desc.to_a).to eq [note, note2] end end describe "#awarded" do it "filters by user and emoji name" do - expect(Issue.awarded(award_emoji.user, "thumbsup")).to be_empty - expect(Issue.awarded(award_emoji.user, "thumbsdown")).to eq [issue] - expect(Issue.awarded(award_emoji2.user, "thumbsup")).to eq [issue2] - expect(Issue.awarded(award_emoji2.user, "thumbsdown")).to be_empty + expect(Note.awarded(award_emoji.user, "thumbsup")).to be_empty + expect(Note.awarded(award_emoji.user, "thumbsdown")).to eq [note] + expect(Note.awarded(award_emoji2.user, "thumbsup")).to eq [note2] + expect(Note.awarded(award_emoji2.user, "thumbsdown")).to be_empty end it "filters by user and any emoji" do - issue3 = create(:issue) - create(:award_emoji, awardable: issue3, name: "star", user: award_emoji.user) - create(:award_emoji, awardable: issue3, name: "star", user: award_emoji2.user) + note3 = create(:note) + create(:award_emoji, awardable: note3, name: "star", user: award_emoji.user) + create(:award_emoji, awardable: note3, name: "star", user: award_emoji2.user) - expect(Issue.awarded(award_emoji.user)).to contain_exactly(issue, issue3) - expect(Issue.awarded(award_emoji2.user)).to contain_exactly(issue2, issue3) + expect(Note.awarded(award_emoji.user)).to contain_exactly(note, note3) + expect(Note.awarded(award_emoji2.user)).to contain_exactly(note2, note3) end end describe "#not_awarded" do - it "returns issues not awarded by user" do - expect(Issue.not_awarded(award_emoji.user)).to eq [issue2] - expect(Issue.not_awarded(award_emoji2.user)).to eq [issue] + it "returns notes not awarded by user" do + expect(Note.not_awarded(award_emoji.user)).to eq [note2] + expect(Note.not_awarded(award_emoji2.user)).to eq [note] end end end describe "#upvotes" do it "counts the number of upvotes" do - expect(issue.upvotes).to be 0 + expect(note.upvotes).to be 0 end end describe "#downvotes" do it "counts the number of downvotes" do - expect(issue.downvotes).to be 1 + expect(note.downvotes).to be 1 end end @@ -68,67 +68,67 @@ RSpec.describe Awardable do let(:user) { create(:user) } before do - issue.project.add_guest(user) + note.project.add_guest(user) end it 'is truthy when the user is allowed to award emoji' do - expect(issue.user_can_award?(user)).to be_truthy + expect(note.user_can_award?(user)).to be_truthy end it 'is falsy when the project is archived' do - issue.project.update!(archived: true) + note.project.update!(archived: true) - expect(issue.user_can_award?(user)).to be_falsy + expect(note.user_can_award?(user)).to be_falsy end end describe 'querying award_emoji on an Awardable' do - let(:issue) { create(:issue) } + let(:note) { create(:note) } it 'sorts in ascending fashion' do - create_list(:award_emoji, 3, awardable: issue) + create_list(:award_emoji, 3, awardable: note) - expect(issue.award_emoji).to eq issue.award_emoji.sort_by(&:id) + expect(note.award_emoji).to eq note.award_emoji.sort_by(&:id) end end describe "#grouped_awards" do context 'default award emojis' do - let(:issue_without_downvote) { create(:issue) } - let(:issue_with_downvote) do - issue_with_downvote = create(:issue) - create(:award_emoji, :downvote, awardable: issue_with_downvote) - issue_with_downvote + let(:note_without_downvote) { create(:note) } + let(:note_with_downvote) do + note_with_downvote = create(:note) + create(:award_emoji, :downvote, awardable: note_with_downvote) + note_with_downvote end it "includes unused thumbs buttons by default" do - expect(issue_without_downvote.grouped_awards.keys.sort).to eq %w(thumbsdown thumbsup) + expect(note_without_downvote.grouped_awards.keys.sort).to eq %w(thumbsdown thumbsup) end it "doesn't include unused thumbs buttons when disabled in project" do - issue_without_downvote.project.show_default_award_emojis = false + note_without_downvote.project.show_default_award_emojis = false - expect(issue_without_downvote.grouped_awards.keys.sort).to be_empty + expect(note_without_downvote.grouped_awards.keys.sort).to be_empty end it "includes unused thumbs buttons when enabled in project" do - issue_without_downvote.project.show_default_award_emojis = true + note_without_downvote.project.show_default_award_emojis = true - expect(issue_without_downvote.grouped_awards.keys.sort).to eq %w(thumbsdown thumbsup) + expect(note_without_downvote.grouped_awards.keys.sort).to eq %w(thumbsdown thumbsup) end it "doesn't include unused thumbs buttons in summary" do - expect(issue_without_downvote.grouped_awards(with_thumbs: false).keys).to be_empty + expect(note_without_downvote.grouped_awards(with_thumbs: false).keys).to be_empty end it "includes used thumbs buttons when disabled in project" do - issue_with_downvote.project.show_default_award_emojis = false + note_with_downvote.project.show_default_award_emojis = false - expect(issue_with_downvote.grouped_awards.keys).to eq %w(thumbsdown) + expect(note_with_downvote.grouped_awards.keys).to eq %w(thumbsdown) end it "includes used thumbs buttons in summary" do - expect(issue_with_downvote.grouped_awards(with_thumbs: false).keys).to eq %w(thumbsdown) + expect(note_with_downvote.grouped_awards(with_thumbs: false).keys).to eq %w(thumbsdown) end end end diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb index 33a4c8eac41..1c1efab2889 100644 --- a/spec/models/concerns/cache_markdown_field_spec.rb +++ b/spec/models/concerns/cache_markdown_field_spec.rb @@ -75,7 +75,7 @@ RSpec.describe CacheMarkdownField, :clean_gitlab_redis_cache do end it 'returns false when the local version was bumped' do - allow(Gitlab::CurrentSettings.current_application_settings).to receive(:local_markdown_version).and_return(2) + stub_application_setting(local_markdown_version: 2) thing.cached_markdown_version = cache_version is_expected.to be_falsy @@ -88,7 +88,7 @@ RSpec.describe CacheMarkdownField, :clean_gitlab_redis_cache do end 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) + stub_application_setting(local_markdown_version: 2) thing.cached_markdown_version = cache_version + 2 is_expected.to be_truthy diff --git a/spec/models/concerns/cascading_namespace_setting_attribute_spec.rb b/spec/models/concerns/cascading_namespace_setting_attribute_spec.rb index 02cd8557231..e8f2b18e662 100644 --- a/spec/models/concerns/cascading_namespace_setting_attribute_spec.rb +++ b/spec/models/concerns/cascading_namespace_setting_attribute_spec.rb @@ -17,18 +17,6 @@ RSpec.describe NamespaceSetting, 'CascadingNamespaceSettingAttribute' do describe '#delayed_project_removal' do subject(:delayed_project_removal) { subgroup_settings.delayed_project_removal } - context 'when the feature is disabled' do - before do - stub_feature_flags(cascading_namespace_settings: false) - - group_settings.update!(delayed_project_removal: true) - end - - it 'does not cascade' do - expect(delayed_project_removal).to eq(nil) - end - end - context 'when there is no parent' do context 'and the value is not nil' do before do @@ -192,16 +180,6 @@ RSpec.describe NamespaceSetting, 'CascadingNamespaceSettingAttribute' do end end - context 'when the feature is disabled' do - before do - stub_feature_flags(cascading_namespace_settings: false) - - group_settings.update!(delayed_project_removal: true) - end - - it_behaves_like 'not locked' - end - context 'when attribute is locked by self' do before do subgroup_settings.update!(lock_delayed_project_removal: true) diff --git a/spec/models/concerns/has_integrations_spec.rb b/spec/models/concerns/has_integrations_spec.rb index 6e55a1c8b01..6b3f75bfcfd 100644 --- a/spec/models/concerns/has_integrations_spec.rb +++ b/spec/models/concerns/has_integrations_spec.rb @@ -7,14 +7,14 @@ RSpec.describe HasIntegrations do let_it_be(:project_2) { create(:project) } let_it_be(:project_3) { create(:project) } let_it_be(:project_4) { create(:project) } - let_it_be(:instance_integration) { create(:jira_service, :instance) } + let_it_be(:instance_integration) { create(:jira_integration, :instance) } before do - create(:jira_service, project: project_1, inherit_from_id: instance_integration.id) - create(:jira_service, project: project_2, inherit_from_id: nil) - create(:jira_service, group: create(:group), project: nil, inherit_from_id: nil) - create(:jira_service, project: project_3, inherit_from_id: nil) - create(:slack_service, project: project_4, inherit_from_id: nil) + create(:jira_integration, project: project_1, inherit_from_id: instance_integration.id) + create(:jira_integration, project: project_2, inherit_from_id: nil) + create(:jira_integration, group: create(:group), project: nil, inherit_from_id: nil) + create(:jira_integration, project: project_3, inherit_from_id: nil) + create(:integrations_slack, project: project_4, inherit_from_id: nil) end describe '.with_custom_integration_for' do diff --git a/spec/models/concerns/integrations/has_data_fields_spec.rb b/spec/models/concerns/integrations/has_data_fields_spec.rb index 54e0ac9c5a5..b28fef571c6 100644 --- a/spec/models/concerns/integrations/has_data_fields_spec.rb +++ b/spec/models/concerns/integrations/has_data_fields_spec.rb @@ -84,7 +84,7 @@ RSpec.describe Integrations::HasDataFields do context 'when data are stored in data_fields' do let(:service) do - create(:jira_service, url: url, username: username) + create(:jira_integration, url: url, username: username) end it_behaves_like 'data fields' @@ -111,45 +111,52 @@ RSpec.describe Integrations::HasDataFields do end context 'when data are stored in properties' do - let(:service) { create(:jira_service, :without_properties_callback, properties: properties) } + let(:integration) { create(:jira_integration, :without_properties_callback, properties: properties) } - it_behaves_like 'data fields' + it_behaves_like 'data fields' do + let(:service) { integration } + end describe '{arg}_was?' do it 'returns nil when the property has not been assigned a new value' do - service.username = 'new_username' - service.validate - expect(service.url_was).to be_nil + integration.username = 'new_username' + integration.validate + + expect(integration.url_was).to be_nil end it 'returns initial value when the property has been assigned a different value' do - service.url = 'http://example.com' - service.validate - expect(service.url_was).to eq('http://url.com') + integration.url = 'http://example.com' + integration.validate + + expect(integration.url_was).to eq('http://url.com') end it 'returns initial value when the property has been re-assigned the same value' do - service.url = 'http://url.com' - service.validate - expect(service.url_was).to eq('http://url.com') + integration.url = 'http://url.com' + integration.validate + + expect(integration.url_was).to eq('http://url.com') end end end context 'when data are stored in both properties and data_fields' do - let(:service) do - create(:jira_service, :without_properties_callback, active: false, properties: properties).tap do |integration| + let(:integration) do + create(:jira_integration, :without_properties_callback, active: false, properties: properties).tap do |integration| create(:jira_tracker_data, properties.merge(integration: integration)) end end - it_behaves_like 'data fields' + it_behaves_like 'data fields' do + let(:service) { integration } + end describe '{arg}_was?' do it 'returns nil' do - service.url = 'http://example.com' - service.validate - expect(service.url_was).to be_nil + integration.url = 'http://example.com' + integration.validate + expect(integration.url_was).to be_nil end end end diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 7b100b7a6f3..071e0dcba44 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -535,6 +535,26 @@ RSpec.describe Issuable do merge_request.to_hook_data(user, old_associations: { assignees: [user] }) end end + + context 'incident severity is updated' do + let(:issue) { create(:incident) } + + before do + issue.update!(issuable_severity_attributes: { severity: 'low' }) + expect(Gitlab::HookData::IssuableBuilder) + .to receive(:new).with(issue).and_return(builder) + end + + it 'delegates to Gitlab::HookData::IssuableBuilder#build' do + expect(builder).to receive(:build).with( + user: user, + changes: hash_including( + 'severity' => %w(unknown low) + )) + + issue.to_hook_data(user, old_associations: { severity: 'unknown' }) + end + end end describe '#labels_array' do diff --git a/spec/models/concerns/partitioned_table_spec.rb b/spec/models/concerns/partitioned_table_spec.rb index 3343b273ba2..c37fb81a1cf 100644 --- a/spec/models/concerns/partitioned_table_spec.rb +++ b/spec/models/concerns/partitioned_table_spec.rb @@ -14,6 +14,16 @@ RSpec.describe PartitionedTable do end end + context 'with keyword arguments passed to the strategy' do + subject { my_class.partitioned_by(key, strategy: :monthly, retain_for: 3.months) } + + it 'passes the keyword arguments to the strategy' do + expect(Gitlab::Database::Partitioning::MonthlyStrategy).to receive(:new).with(my_class, key, retain_for: 3.months).and_call_original + + subject + end + end + it 'assigns the MonthlyStrategy as the partitioning strategy' do subject @@ -27,7 +37,7 @@ RSpec.describe PartitionedTable do end it 'registers itself with the PartitionCreator' do - expect(Gitlab::Database::Partitioning::PartitionCreator).to receive(:register).with(my_class) + expect(Gitlab::Database::Partitioning::PartitionManager).to receive(:register).with(my_class) subject end diff --git a/spec/models/concerns/prometheus_adapter_spec.rb b/spec/models/concerns/prometheus_adapter_spec.rb index 235e505c6e9..01c987a1d92 100644 --- a/spec/models/concerns/prometheus_adapter_spec.rb +++ b/spec/models/concerns/prometheus_adapter_spec.rb @@ -7,7 +7,7 @@ RSpec.describe PrometheusAdapter, :use_clean_rails_memory_store_caching do include ReactiveCachingHelpers let(:project) { create(:prometheus_project) } - let(:service) { project.prometheus_service } + let(:integration) { project.prometheus_integration } let(:described_class) do Class.new do @@ -29,10 +29,10 @@ RSpec.describe PrometheusAdapter, :use_clean_rails_memory_store_caching do end context 'with valid data' do - subject { service.query(:validate, query) } + subject { integration.query(:validate, query) } before do - stub_reactive_cache(service, validation_respone, validation_query, query) + stub_reactive_cache(integration, validation_respone, validation_query, query) end it 'returns query data' do @@ -49,10 +49,10 @@ RSpec.describe PrometheusAdapter, :use_clean_rails_memory_store_caching do end context 'with valid data' do - subject { service.query(:environment, environment) } + subject { integration.query(:environment, environment) } before do - stub_reactive_cache(service, prometheus_data, environment_query, environment.id) + stub_reactive_cache(integration, prometheus_data, environment_query, environment.id) end it 'returns reactive data' do @@ -66,11 +66,11 @@ RSpec.describe PrometheusAdapter, :use_clean_rails_memory_store_caching do let(:prometheus_client) { double(:prometheus_client, label_values: nil) } context 'with valid data' do - subject { service.query(:matched_metrics) } + subject { integration.query(:matched_metrics) } before do - allow(service).to receive(:prometheus_client).and_return(prometheus_client) - synchronous_reactive_cache(service) + allow(integration).to receive(:prometheus_client).and_return(prometheus_client) + synchronous_reactive_cache(integration) end it 'returns reactive data' do @@ -89,10 +89,10 @@ RSpec.describe PrometheusAdapter, :use_clean_rails_memory_store_caching do end context 'with valid data' do - subject { service.query(:deployment, deployment) } + subject { integration.query(:deployment, deployment) } before do - stub_reactive_cache(service, prometheus_data, deployment_query, deployment.id) + stub_reactive_cache(integration, prometheus_data, deployment_query, deployment.id) end it 'returns reactive data' do @@ -111,10 +111,10 @@ RSpec.describe PrometheusAdapter, :use_clean_rails_memory_store_caching do end context 'with valid data' do - subject { service.query(:additional_metrics_environment, environment, *time_window) } + subject { integration.query(:additional_metrics_environment, environment, *time_window) } before do - stub_reactive_cache(service, prometheus_data, additional_metrics_environment_query, environment.id, *time_window) + stub_reactive_cache(integration, prometheus_data, additional_metrics_environment_query, environment.id, *time_window) end it 'returns reactive data' do @@ -128,21 +128,21 @@ RSpec.describe PrometheusAdapter, :use_clean_rails_memory_store_caching do let(:environment) { create(:environment, slug: 'env-slug') } before do - service.manual_configuration = true - service.active = true + integration.manual_configuration = true + integration.active = true end subject do - service.calculate_reactive_cache(environment_query.name, environment.id) + integration.calculate_reactive_cache(environment_query.name, environment.id) end around do |example| freeze_time { example.run } end - context 'when service is inactive' do + context 'when integration is inactive' do before do - service.active = false + integration.active = false end it { is_expected.to be_nil } @@ -168,7 +168,7 @@ RSpec.describe PrometheusAdapter, :use_clean_rails_memory_store_caching do end describe '#build_query_args' do - subject { service.build_query_args(*args) } + subject { integration.build_query_args(*args) } context 'when active record models are included' do let(:args) { [double(:environment, id: 12)] } diff --git a/spec/models/container_repository_spec.rb b/spec/models/container_repository_spec.rb index 3232a559d0b..a53db07cc59 100644 --- a/spec/models/container_repository_spec.rb +++ b/spec/models/container_repository_spec.rb @@ -281,6 +281,16 @@ RSpec.describe ContainerRepository do expect(repository.name).to be_empty end end + + context 'when repository already exists' do + let(:path) { project.full_path + '/some/image' } + + it 'returns the existing repository' do + container_repository = create(:container_repository, project: project, name: 'some/image') + + expect(repository.id).to eq(container_repository.id) + end + end end describe '.build_root_repository' do @@ -311,13 +321,18 @@ RSpec.describe ContainerRepository do end context 'with a subgroup' do - let(:test_group) { create(:group) } - let(:another_project) { create(:project, path: 'test', group: test_group) } + let_it_be(:test_group) { create(:group) } + let_it_be(:another_project) { create(:project, path: 'test', group: test_group) } + let_it_be(:project3) { create(:project, path: 'test3', group: test_group, container_registry_enabled: false) } - let(:another_repository) do + let_it_be(:another_repository) do create(:container_repository, name: 'my_image', project: another_project) end + let_it_be(:repository3) do + create(:container_repository, name: 'my_image3', project: project3) + end + before do group.parent = test_group group.save! @@ -331,40 +346,6 @@ RSpec.describe ContainerRepository do it { is_expected.to eq([]) } end - - context 'with read_container_registry_access_level disabled' do - before do - stub_feature_flags(read_container_registry_access_level: false) - end - - context 'in a group' do - let(:test_group) { group } - - it { is_expected.to contain_exactly(repository) } - end - - context 'with a subgroup' do - let(:test_group) { create(:group) } - let(:another_project) { create(:project, path: 'test', group: test_group) } - - let(:another_repository) do - create(:container_repository, name: 'my_image', project: another_project) - end - - before do - group.parent = test_group - group.save! - end - - it { is_expected.to contain_exactly(repository, another_repository) } - end - - context 'group without container_repositories' do - let(:test_group) { create(:group) } - - it { is_expected.to eq([]) } - end - end end describe '.search_by_name' do diff --git a/spec/models/deploy_token_spec.rb b/spec/models/deploy_token_spec.rb index dfc37f9e661..c9f7895a616 100644 --- a/spec/models/deploy_token_spec.rb +++ b/spec/models/deploy_token_spec.rb @@ -242,6 +242,7 @@ RSpec.describe DeployToken do context 'and when the token is of group type' do let_it_be(:group) { create(:group) } + let(:deploy_token) { create(:deploy_token, :group) } before do diff --git a/spec/models/deployment_metrics_spec.rb b/spec/models/deployment_metrics_spec.rb index fadfc1b63ac..c804e20d66d 100644 --- a/spec/models/deployment_metrics_spec.rb +++ b/spec/models/deployment_metrics_spec.rb @@ -15,35 +15,35 @@ RSpec.describe DeploymentMetrics do context 'when deployment is success' do let(:deployment) { create(:deployment, :success) } - context 'without a monitoring service' do + context 'without a monitoring integration' do it { is_expected.to be_falsy } end - context 'with a Prometheus Service' do - let(:prometheus_service) { instance_double(PrometheusService, can_query?: true, configured?: true) } + context 'with a Prometheus integration' do + let(:prometheus_integration) { instance_double(::Integrations::Prometheus, can_query?: true, configured?: true) } before do - allow(deployment.project).to receive(:find_or_initialize_service).with('prometheus').and_return prometheus_service + allow(deployment.project).to receive(:find_or_initialize_integration).with('prometheus').and_return prometheus_integration end it { is_expected.to be_truthy } end - context 'with a Prometheus Service that cannot query' do - let(:prometheus_service) { instance_double(PrometheusService, configured?: true, can_query?: false) } + context 'with a Prometheus integration that cannot query' do + let(:prometheus_integration) { instance_double(::Integrations::Prometheus, configured?: true, can_query?: false) } before do - allow(deployment.project).to receive(:find_or_initialize_service).with('prometheus').and_return prometheus_service + allow(deployment.project).to receive(:find_or_initialize_integration).with('prometheus').and_return prometheus_integration end it { is_expected.to be_falsy } end - context 'with a Prometheus Service that is not configured' do - let(:prometheus_service) { instance_double(PrometheusService, configured?: false, can_query?: false) } + context 'with a Prometheus integration that is not configured' do + let(:prometheus_integration) { instance_double(::Integrations::Prometheus, configured?: false, can_query?: false) } before do - allow(deployment.project).to receive(:find_or_initialize_service).with('prometheus').and_return prometheus_service + allow(deployment.project).to receive(:find_or_initialize_integration).with('prometheus').and_return prometheus_integration end it { is_expected.to be_falsy } @@ -64,7 +64,7 @@ RSpec.describe DeploymentMetrics do describe '#metrics' do let(:deployment) { create(:deployment, :success) } - let(:prometheus_adapter) { instance_double(PrometheusService, can_query?: true, configured?: true) } + let(:prometheus_adapter) { instance_double(::Integrations::Prometheus, can_query?: true, configured?: true) } let(:deployment_metrics) { described_class.new(deployment.project, deployment) } subject { deployment_metrics.metrics } diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index bcd237cbd38..a0e5e9cbfe4 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -80,6 +80,7 @@ RSpec.describe Deployment do let_it_be(:staging) { create(:environment, :staging, project: project) } let_it_be(:other_project) { create(:project, :repository) } let_it_be(:other_production) { create(:environment, :production, project: other_project) } + let(:environment_name) { production.name } context 'when deployment belongs to the environment' do @@ -488,6 +489,7 @@ RSpec.describe Deployment do let_it_be(:project) { create(:project, :repository) } let_it_be(:commits) { project.repository.commits('master', limit: 2) } let_it_be(:deployments) { commits.reverse.map { |commit| create(:deployment, project: project, sha: commit.id) } } + let(:sha) { commits.map(&:id) } it 'finds the latest deployment with sha' do @@ -823,6 +825,7 @@ RSpec.describe Deployment do describe '#update_merge_request_metrics!' do let_it_be(:project) { create(:project, :repository) } + let(:environment) { build(:environment, environment_tier, project: project) } let!(:deployment) { create(:deployment, :success, project: project, environment: environment) } let!(:merge_request) { create(:merge_request, :simple, :merged_last_month, project: project) } diff --git a/spec/models/diff_discussion_spec.rb b/spec/models/diff_discussion_spec.rb index 2a2663149d0..998204626d3 100644 --- a/spec/models/diff_discussion_spec.rb +++ b/spec/models/diff_discussion_spec.rb @@ -126,4 +126,13 @@ RSpec.describe DiffDiscussion do end end end + + describe '#cache_key' do + it 'returns the cache key with the position sha' do + notes_sha = Digest::SHA1.hexdigest("#{diff_note.id}") + position_sha = Digest::SHA1.hexdigest(diff_note.position.to_json) + + expect(subject.cache_key).to eq("#{described_class::CACHE_VERSION}:#{diff_note.latest_cached_markdown_version}:#{subject.id}:#{notes_sha}:#{diff_note.updated_at}::#{position_sha}") + end + end end diff --git a/spec/models/diff_viewer/server_side_spec.rb b/spec/models/diff_viewer/server_side_spec.rb index 686dd1249be..28660b0d4b9 100644 --- a/spec/models/diff_viewer/server_side_spec.rb +++ b/spec/models/diff_viewer/server_side_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe DiffViewer::ServerSide do let_it_be(:project) { create(:project, :repository) } + let(:commit) { project.commit_by(oid: '570e7b2abdd848b95f2f578043fc23bd6f6fd24d') } let!(:diff_file) { commit.diffs.diff_file_with_new_path('files/ruby/popen.rb') } diff --git a/spec/models/discussion_spec.rb b/spec/models/discussion_spec.rb index 021940be0c2..2b33de96e04 100644 --- a/spec/models/discussion_spec.rb +++ b/spec/models/discussion_spec.rb @@ -51,4 +51,22 @@ RSpec.describe Discussion do expect(policy).to be_a(NotePolicy) end end + + describe '#cache_key' do + let(:notes_sha) { Digest::SHA1.hexdigest("#{first_note.id}:#{second_note.id}:#{third_note.id}") } + + it 'returns the cache key with ID and latest updated note updated at' do + expect(subject.cache_key).to eq("#{described_class::CACHE_VERSION}:#{third_note.latest_cached_markdown_version}:#{subject.id}:#{notes_sha}:#{third_note.updated_at}:") + end + + context 'when discussion is resolved' do + before do + subject.resolve!(first_note.author) + end + + it 'returns the cache key with resolved at' do + expect(subject.cache_key).to eq("#{described_class::CACHE_VERSION}:#{third_note.latest_cached_markdown_version}:#{subject.id}:#{notes_sha}:#{third_note.updated_at}:#{subject.resolved_at}") + end + end + end end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index ff4c8ae950d..18a172b72d7 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -219,6 +219,7 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { create(:user) } + let(:environments) { Environment.all } before_all do @@ -760,6 +761,7 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do describe '#last_visible_pipeline' do let(:user) { create(:user) } let_it_be(:project) { create(:project, :repository) } + let(:environment) { create(:environment, project: project) } let(:commit) { project.commit } @@ -1462,6 +1464,7 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do let_it_be(:project) { create(:project, :repository) } let_it_be(:environment, reload: true) { create(:environment, project: project) } + let!(:deployment) { create(:deployment, project: project, environment: environment, deployable: build) } let!(:build) { create(:ci_build, :running, project: project, environment: environment) } diff --git a/spec/models/error_tracking/error_event_spec.rb b/spec/models/error_tracking/error_event_spec.rb new file mode 100644 index 00000000000..331661f88cc --- /dev/null +++ b/spec/models/error_tracking/error_event_spec.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ErrorTracking::ErrorEvent, type: :model do + describe 'relationships' do + it { is_expected.to belong_to(:error) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:description) } + it { is_expected.to validate_presence_of(:occurred_at) } + end +end diff --git a/spec/models/error_tracking/error_spec.rb b/spec/models/error_tracking/error_spec.rb new file mode 100644 index 00000000000..8591802d15c --- /dev/null +++ b/spec/models/error_tracking/error_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ErrorTracking::Error, type: :model do + describe 'relationships' do + it { is_expected.to belong_to(:project) } + it { is_expected.to have_many(:events) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:name) } + it { is_expected.to validate_presence_of(:description) } + it { is_expected.to validate_presence_of(:actor) } + end +end diff --git a/spec/models/event_collection_spec.rb b/spec/models/event_collection_spec.rb index aca2a8c3a2f..107447c9630 100644 --- a/spec/models/event_collection_spec.rb +++ b/spec/models/event_collection_spec.rb @@ -28,6 +28,7 @@ RSpec.describe EventCollection do let_it_be(:closed_issue_event) { create(:closed_issue_event, project: project, author: user) } let_it_be(:wiki_page_event) { create(:wiki_page_event, project: project) } let_it_be(:design_event) { create(:design_event, project: project) } + let(:push_events) { push_event_payloads.map(&:event) } it 'returns an Array of events', :aggregate_failures do diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 949e8ec0a72..fc229dcaa22 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -268,6 +268,7 @@ RSpec.describe Event do let(:design) { create(:design, issue: issue, project: project) } let(:note_on_commit) { create(:note_on_commit, project: project) } let(:note_on_issue) { create(:note_on_issue, noteable: issue, project: project) } + let(:confidential_note) { create(:note, noteable: issue, project: project, confidential: true) } let(:note_on_confidential_issue) { create(:note_on_issue, noteable: confidential_issue, project: project) } let(:note_on_project_snippet) { create(:note_on_project_snippet, author: author, noteable: project_snippet, project: project) } let(:note_on_personal_snippet) { create(:note_on_personal_snippet, author: author, noteable: personal_snippet, project: nil) } @@ -399,6 +400,16 @@ RSpec.describe Event do include_examples 'visible to assignee and author', true end + context 'confidential note' do + let(:target) { confidential_note } + + include_examples 'visibility examples' do + let(:visibility) { visible_to_none_except(:member) } + end + + include_examples 'visible to author', true + end + context 'private project' do let(:project) { private_project } let(:target) { note_on_issue } @@ -967,14 +978,13 @@ RSpec.describe Event do describe '#action_name' do it 'handles all valid design events' do - created, updated, destroyed, archived = %i[created updated destroyed archived].map do |trait| + created, updated, destroyed = %i[created updated destroyed].map do |trait| build(:design_event, trait).action_name end expect(created).to eq('uploaded') expect(updated).to eq('revised') expect(destroyed).to eq('deleted') - expect(archived).to eq('archived') end it 'handles correct push_action' do diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 8f4bc43c38a..0a08b15a1eb 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -565,11 +565,11 @@ RSpec.describe Group do describe '.without_integration' do let(:another_group) { create(:group) } - let(:instance_integration) { build(:jira_service, :instance) } + let(:instance_integration) { build(:jira_integration, :instance) } before do - create(:jira_service, group: group, project: nil) - create(:slack_service, group: another_group, project: nil) + create(:jira_integration, group: group, project: nil) + create(:integrations_slack, group: another_group, project: nil) end it 'returns groups without integration' do diff --git a/spec/models/integration_spec.rb b/spec/models/integration_spec.rb index d4ea3e5d08a..ab4027170b2 100644 --- a/spec/models/integration_spec.rb +++ b/spec/models/integration_spec.rb @@ -9,11 +9,12 @@ RSpec.describe Integration do let_it_be(:project) { create(:project, group: group) } describe "Associations" do - it { is_expected.to belong_to :project } - it { is_expected.to belong_to :group } - it { is_expected.to have_one :service_hook } - it { is_expected.to have_one :jira_tracker_data } - it { is_expected.to have_one :issue_tracker_data } + it { is_expected.to belong_to(:project).inverse_of(:integrations) } + it { is_expected.to belong_to(:group).inverse_of(:integrations) } + it { is_expected.to have_one(:service_hook).inverse_of(:integration).with_foreign_key(:service_id) } + it { is_expected.to have_one(:issue_tracker_data).autosave(true).inverse_of(:integration).with_foreign_key(:service_id).class_name('Integrations::IssueTrackerData') } + it { is_expected.to have_one(:jira_tracker_data).autosave(true).inverse_of(:integration).with_foreign_key(:service_id).class_name('Integrations::JiraTrackerData') } + it { is_expected.to have_one(:open_project_tracker_data).autosave(true).inverse_of(:integration).with_foreign_key(:service_id).class_name('Integrations::OpenProjectTrackerData') } end describe 'validations' do @@ -68,9 +69,9 @@ RSpec.describe Integration do describe 'Scopes' do describe '.by_type' do - let!(:service1) { create(:jira_service) } - let!(:service2) { create(:jira_service) } - let!(:service3) { create(:redmine_service) } + let!(:service1) { create(:jira_integration) } + let!(:service2) { create(:jira_integration) } + let!(:service3) { create(:redmine_integration) } subject { described_class.by_type(type) } @@ -88,8 +89,8 @@ RSpec.describe Integration do end describe '.for_group' do - let!(:service1) { create(:jira_service, project_id: nil, group_id: group.id) } - let!(:service2) { create(:jira_service) } + let!(:service1) { create(:jira_integration, project_id: nil, group_id: group.id) } + let!(:service2) { create(:jira_integration) } it 'returns the right group service' do expect(described_class.for_group(group)).to match_array([service1]) @@ -139,67 +140,38 @@ RSpec.describe Integration do end end - describe "Test Button" do - let(:service) { build(:service, project: project) } - - describe '#can_test?' do - subject { service.can_test? } - - context 'when repository is not empty' do - let(:project) { build(:project, :repository) } - - it { is_expected.to be true } - end - - context 'when repository is empty' do - let(:project) { build(:project) } - - it { is_expected.to be true } - end - - context 'when instance-level service' do - Integration.available_services_types.each do |service_type| - let(:service) do - described_class.send(:integration_type_to_model, service_type).new(instance: true) - end - - it { is_expected.to be_falsey } - end - end - - context 'when group-level service' do - Integration.available_services_types.each do |service_type| - let(:service) do - described_class.send(:integration_type_to_model, service_type).new(group_id: group.id) - end + describe '#testable?' do + context 'when integration is project-level' do + subject { build(:service, project: project) } - it { is_expected.to be_falsey } - end - end + it { is_expected.to be_testable } end - describe '#test' do - let(:data) { 'test' } + context 'when integration is not project-level' do + subject { build(:service, project: nil) } - context 'when repository is not empty' do - let(:project) { build(:project, :repository) } + it { is_expected.not_to be_testable } + end + end - it 'test runs execute' do - expect(service).to receive(:execute).with(data) + describe '#test' do + let(:integration) { build(:service, project: project) } + let(:data) { 'test' } - service.test(data) - end - end + it 'calls #execute' do + expect(integration).to receive(:execute).with(data) - context 'when repository is empty' do - let(:project) { build(:project) } + integration.test(data) + end - it 'test runs execute' do - expect(service).to receive(:execute).with(data) + it 'returns a result' do + result = 'foo' + allow(integration).to receive(:execute).with(data).and_return(result) - service.test(data) - end - end + expect(integration.test(data)).to eq( + success: true, + result: result + ) end end @@ -234,26 +206,30 @@ RSpec.describe Integration do end describe '.find_or_initialize_non_project_specific_integration' do - let!(:service1) { create(:jira_service, project_id: nil, group_id: group.id) } - let!(:service2) { create(:jira_service) } + let!(:integration_1) { create(:jira_integration, project_id: nil, group_id: group.id) } + let!(:integration_2) { create(:jira_integration) } - it 'returns the right service' do - expect(Integration.find_or_initialize_non_project_specific_integration('jira', group_id: group)).to eq(service1) + it 'returns the right integration' do + expect(Integration.find_or_initialize_non_project_specific_integration('jira', group_id: group)) + .to eq(integration_1) end - it 'does not create a new service' do - expect { Integration.find_or_initialize_non_project_specific_integration('redmine', group_id: group) }.not_to change { Integration.count } + it 'does not create a new integration' do + expect { Integration.find_or_initialize_non_project_specific_integration('redmine', group_id: group) } + .not_to change(Integration, :count) end end describe '.find_or_initialize_all_non_project_specific' do shared_examples 'service instances' do it 'returns the available service instances' do - expect(Integration.find_or_initialize_all_non_project_specific(Integration.for_instance).map(&:to_param)).to match_array(Integration.available_services_names(include_project_specific: false)) + expect(Integration.find_or_initialize_all_non_project_specific(Integration.for_instance).map(&:to_param)) + .to match_array(Integration.available_integration_names(include_project_specific: false)) end it 'does not create service instances' do - expect { Integration.find_or_initialize_all_non_project_specific(Integration.for_instance) }.not_to change { Integration.count } + expect { Integration.find_or_initialize_all_non_project_specific(Integration.for_instance) } + .not_to change(Integration, :count) end end @@ -262,7 +238,7 @@ RSpec.describe Integration do context 'with all existing instances' do before do Integration.insert_all( - Integration.available_services_types(include_project_specific: false).map { |type| { instance: true, type: type } } + Integration.available_integration_types(include_project_specific: false).map { |type| { instance: true, type: type } } ) end @@ -280,7 +256,7 @@ RSpec.describe Integration do context 'with a few existing instances' do before do - create(:jira_service, :instance) + create(:jira_integration, :instance) end it_behaves_like 'service instances' @@ -290,13 +266,15 @@ RSpec.describe Integration do describe 'template' do shared_examples 'retrieves service templates' do it 'returns the available service templates' do - expect(Integration.find_or_create_templates.pluck(:type)).to match_array(Integration.available_services_types(include_project_specific: false)) + expect(Integration.find_or_create_templates.pluck(:type)).to match_array(Integration.available_integration_types(include_project_specific: false)) end end describe '.find_or_create_templates' do it 'creates service templates' do - expect { Integration.find_or_create_templates }.to change { Integration.count }.from(0).to(Integration.available_services_names(include_project_specific: false).size) + total = Integration.available_integration_names(include_project_specific: false).size + + expect { Integration.find_or_create_templates }.to change(Integration, :count).from(0).to(total) end it_behaves_like 'retrieves service templates' @@ -304,7 +282,7 @@ RSpec.describe Integration do context 'with all existing templates' do before do Integration.insert_all( - Integration.available_services_types(include_project_specific: false).map { |type| { template: true, type: type } } + Integration.available_integration_types(include_project_specific: false).map { |type| { template: true, type: type } } ) end @@ -326,11 +304,13 @@ RSpec.describe Integration do context 'with a few existing templates' do before do - create(:jira_service, :template) + create(:jira_integration, :template) end it 'creates the rest of the service templates' do - expect { Integration.find_or_create_templates }.to change { Integration.count }.from(1).to(Integration.available_services_names(include_project_specific: false).size) + total = Integration.available_integration_names(include_project_specific: false).size + + expect { Integration.find_or_create_templates }.to change(Integration, :count).from(1).to(total) end it_behaves_like 'retrieves service templates' @@ -339,36 +319,36 @@ RSpec.describe Integration do describe '.build_from_integration' do context 'when integration is invalid' do - let(:integration) do - build(:prometheus_service, :template, active: true, properties: {}) + let(:template_integration) do + build(:prometheus_integration, :template, active: true, properties: {}) .tap { |integration| integration.save!(validate: false) } end - it 'sets service to inactive' do - service = described_class.build_from_integration(integration, project_id: project.id) + it 'sets integration to inactive' do + integration = described_class.build_from_integration(template_integration, project_id: project.id) - expect(service).to be_valid - expect(service.active).to be false + expect(integration).to be_valid + expect(integration.active).to be false end end context 'when integration is an instance-level integration' do - let(:integration) { create(:jira_service, :instance) } + let(:instance_integration) { create(:jira_integration, :instance) } it 'sets inherit_from_id from integration' do - service = described_class.build_from_integration(integration, project_id: project.id) + integration = described_class.build_from_integration(instance_integration, project_id: project.id) - expect(service.inherit_from_id).to eq(integration.id) + expect(integration.inherit_from_id).to eq(instance_integration.id) end end context 'when integration is a group-level integration' do - let(:integration) { create(:jira_service, group: group, project: nil) } + let(:group_integration) { create(:jira_integration, group: group, project: nil) } it 'sets inherit_from_id from integration' do - service = described_class.build_from_integration(integration, project_id: project.id) + integration = described_class.build_from_integration(group_integration, project_id: project.id) - expect(service.inherit_from_id).to eq(integration.id) + expect(integration.inherit_from_id).to eq(group_integration.id) end end @@ -418,7 +398,7 @@ RSpec.describe Integration do context 'when data are stored in properties' do let(:properties) { data_params } let!(:integration) do - create(:jira_service, :without_properties_callback, template: true, properties: properties.merge(additional: 'something')) + create(:jira_integration, :without_properties_callback, template: true, properties: properties.merge(additional: 'something')) end it_behaves_like 'service creation from an integration' @@ -426,7 +406,7 @@ RSpec.describe Integration do context 'when data are stored in separated fields' do let(:integration) do - create(:jira_service, :template, data_params.merge(properties: {})) + create(:jira_integration, :template, data_params.merge(properties: {})) end it_behaves_like 'service creation from an integration' @@ -435,7 +415,7 @@ RSpec.describe Integration do context 'when data are stored in both properties and separated fields' do let(:properties) { data_params } let(:integration) do - create(:jira_service, :without_properties_callback, active: true, template: true, properties: properties).tap do |integration| + create(:jira_integration, :without_properties_callback, active: true, template: true, properties: properties).tap do |integration| create(:jira_tracker_data, data_params.merge(integration: integration)) end end @@ -459,39 +439,41 @@ RSpec.describe Integration do describe 'is prefilled for projects pushover service' do it "has all fields prefilled" do - service = project.find_or_initialize_service('pushover') - - expect(service.template).to eq(false) - expect(service.device).to eq('MyDevice') - expect(service.sound).to eq('mic') - expect(service.priority).to eq(4) - expect(service.api_key).to eq('123456789') + integration = project.find_or_initialize_integration('pushover') + + expect(integration).to have_attributes( + template: eq(false), + device: eq('MyDevice'), + sound: eq('mic'), + priority: eq(4), + api_key: eq('123456789') + ) end end end end describe '.default_integration' do - context 'with an instance-level service' do - let_it_be(:instance_service) { create(:jira_service, :instance) } + context 'with an instance-level integration' do + let_it_be(:instance_integration) { create(:jira_integration, :instance) } - it 'returns the instance service' do - expect(described_class.default_integration('JiraService', project)).to eq(instance_service) + it 'returns the instance integration' do + expect(described_class.default_integration('JiraService', project)).to eq(instance_integration) end - it 'returns nil for nonexistent service type' do + it 'returns nil for nonexistent integration type' do expect(described_class.default_integration('HipchatService', project)).to eq(nil) end - context 'with a group service' do - let_it_be(:group_service) { create(:jira_service, group_id: group.id, project_id: nil) } + context 'with a group integration' do + let_it_be(:group_integration) { create(:jira_integration, group_id: group.id, project_id: nil) } - it 'returns the group service for a project' do - expect(described_class.default_integration('JiraService', project)).to eq(group_service) + it 'returns the group integration for a project' do + expect(described_class.default_integration('JiraService', project)).to eq(group_integration) end - it 'returns the instance service for a group' do - expect(described_class.default_integration('JiraService', group)).to eq(instance_service) + it 'returns the instance integration for a group' do + expect(described_class.default_integration('JiraService', group)).to eq(instance_integration) end context 'with a subgroup' do @@ -499,27 +481,27 @@ RSpec.describe Integration do let!(:project) { create(:project, group: subgroup) } - it 'returns the closest group service for a project' do - expect(described_class.default_integration('JiraService', project)).to eq(group_service) + it 'returns the closest group integration for a project' do + expect(described_class.default_integration('JiraService', project)).to eq(group_integration) end - it 'returns the closest group service for a subgroup' do - expect(described_class.default_integration('JiraService', subgroup)).to eq(group_service) + it 'returns the closest group integration for a subgroup' do + expect(described_class.default_integration('JiraService', subgroup)).to eq(group_integration) end - context 'having a service with custom settings' do - let!(:subgroup_service) { create(:jira_service, group_id: subgroup.id, project_id: nil) } + context 'having a integration with custom settings' do + let!(:subgroup_integration) { create(:jira_integration, group_id: subgroup.id, project_id: nil) } - it 'returns the closest group service for a project' do - expect(described_class.default_integration('JiraService', project)).to eq(subgroup_service) + it 'returns the closest group integration for a project' do + expect(described_class.default_integration('JiraService', project)).to eq(subgroup_integration) end end - context 'having a service inheriting settings' do - let!(:subgroup_service) { create(:jira_service, group_id: subgroup.id, project_id: nil, inherit_from_id: group_service.id) } + context 'having a integration inheriting settings' do + let!(:subgroup_integration) { create(:jira_integration, group_id: subgroup.id, project_id: nil, inherit_from_id: group_integration.id) } - it 'returns the closest group service which does not inherit from its parent for a project' do - expect(described_class.default_integration('JiraService', project)).to eq(group_service) + it 'returns the closest group integration which does not inherit from its parent for a project' do + expect(described_class.default_integration('JiraService', project)).to eq(group_integration) end end end @@ -528,10 +510,10 @@ RSpec.describe Integration do end describe '.create_from_active_default_integrations' do - context 'with an active service template' do - let_it_be(:template_integration) { create(:prometheus_service, :template, api_url: 'https://prometheus.template.com/') } + context 'with an active integration template' do + let_it_be(:template_integration) { create(:prometheus_integration, :template, api_url: 'https://prometheus.template.com/') } - it 'creates a service from the template' do + it 'creates an integration from the template' do described_class.create_from_active_default_integrations(project, :project_id, with_templates: true) expect(project.reload.integrations.size).to eq(1) @@ -540,9 +522,9 @@ RSpec.describe Integration do end context 'with an active instance-level integration' do - let!(:instance_integration) { create(:prometheus_service, :instance, api_url: 'https://prometheus.instance.com/') } + let!(:instance_integration) { create(:prometheus_integration, :instance, api_url: 'https://prometheus.instance.com/') } - it 'creates a service from the instance-level integration' do + it 'creates an integration from the instance-level integration' do described_class.create_from_active_default_integrations(project, :project_id, with_templates: true) expect(project.reload.integrations.size).to eq(1) @@ -551,7 +533,7 @@ RSpec.describe Integration do end context 'passing a group' do - it 'creates a service from the instance-level integration' do + it 'creates an integration from the instance-level integration' do described_class.create_from_active_default_integrations(group, :group_id) expect(group.reload.integrations.size).to eq(1) @@ -561,9 +543,9 @@ RSpec.describe Integration do end context 'with an active group-level integration' do - let!(:group_integration) { create(:prometheus_service, group: group, project: nil, api_url: 'https://prometheus.group.com/') } + let!(:group_integration) { create(:prometheus_integration, group: group, project: nil, api_url: 'https://prometheus.group.com/') } - it 'creates a service from the group-level integration' do + it 'creates an integration from the group-level integration' do described_class.create_from_active_default_integrations(project, :project_id, with_templates: true) expect(project.reload.integrations.size).to eq(1) @@ -574,7 +556,7 @@ RSpec.describe Integration do context 'passing a group' do let!(:subgroup) { create(:group, parent: group) } - it 'creates a service from the group-level integration' do + it 'creates an integration from the group-level integration' do described_class.create_from_active_default_integrations(subgroup, :group_id) expect(subgroup.reload.integrations.size).to eq(1) @@ -584,11 +566,11 @@ RSpec.describe Integration do end context 'with an active subgroup' do - let!(:subgroup_integration) { create(:prometheus_service, group: subgroup, project: nil, api_url: 'https://prometheus.subgroup.com/') } + let!(:subgroup_integration) { create(:prometheus_integration, group: subgroup, project: nil, api_url: 'https://prometheus.subgroup.com/') } let!(:subgroup) { create(:group, parent: group) } let(:project) { create(:project, group: subgroup) } - it 'creates a service from the subgroup-level integration' do + it 'creates an integration from the subgroup-level integration' do described_class.create_from_active_default_integrations(project, :project_id, with_templates: true) expect(project.reload.integrations.size).to eq(1) @@ -601,7 +583,7 @@ RSpec.describe Integration do context 'traversal queries' do shared_examples 'correct ancestor order' do - it 'creates a service from the subgroup-level integration' do + it 'creates an integration from the subgroup-level integration' do described_class.create_from_active_default_integrations(sub_subgroup, :group_id) sub_subgroup.reload @@ -611,10 +593,10 @@ RSpec.describe Integration do expect(sub_subgroup.integrations.first.inherit_from_id).to eq(subgroup_integration.id) end - context 'having a service inheriting settings' do - let!(:subgroup_integration) { create(:prometheus_service, group: subgroup, project: nil, inherit_from_id: group_integration.id, api_url: 'https://prometheus.subgroup.com/') } + context 'having an integration inheriting settings' do + let!(:subgroup_integration) { create(:prometheus_integration, group: subgroup, project: nil, inherit_from_id: group_integration.id, api_url: 'https://prometheus.subgroup.com/') } - it 'creates a service from the group-level integration' do + it 'creates an integration from the group-level integration' do described_class.create_from_active_default_integrations(sub_subgroup, :group_id) sub_subgroup.reload @@ -656,11 +638,11 @@ RSpec.describe Integration do let_it_be(:subgroup2) { create(:group, parent: group) } let_it_be(:project1) { create(:project, group: subgroup1) } let_it_be(:project2) { create(:project, group: subgroup2) } - let_it_be(:group_integration) { create(:prometheus_service, group: group, project: nil) } - let_it_be(:subgroup_integration1) { create(:prometheus_service, group: subgroup1, project: nil, inherit_from_id: group_integration.id) } - let_it_be(:subgroup_integration2) { create(:prometheus_service, group: subgroup2, project: nil) } - let_it_be(:project_integration1) { create(:prometheus_service, group: nil, project: project1, inherit_from_id: group_integration.id) } - let_it_be(:project_integration2) { create(:prometheus_service, group: nil, project: project2, inherit_from_id: subgroup_integration2.id) } + let_it_be(:group_integration) { create(:prometheus_integration, group: group, project: nil) } + let_it_be(:subgroup_integration1) { create(:prometheus_integration, group: subgroup1, project: nil, inherit_from_id: group_integration.id) } + let_it_be(:subgroup_integration2) { create(:prometheus_integration, group: subgroup2, project: nil) } + let_it_be(:project_integration1) { create(:prometheus_integration, group: nil, project: project1, inherit_from_id: group_integration.id) } + let_it_be(:project_integration2) { create(:prometheus_integration, group: nil, project: project2, inherit_from_id: subgroup_integration2.id) } it 'returns the groups and projects inheriting from integration ancestors', :aggregate_failures do expect(described_class.inherited_descendants_from_self_or_ancestors_from(group_integration)).to eq([subgroup_integration1, project_integration1]) @@ -669,11 +651,8 @@ RSpec.describe Integration do end describe '.integration_name_to_model' do - it 'returns the model for the given service name', :aggregate_failures do + it 'returns the model for the given service name' do expect(described_class.integration_name_to_model('asana')).to eq(Integrations::Asana) - # TODO We can remove this test when all models have been namespaced: - # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60968#note_570994955 - expect(described_class.integration_name_to_model('prometheus')).to eq(PrometheusService) end it 'raises an error if service name is invalid' do @@ -897,37 +876,37 @@ RSpec.describe Integration do end end - describe '.available_services_names' do + describe '.available_integration_names' do it 'calls the right methods' do - expect(described_class).to receive(:services_names).and_call_original - expect(described_class).to receive(:dev_services_names).and_call_original - expect(described_class).to receive(:project_specific_services_names).and_call_original + expect(described_class).to receive(:integration_names).and_call_original + expect(described_class).to receive(:dev_integration_names).and_call_original + expect(described_class).to receive(:project_specific_integration_names).and_call_original - described_class.available_services_names + described_class.available_integration_names end - it 'does not call project_specific_services_names with include_project_specific false' do - expect(described_class).to receive(:services_names).and_call_original - expect(described_class).to receive(:dev_services_names).and_call_original - expect(described_class).not_to receive(:project_specific_services_names) + it 'does not call project_specific_integration_names with include_project_specific false' do + expect(described_class).to receive(:integration_names).and_call_original + expect(described_class).to receive(:dev_integration_names).and_call_original + expect(described_class).not_to receive(:project_specific_integration_names) - described_class.available_services_names(include_project_specific: false) + described_class.available_integration_names(include_project_specific: false) end - it 'does not call dev_services_names with include_dev false' do - expect(described_class).to receive(:services_names).and_call_original - expect(described_class).not_to receive(:dev_services_names) - expect(described_class).to receive(:project_specific_services_names).and_call_original + it 'does not call dev_integration_names with include_dev false' do + expect(described_class).to receive(:integration_names).and_call_original + expect(described_class).not_to receive(:dev_integration_names) + expect(described_class).to receive(:project_specific_integration_names).and_call_original - described_class.available_services_names(include_dev: false) + described_class.available_integration_names(include_dev: false) end - it { expect(described_class.available_services_names).to include('jenkins') } + it { expect(described_class.available_integration_names).to include('jenkins') } end - describe '.project_specific_services_names' do + describe '.project_specific_integration_names' do it do - expect(described_class.project_specific_services_names) + expect(described_class.project_specific_integration_names) .to include(*described_class::PROJECT_SPECIFIC_INTEGRATION_NAMES) end end diff --git a/spec/models/integrations/asana_spec.rb b/spec/models/integrations/asana_spec.rb index 4473478910a..f7e7eb1b0ae 100644 --- a/spec/models/integrations/asana_spec.rb +++ b/spec/models/integrations/asana_spec.rb @@ -3,11 +3,6 @@ require 'spec_helper' RSpec.describe Integrations::Asana do - describe 'Associations' do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } - end - describe 'Validations' do context 'active' do before do @@ -42,13 +37,12 @@ RSpec.describe Integrations::Asana do allow(@asana).to receive_messages( project: project, project_id: project.id, - service_hook: true, api_key: 'verySecret', restrict_to_branch: 'master' ) end - it 'calls Asana service to create a story' do + it 'calls Asana integration to create a story' do data = create_data_for_commits("Message from commit. related to ##{gid}") expected_message = "#{data[:user_name]} pushed to branch #{data[:ref]} of #{project.full_name} ( #{data[:commits][0][:url]} ): #{data[:commits][0][:message]}" @@ -59,7 +53,7 @@ RSpec.describe Integrations::Asana do @asana.execute(data) end - it 'calls Asana service to create a story and close a task' do + it 'calls Asana integration to create a story and close a task' do data = create_data_for_commits('fix #456789') d1 = double('Asana::Resources::Task') expect(d1).to receive(:add_comment) diff --git a/spec/models/integrations/assembla_spec.rb b/spec/models/integrations/assembla_spec.rb index e5972bce95d..960dfea3dc4 100644 --- a/spec/models/integrations/assembla_spec.rb +++ b/spec/models/integrations/assembla_spec.rb @@ -5,11 +5,6 @@ require 'spec_helper' RSpec.describe Integrations::Assembla do include StubRequests - describe "Associations" do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } - end - describe "Execute" do let(:user) { create(:user) } let(:project) { create(:project, :repository) } @@ -19,7 +14,6 @@ RSpec.describe Integrations::Assembla do allow(@assembla_integration).to receive_messages( project_id: project.id, project: project, - service_hook: true, token: 'verySecret', subdomain: 'project_name' ) diff --git a/spec/models/integrations/bamboo_spec.rb b/spec/models/integrations/bamboo_spec.rb index 39966f7978d..73ebf404828 100644 --- a/spec/models/integrations/bamboo_spec.rb +++ b/spec/models/integrations/bamboo_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Integrations::Bamboo, :use_clean_rails_memory_store_caching do let_it_be(:project) { create(:project) } - subject(:service) do + subject(:integration) do described_class.create!( project: project, properties: { @@ -22,53 +22,48 @@ RSpec.describe Integrations::Bamboo, :use_clean_rails_memory_store_caching do ) end - describe 'Associations' do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } - end - describe 'Validations' do - context 'when service is active' do + context 'when active' do before do - subject.active = true + integration.active = true end it { is_expected.to validate_presence_of(:build_key) } it { is_expected.to validate_presence_of(:bamboo_url) } - it_behaves_like 'issue tracker service URL attribute', :bamboo_url + it_behaves_like 'issue tracker integration URL attribute', :bamboo_url describe '#username' do it 'does not validate the presence of username if password is nil' do - subject.password = nil + integration.password = nil - expect(subject).not_to validate_presence_of(:username) + expect(integration).not_to validate_presence_of(:username) end it 'validates the presence of username if password is present' do - subject.password = 'secret' + integration.password = 'secret' - expect(subject).to validate_presence_of(:username) + expect(integration).to validate_presence_of(:username) end end describe '#password' do it 'does not validate the presence of password if username is nil' do - subject.username = nil + integration.username = nil - expect(subject).not_to validate_presence_of(:password) + expect(integration).not_to validate_presence_of(:password) end it 'validates the presence of password if username is present' do - subject.username = 'john' + integration.username = 'john' - expect(subject).to validate_presence_of(:password) + expect(integration).to validate_presence_of(:password) end end end - context 'when service is inactive' do + context 'when inactive' do before do - subject.active = false + integration.active = false end it { is_expected.not_to validate_presence_of(:build_key) } @@ -82,45 +77,38 @@ RSpec.describe Integrations::Bamboo, :use_clean_rails_memory_store_caching do describe 'before_update :reset_password' do context 'when a password was previously set' do it 'resets password if url changed' do - bamboo_integration = service - - bamboo_integration.bamboo_url = 'http://gitlab1.com' - bamboo_integration.save! + integration.bamboo_url = 'http://gitlab1.com' + integration.save! - expect(bamboo_integration.password).to be_nil + expect(integration.password).to be_nil end it 'does not reset password if username changed' do - bamboo_integration = service + integration.username = 'some_name' + integration.save! - bamboo_integration.username = 'some_name' - bamboo_integration.save! - - expect(bamboo_integration.password).to eq('password') + expect(integration.password).to eq('password') end it "does not reset password if new url is set together with password, even if it's the same password" do - bamboo_integration = service - - bamboo_integration.bamboo_url = 'http://gitlab_edited.com' - bamboo_integration.password = 'password' - bamboo_integration.save! + integration.bamboo_url = 'http://gitlab_edited.com' + integration.password = 'password' + integration.save! - expect(bamboo_integration.password).to eq('password') - expect(bamboo_integration.bamboo_url).to eq('http://gitlab_edited.com') + expect(integration.password).to eq('password') + expect(integration.bamboo_url).to eq('http://gitlab_edited.com') end end it 'saves password if new url is set together with password when no password was previously set' do - bamboo_integration = service - bamboo_integration.password = nil + integration.password = nil - bamboo_integration.bamboo_url = 'http://gitlab_edited.com' - bamboo_integration.password = 'password' - bamboo_integration.save! + integration.bamboo_url = 'http://gitlab_edited.com' + integration.password = 'password' + integration.save! - expect(bamboo_integration.password).to eq('password') - expect(bamboo_integration.bamboo_url).to eq('http://gitlab_edited.com') + expect(integration.password).to eq('password') + expect(integration.bamboo_url).to eq('http://gitlab_edited.com') end end end @@ -129,29 +117,29 @@ RSpec.describe Integrations::Bamboo, :use_clean_rails_memory_store_caching do it 'runs update and build action' do stub_update_and_build_request - subject.execute(Gitlab::DataBuilder::Push::SAMPLE_DATA) + integration.execute(Gitlab::DataBuilder::Push::SAMPLE_DATA) end end describe '#build_page' do it 'returns the contents of the reactive cache' do - stub_reactive_cache(service, { build_page: 'foo' }, 'sha', 'ref') + stub_reactive_cache(integration, { build_page: 'foo' }, 'sha', 'ref') - expect(service.build_page('sha', 'ref')).to eq('foo') + expect(integration.build_page('sha', 'ref')).to eq('foo') end end describe '#commit_status' do it 'returns the contents of the reactive cache' do - stub_reactive_cache(service, { commit_status: 'foo' }, 'sha', 'ref') + stub_reactive_cache(integration, { commit_status: 'foo' }, 'sha', 'ref') - expect(service.commit_status('sha', 'ref')).to eq('foo') + expect(integration.commit_status('sha', 'ref')).to eq('foo') end end shared_examples 'reactive cache calculation' do describe '#build_page' do - subject { service.calculate_reactive_cache('123', 'unused')[:build_page] } + subject { integration.calculate_reactive_cache('123', 'unused')[:build_page] } it 'returns a specific URL when status is 500' do stub_request(status: 500) @@ -183,7 +171,7 @@ RSpec.describe Integrations::Bamboo, :use_clean_rails_memory_store_caching do end describe '#commit_status' do - subject { service.calculate_reactive_cache('123', 'unused')[:commit_status] } + subject { integration.calculate_reactive_cache('123', 'unused')[:commit_status] } it 'sets commit status to :error when status is 500' do stub_request(status: 500) diff --git a/spec/models/integrations/base_chat_notification_spec.rb b/spec/models/integrations/base_chat_notification_spec.rb index 656eaa3bbdd..ac4031a9b7d 100644 --- a/spec/models/integrations/base_chat_notification_spec.rb +++ b/spec/models/integrations/base_chat_notification_spec.rb @@ -15,26 +15,8 @@ RSpec.describe Integrations::BaseChatNotification do it { is_expected.to validate_inclusion_of(:labels_to_be_notified_behavior).in_array(%w[match_any match_all]).allow_blank } end - describe '#can_test?' do - context 'with empty repository' do - it 'returns true' do - subject.project = create(:project, :empty_repo) - - expect(subject.can_test?).to be true - end - end - - context 'with repository' do - it 'returns true' do - subject.project = create(:project, :repository) - - expect(subject.can_test?).to be true - end - end - end - describe '#execute' do - subject(:chat_service) { described_class.new } + subject(:chat_integration) { described_class.new } let_it_be(:project) { create(:project, :repository) } @@ -43,10 +25,9 @@ RSpec.describe Integrations::BaseChatNotification do let(:data) { Gitlab::DataBuilder::Push.build_sample(subject.project, user) } before do - allow(chat_service).to receive_messages( + allow(chat_integration).to receive_messages( project: project, project_id: project.id, - service_hook: true, webhook: webhook_url ) @@ -57,8 +38,8 @@ RSpec.describe Integrations::BaseChatNotification do context 'with a repository' do it 'returns true' do - expect(chat_service).to receive(:notify).and_return(true) - expect(chat_service.execute(data)).to be true + expect(chat_integration).to receive(:notify).and_return(true) + expect(chat_integration.execute(data)).to be true end end @@ -66,8 +47,8 @@ RSpec.describe Integrations::BaseChatNotification do it 'returns true' do subject.project = create(:project, :empty_repo) - expect(chat_service).to receive(:notify).and_return(true) - expect(chat_service.execute(data)).to be true + expect(chat_integration).to receive(:notify).and_return(true) + expect(chat_integration.execute(data)).to be true end end @@ -75,8 +56,8 @@ RSpec.describe Integrations::BaseChatNotification do it 'does not remove spaces' do allow(project).to receive(:full_name).and_return('Project Name') - expect(chat_service).to receive(:get_message).with(any_args, hash_including(project_name: 'Project Name')) - chat_service.execute(data) + expect(chat_integration).to receive(:get_message).with(any_args, hash_including(project_name: 'Project Name')) + chat_integration.execute(data) end end @@ -89,76 +70,76 @@ RSpec.describe Integrations::BaseChatNotification do let(:data) { Gitlab::DataBuilder::Note.build(note, user) } - shared_examples 'notifies the chat service' do + shared_examples 'notifies the chat integration' do specify do - expect(chat_service).to receive(:notify).with(any_args) + expect(chat_integration).to receive(:notify).with(any_args) - chat_service.execute(data) + chat_integration.execute(data) end end - shared_examples 'does not notify the chat service' do + shared_examples 'does not notify the chat integration' do specify do - expect(chat_service).not_to receive(:notify).with(any_args) + expect(chat_integration).not_to receive(:notify).with(any_args) - chat_service.execute(data) + chat_integration.execute(data) end end - it_behaves_like 'notifies the chat service' + it_behaves_like 'notifies the chat integration' context 'with label filter' do - subject(:chat_service) { described_class.new(labels_to_be_notified: '~Bug') } + subject(:chat_integration) { described_class.new(labels_to_be_notified: '~Bug') } - it_behaves_like 'notifies the chat service' + it_behaves_like 'notifies the chat integration' context 'MergeRequest events' do let(:data) { create(:merge_request, labels: [label]).to_hook_data(user) } - it_behaves_like 'notifies the chat service' + it_behaves_like 'notifies the chat integration' end context 'Issue events' do let(:data) { issue.to_hook_data(user) } - it_behaves_like 'notifies the chat service' + it_behaves_like 'notifies the chat integration' end end context 'when labels_to_be_notified_behavior is not defined' do - subject(:chat_service) { described_class.new(labels_to_be_notified: label_filter) } + subject(:chat_integration) { described_class.new(labels_to_be_notified: label_filter) } context 'no matching labels' do let(:label_filter) { '~some random label' } - it_behaves_like 'does not notify the chat service' + it_behaves_like 'does not notify the chat integration' end context 'only one label matches' do let(:label_filter) { '~some random label, ~Bug' } - it_behaves_like 'notifies the chat service' + it_behaves_like 'notifies the chat integration' end end context 'when labels_to_be_notified_behavior is blank' do - subject(:chat_service) { described_class.new(labels_to_be_notified: label_filter, labels_to_be_notified_behavior: '') } + subject(:chat_integration) { described_class.new(labels_to_be_notified: label_filter, labels_to_be_notified_behavior: '') } context 'no matching labels' do let(:label_filter) { '~some random label' } - it_behaves_like 'does not notify the chat service' + it_behaves_like 'does not notify the chat integration' end context 'only one label matches' do let(:label_filter) { '~some random label, ~Bug' } - it_behaves_like 'notifies the chat service' + it_behaves_like 'notifies the chat integration' end end context 'when labels_to_be_notified_behavior is match_any' do - subject(:chat_service) do + subject(:chat_integration) do described_class.new( labels_to_be_notified: label_filter, labels_to_be_notified_behavior: 'match_any' @@ -168,24 +149,24 @@ RSpec.describe Integrations::BaseChatNotification do context 'no label filter' do let(:label_filter) { nil } - it_behaves_like 'notifies the chat service' + it_behaves_like 'notifies the chat integration' end context 'no matching labels' do let(:label_filter) { '~some random label' } - it_behaves_like 'does not notify the chat service' + it_behaves_like 'does not notify the chat integration' end context 'only one label matches' do let(:label_filter) { '~some random label, ~Bug' } - it_behaves_like 'notifies the chat service' + it_behaves_like 'notifies the chat integration' end end context 'when labels_to_be_notified_behavior is match_all' do - subject(:chat_service) do + subject(:chat_integration) do described_class.new( labels_to_be_notified: label_filter, labels_to_be_notified_behavior: 'match_all' @@ -195,31 +176,31 @@ RSpec.describe Integrations::BaseChatNotification do context 'no label filter' do let(:label_filter) { nil } - it_behaves_like 'notifies the chat service' + it_behaves_like 'notifies the chat integration' end context 'no matching labels' do let(:label_filter) { '~some random label' } - it_behaves_like 'does not notify the chat service' + it_behaves_like 'does not notify the chat integration' end context 'only one label matches' do let(:label_filter) { '~some random label, ~Bug' } - it_behaves_like 'does not notify the chat service' + it_behaves_like 'does not notify the chat integration' end context 'labels matches exactly' do let(:label_filter) { '~Bug, ~Backend, ~Community contribution' } - it_behaves_like 'notifies the chat service' + it_behaves_like 'notifies the chat integration' end context 'labels matches but object has more' do let(:label_filter) { '~Bug, ~Backend' } - it_behaves_like 'notifies the chat service' + it_behaves_like 'notifies the chat integration' end context 'labels are distributed on multiple objects' do @@ -241,22 +222,22 @@ RSpec.describe Integrations::BaseChatNotification do }) end - it_behaves_like 'does not notify the chat service' + it_behaves_like 'does not notify the chat integration' end end end context 'with "channel" property' do before do - allow(chat_service).to receive(:channel).and_return(channel) + allow(chat_integration).to receive(:channel).and_return(channel) end context 'empty string' do let(:channel) { '' } it 'does not include the channel' do - expect(chat_service).to receive(:notify).with(any_args, hash_excluding(:channel)).and_return(true) - expect(chat_service.execute(data)).to be(true) + expect(chat_integration).to receive(:notify).with(any_args, hash_excluding(:channel)).and_return(true) + expect(chat_integration.execute(data)).to be(true) end end @@ -264,20 +245,20 @@ RSpec.describe Integrations::BaseChatNotification do let(:channel) { ' ' } it 'does not include the channel' do - expect(chat_service).to receive(:notify).with(any_args, hash_excluding(:channel)).and_return(true) - expect(chat_service.execute(data)).to be(true) + expect(chat_integration).to receive(:notify).with(any_args, hash_excluding(:channel)).and_return(true) + expect(chat_integration.execute(data)).to be(true) end end end shared_examples 'with channel specified' do |channel, expected_channels| before do - allow(chat_service).to receive(:push_channel).and_return(channel) + allow(chat_integration).to receive(:push_channel).and_return(channel) end it 'notifies all channels' do - expect(chat_service).to receive(:notify).with(any_args, hash_including(channel: expected_channels)).and_return(true) - expect(chat_service.execute(data)).to be(true) + expect(chat_integration).to receive(:notify).with(any_args, hash_including(channel: expected_channels)).and_return(true) + expect(chat_integration.execute(data)).to be(true) end end diff --git a/spec/models/integrations/base_issue_tracker_spec.rb b/spec/models/integrations/base_issue_tracker_spec.rb index 0f1bc39929a..25e27e96a84 100644 --- a/spec/models/integrations/base_issue_tracker_spec.rb +++ b/spec/models/integrations/base_issue_tracker_spec.rb @@ -7,26 +7,26 @@ RSpec.describe Integrations::BaseIssueTracker do let(:project) { create :project } describe 'only one issue tracker per project' do - let(:service) { Integrations::Redmine.new(project: project, active: true, issue_tracker_data: build(:issue_tracker_data)) } + let(:integration) { Integrations::Redmine.new(project: project, active: true, issue_tracker_data: build(:issue_tracker_data)) } before do create(:custom_issue_tracker_integration, project: project) end - context 'when service is changed manually by user' do + context 'when integration is changed manually by user' do it 'executes the validation' do - valid = service.valid?(:manual_change) + valid = integration.valid?(:manual_change) expect(valid).to be_falsey - expect(service.errors[:base]).to include( + expect(integration.errors[:base]).to include( 'Another issue tracker is already in use. Only one issue tracker service can be active at a time' ) end end - context 'when service is changed internally' do + context 'when integration is changed internally' do it 'does not execute the validation' do - expect(service.valid?).to be_truthy + expect(integration.valid?).to be_truthy end end end diff --git a/spec/models/integrations/bugzilla_spec.rb b/spec/models/integrations/bugzilla_spec.rb index e75fa8dd4d4..432306c8fa8 100644 --- a/spec/models/integrations/bugzilla_spec.rb +++ b/spec/models/integrations/bugzilla_spec.rb @@ -3,13 +3,8 @@ require 'spec_helper' RSpec.describe Integrations::Bugzilla do - describe 'Associations' do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } - end - describe 'Validations' do - context 'when service is active' do + context 'when integration is active' do before do subject.active = true end @@ -17,12 +12,12 @@ RSpec.describe Integrations::Bugzilla do it { is_expected.to validate_presence_of(:project_url) } it { is_expected.to validate_presence_of(:issues_url) } it { is_expected.to validate_presence_of(:new_issue_url) } - it_behaves_like 'issue tracker service URL attribute', :project_url - it_behaves_like 'issue tracker service URL attribute', :issues_url - it_behaves_like 'issue tracker service URL attribute', :new_issue_url + it_behaves_like 'issue tracker integration URL attribute', :project_url + it_behaves_like 'issue tracker integration URL attribute', :issues_url + it_behaves_like 'issue tracker integration URL attribute', :new_issue_url end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end diff --git a/spec/models/integrations/buildkite_spec.rb b/spec/models/integrations/buildkite_spec.rb index 7dc81da7003..4207ae0d555 100644 --- a/spec/models/integrations/buildkite_spec.rb +++ b/spec/models/integrations/buildkite_spec.rb @@ -8,34 +8,32 @@ RSpec.describe Integrations::Buildkite, :use_clean_rails_memory_store_caching do let(:project) { create(:project) } - subject(:service) do + subject(:integration) do described_class.create!( project: project, properties: { - service_hook: true, project_url: 'https://buildkite.com/organization-name/example-pipeline', token: 'secret-sauce-webhook-token:secret-sauce-status-token' } ) end - describe 'Associations' do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } + it_behaves_like Integrations::HasWebHook do + let(:hook_url) { 'https://webhook.buildkite.com/deliver/secret-sauce-webhook-token' } end describe 'Validations' do - context 'when service is active' do + context 'when integration is active' do before do subject.active = true end it { is_expected.to validate_presence_of(:project_url) } it { is_expected.to validate_presence_of(:token) } - it_behaves_like 'issue tracker service URL attribute', :project_url + it_behaves_like 'issue tracker integration URL attribute', :project_url end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end @@ -47,7 +45,7 @@ RSpec.describe Integrations::Buildkite, :use_clean_rails_memory_store_caching do describe '.supported_events' do it 'supports push, merge_request, and tag_push events' do - expect(service.supported_events).to eq %w(push merge_request tag_push) + expect(integration.supported_events).to eq %w(push merge_request tag_push) end end @@ -57,18 +55,18 @@ RSpec.describe Integrations::Buildkite, :use_clean_rails_memory_store_caching do end it 'always activates SSL verification after saved' do - service.create_service_hook(enable_ssl_verification: false) + integration.create_service_hook(enable_ssl_verification: false) - service.enable_ssl_verification = false - service.active = true + integration.enable_ssl_verification = false + integration.active = true - expect { service.save! } - .to change { service.service_hook.enable_ssl_verification }.from(false).to(true) + expect { integration.save! } + .to change { integration.service_hook.enable_ssl_verification }.from(false).to(true) end - describe '#webhook_url' do + describe '#hook_url' do it 'returns the webhook url' do - expect(service.webhook_url).to eq( + expect(integration.hook_url).to eq( 'https://webhook.buildkite.com/deliver/secret-sauce-webhook-token' ) end @@ -76,7 +74,7 @@ RSpec.describe Integrations::Buildkite, :use_clean_rails_memory_store_caching do describe '#commit_status_path' do it 'returns the correct status page' do - expect(service.commit_status_path('2ab7834c')).to eq( + expect(integration.commit_status_path('2ab7834c')).to eq( 'https://gitlab.buildkite.com/status/secret-sauce-status-token.json?commit=2ab7834c' ) end @@ -84,7 +82,7 @@ RSpec.describe Integrations::Buildkite, :use_clean_rails_memory_store_caching do describe '#build_page' do it 'returns the correct build page' do - expect(service.build_page('2ab7834c', nil)).to eq( + expect(integration.build_page('2ab7834c', nil)).to eq( 'https://buildkite.com/organization-name/example-pipeline/builds?commit=2ab7834c' ) end @@ -92,9 +90,9 @@ RSpec.describe Integrations::Buildkite, :use_clean_rails_memory_store_caching do describe '#commit_status' do it 'returns the contents of the reactive cache' do - stub_reactive_cache(service, { commit_status: 'foo' }, 'sha', 'ref') + stub_reactive_cache(integration, { commit_status: 'foo' }, 'sha', 'ref') - expect(service.commit_status('sha', 'ref')).to eq('foo') + expect(integration.commit_status('sha', 'ref')).to eq('foo') end end @@ -104,7 +102,7 @@ RSpec.describe Integrations::Buildkite, :use_clean_rails_memory_store_caching do 'https://gitlab.buildkite.com/status/secret-sauce-status-token.json?commit=123' end - subject { service.calculate_reactive_cache('123', 'unused')[:commit_status] } + subject { integration.calculate_reactive_cache('123', 'unused')[:commit_status] } it 'sets commit status to :error when status is 500' do stub_request(status: 500) diff --git a/spec/models/integrations/campfire_spec.rb b/spec/models/integrations/campfire_spec.rb index d68f8e0bd4e..0044e6fae21 100644 --- a/spec/models/integrations/campfire_spec.rb +++ b/spec/models/integrations/campfire_spec.rb @@ -5,13 +5,8 @@ require 'spec_helper' RSpec.describe Integrations::Campfire do include StubRequests - describe 'Associations' do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } - end - describe 'Validations' do - context 'when service is active' do + context 'when integration is active' do before do subject.active = true end @@ -19,7 +14,7 @@ RSpec.describe Integrations::Campfire do it { is_expected.to validate_presence_of(:token) } end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end @@ -37,7 +32,6 @@ RSpec.describe Integrations::Campfire do allow(@campfire_integration).to receive_messages( project_id: project.id, project: project, - service_hook: true, token: 'verySecret', subdomain: 'project-name', room: 'test-room' diff --git a/spec/models/integrations/confluence_spec.rb b/spec/models/integrations/confluence_spec.rb index 08e18c99376..e2f9316bc95 100644 --- a/spec/models/integrations/confluence_spec.rb +++ b/spec/models/integrations/confluence_spec.rb @@ -3,17 +3,12 @@ require 'spec_helper' RSpec.describe Integrations::Confluence do - describe 'Associations' do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } - end - describe 'Validations' do before do subject.active = active end - context 'when service is active' do + context 'when integration is active' do let(:active) { true } it { is_expected.not_to allow_value('https://example.com').for(:confluence_url) } @@ -35,7 +30,7 @@ RSpec.describe Integrations::Confluence do it { is_expected.to validate_presence_of(:confluence_url) } end - context 'when service is inactive' do + context 'when integration is inactive' do let(:active) { false } it { is_expected.not_to validate_presence_of(:confluence_url) } @@ -71,13 +66,13 @@ RSpec.describe Integrations::Confluence do subject { project.project_setting.has_confluence? } - it 'sets the property to true when service is active' do + it 'sets the property to true when integration is active' do create(:confluence_integration, project: project, active: true) is_expected.to be(true) end - it 'sets the property to false when service is not active' do + it 'sets the property to false when integration is not active' do create(:confluence_integration, project: project, active: false) is_expected.to be(false) diff --git a/spec/models/integrations/custom_issue_tracker_spec.rb b/spec/models/integrations/custom_issue_tracker_spec.rb index 25f2648e738..e1ffe7a74f0 100644 --- a/spec/models/integrations/custom_issue_tracker_spec.rb +++ b/spec/models/integrations/custom_issue_tracker_spec.rb @@ -3,13 +3,8 @@ require 'spec_helper' RSpec.describe Integrations::CustomIssueTracker do - describe 'Associations' do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } - end - describe 'Validations' do - context 'when service is active' do + context 'when integration is active' do before do subject.active = true end @@ -17,12 +12,12 @@ RSpec.describe Integrations::CustomIssueTracker do it { is_expected.to validate_presence_of(:project_url) } it { is_expected.to validate_presence_of(:issues_url) } it { is_expected.to validate_presence_of(:new_issue_url) } - it_behaves_like 'issue tracker service URL attribute', :project_url - it_behaves_like 'issue tracker service URL attribute', :issues_url - it_behaves_like 'issue tracker service URL attribute', :new_issue_url + it_behaves_like 'issue tracker integration URL attribute', :project_url + it_behaves_like 'issue tracker integration URL attribute', :issues_url + it_behaves_like 'issue tracker integration URL attribute', :new_issue_url end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end diff --git a/spec/models/integrations/datadog_spec.rb b/spec/models/integrations/datadog_spec.rb index 165b21840e0..e2749ab1bc1 100644 --- a/spec/models/integrations/datadog_spec.rb +++ b/spec/models/integrations/datadog_spec.rb @@ -10,13 +10,13 @@ RSpec.describe Integrations::Datadog do let(:active) { true } let(:dd_site) { 'datadoghq.com' } - let(:default_url) { 'https://webhooks-http-intake.logs.datadoghq.com/v1/input/' } + let(:default_url) { 'https://webhooks-http-intake.logs.datadoghq.com/api/v2/webhook' } let(:api_url) { '' } let(:api_key) { SecureRandom.hex(32) } let(:dd_env) { 'ci' } let(:dd_service) { 'awesome-gitlab' } - let(:expected_hook_url) { default_url + api_key + "?env=#{dd_env}&service=#{dd_service}" } + let(:expected_hook_url) { default_url + "?dd-api-key=#{api_key}&env=#{dd_env}&service=#{dd_service}" } let(:instance) do described_class.new( @@ -38,9 +38,9 @@ RSpec.describe Integrations::Datadog do let(:pipeline_data) { Gitlab::DataBuilder::Pipeline.build(pipeline) } let(:build_data) { Gitlab::DataBuilder::Build.build(build) } - describe 'associations' do - it { is_expected.to belong_to(:project) } - it { is_expected.to have_one(:service_hook) } + it_behaves_like Integrations::HasWebHook do + let(:integration) { instance } + let(:hook_url) { "#{described_class::URL_TEMPLATE % { datadog_domain: dd_site }}?dd-api-key=#{api_key}&env=#{dd_env}&service=#{dd_service}" } end describe 'validations' do @@ -65,7 +65,7 @@ RSpec.describe Integrations::Datadog do context 'with custom api_url' do let(:dd_site) { '' } - let(:api_url) { 'https://webhooks-http-intake.logs.datad0g.com/v1/input/' } + let(:api_url) { 'https://webhooks-http-intake.logs.datad0g.com/api/v2/webhook' } it { is_expected.not_to validate_presence_of(:datadog_site) } it { is_expected.to validate_presence_of(:api_url) } @@ -91,7 +91,7 @@ RSpec.describe Integrations::Datadog do end end - context 'when service is not active' do + context 'when integration is not active' do let(:active) { false } it { is_expected.to be_valid } @@ -107,9 +107,9 @@ RSpec.describe Integrations::Datadog do end context 'with custom URL' do - let(:api_url) { 'https://webhooks-http-intake.logs.datad0g.com/v1/input/' } + let(:api_url) { 'https://webhooks-http-intake.logs.datad0g.com/api/v2/webhook' } - it { is_expected.to eq(api_url + api_key + "?env=#{dd_env}&service=#{dd_service}") } + it { is_expected.to eq(api_url + "?dd-api-key=#{api_key}&env=#{dd_env}&service=#{dd_service}") } context 'blank' do let(:api_url) { '' } @@ -122,7 +122,7 @@ RSpec.describe Integrations::Datadog do let(:dd_service) { '' } let(:dd_env) { '' } - it { is_expected.to eq(default_url + api_key) } + it { is_expected.to eq(default_url + "?dd-api-key=#{api_key}") } end end diff --git a/spec/models/integrations/discord_spec.rb b/spec/models/integrations/discord_spec.rb index bff6a8ee5b2..b85620782c1 100644 --- a/spec/models/integrations/discord_spec.rb +++ b/spec/models/integrations/discord_spec.rb @@ -11,7 +11,9 @@ RSpec.describe Integrations::Discord do embeds: [ include( author: include(name: be_present), - description: be_present + description: be_present, + color: be_present, + timestamp: be_present ) ] } @@ -33,7 +35,6 @@ RSpec.describe Integrations::Discord do allow(subject).to receive_messages( project: project, project_id: project.id, - service_hook: true, webhook: webhook_url ) @@ -47,15 +48,19 @@ RSpec.describe Integrations::Discord do allow(client).to receive(:execute).and_yield(builder) end - subject.execute(sample_data) + freeze_time do + subject.execute(sample_data) - expect(builder.to_json_hash[:embeds].first).to include( - description: start_with("#{user.name} pushed to branch [master](http://localhost/#{project.namespace.path}/#{project.path}/commits/master) of"), - author: hash_including( - icon_url: start_with('https://www.gravatar.com/avatar/'), - name: user.name + expect(builder.to_json_hash[:embeds].first).to include( + description: start_with("#{user.name} pushed to branch [master](http://localhost/#{project.namespace.path}/#{project.path}/commits/master) of"), + author: hash_including( + icon_url: start_with('https://www.gravatar.com/avatar/'), + name: user.name + ), + color: 16543014, + timestamp: Time.now.utc.iso8601 ) - ) + end end context 'DNS rebind to local address' do diff --git a/spec/models/integrations/drone_ci_spec.rb b/spec/models/integrations/drone_ci_spec.rb index 137f078edca..062e23d628e 100644 --- a/spec/models/integrations/drone_ci_spec.rb +++ b/spec/models/integrations/drone_ci_spec.rb @@ -5,11 +5,6 @@ require 'spec_helper' RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do include ReactiveCachingHelpers - describe 'associations' do - it { is_expected.to belong_to(:project) } - it { is_expected.to have_one(:service_hook) } - end - describe 'validations' do context 'active' do before do @@ -18,7 +13,7 @@ RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do it { is_expected.to validate_presence_of(:token) } it { is_expected.to validate_presence_of(:drone_url) } - it_behaves_like 'issue tracker service URL attribute', :drone_url + it_behaves_like 'issue tracker integration URL attribute', :drone_url end context 'inactive' do @@ -32,7 +27,15 @@ RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do end shared_context :drone_ci_integration do - let(:drone) { described_class.new } + subject(:drone) do + described_class.new( + project: project, + active: true, + drone_url: drone_url, + token: token + ) + end + let(:project) { create(:project, :repository, name: 'project') } let(:path) { project.full_path } let(:drone_url) { 'http://drone.example.com' } @@ -45,16 +48,6 @@ RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do let(:build_page) { "#{drone_url}/gitlab/#{path}/redirect/commits/#{sha}?branch=#{branch}" } let(:commit_status_path) { "#{drone_url}/gitlab/#{path}/commits/#{sha}?branch=#{branch}&access_token=#{token}" } - before do - allow(drone).to receive_messages( - project_id: project.id, - project: project, - active: true, - drone_url: drone_url, - token: token - ) - end - def stub_request(status: 200, body: nil) body ||= %q({"status":"success"}) @@ -66,7 +59,21 @@ RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do end end - describe "service page/path methods" do + it_behaves_like Integrations::HasWebHook do + include_context :drone_ci_integration + + let(:integration) { drone } + let(:hook_url) { "#{drone_url}/hook?owner=#{project.namespace.full_path}&name=#{project.path}&access_token=#{token}" } + + it 'does not create a hook if project is not present' do + integration.project = nil + integration.instance = true + + expect { integration.save! }.not_to change(ServiceHook, :count) + end + end + + describe "integration page/path methods" do include_context :drone_ci_integration it { expect(drone.build_page(sha, branch)).to eq(build_page) } @@ -137,10 +144,17 @@ RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do Gitlab::DataBuilder::Push.build_sample(project, user) end - it do - service_hook = double - expect(service_hook).to receive(:execute) - expect(drone).to receive(:service_hook).and_return(service_hook) + it 'executes the webhook' do + expect(drone).to receive(:execute_web_hook!).with(push_sample_data) + + drone.execute(push_sample_data) + end + + it 'does not try to execute the webhook if the integration is not in a project' do + drone.project = nil + drone.instance = true + + expect(drone).not_to receive(:execute_web_hook!) drone.execute(push_sample_data) end diff --git a/spec/models/integrations/emails_on_push_spec.rb b/spec/models/integrations/emails_on_push_spec.rb index c82d4bdff9b..bdca267f6cb 100644 --- a/spec/models/integrations/emails_on_push_spec.rb +++ b/spec/models/integrations/emails_on_push_spec.rb @@ -6,7 +6,7 @@ RSpec.describe Integrations::EmailsOnPush do let_it_be(:project) { create_default(:project).freeze } describe 'Validations' do - context 'when service is active' do + context 'when integration is active' do before do subject.active = true end @@ -14,7 +14,7 @@ RSpec.describe Integrations::EmailsOnPush do it { is_expected.to validate_presence_of(:recipients) } end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end @@ -27,7 +27,7 @@ RSpec.describe Integrations::EmailsOnPush do stub_const("#{described_class}::RECIPIENTS_LIMIT", 2) end - subject(:service) { described_class.new(project: project, recipients: recipients, active: true) } + subject(:integration) { described_class.new(project: project, recipients: recipients, active: true) } context 'valid number of recipients' do let(:recipients) { 'foo@bar.com duplicate@example.com Duplicate@example.com invalid-email' } @@ -43,14 +43,14 @@ RSpec.describe Integrations::EmailsOnPush do it { is_expected.not_to be_valid } it 'adds an error message' do - service.valid? + integration.valid? - expect(service.errors).to contain_exactly('Recipients can\'t exceed 2') + expect(integration.errors).to contain_exactly('Recipients can\'t exceed 2') end - context 'when service is not active' do + context 'when integration is not active' do before do - service.active = false + integration.active = false end it { is_expected.to be_valid } diff --git a/spec/models/integrations/ewm_spec.rb b/spec/models/integrations/ewm_spec.rb index 38897adb447..49681fefe55 100644 --- a/spec/models/integrations/ewm_spec.rb +++ b/spec/models/integrations/ewm_spec.rb @@ -3,13 +3,8 @@ require 'spec_helper' RSpec.describe Integrations::Ewm do - describe 'Associations' do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } - end - describe 'Validations' do - context 'when service is active' do + context 'when integration is active' do before do subject.active = true end @@ -17,12 +12,12 @@ RSpec.describe Integrations::Ewm do it { is_expected.to validate_presence_of(:project_url) } it { is_expected.to validate_presence_of(:issues_url) } it { is_expected.to validate_presence_of(:new_issue_url) } - it_behaves_like 'issue tracker service URL attribute', :project_url - it_behaves_like 'issue tracker service URL attribute', :issues_url - it_behaves_like 'issue tracker service URL attribute', :new_issue_url + it_behaves_like 'issue tracker integration URL attribute', :project_url + it_behaves_like 'issue tracker integration URL attribute', :issues_url + it_behaves_like 'issue tracker integration URL attribute', :new_issue_url end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end diff --git a/spec/models/integrations/external_wiki_spec.rb b/spec/models/integrations/external_wiki_spec.rb index 8c20b810301..e4d6a1c7c84 100644 --- a/spec/models/integrations/external_wiki_spec.rb +++ b/spec/models/integrations/external_wiki_spec.rb @@ -3,22 +3,17 @@ require 'spec_helper' RSpec.describe Integrations::ExternalWiki do - describe "Associations" do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } - end - describe 'Validations' do - context 'when service is active' do + context 'when integration is active' do before do subject.active = true end it { is_expected.to validate_presence_of(:external_wiki_url) } - it_behaves_like 'issue tracker service URL attribute', :external_wiki_url + it_behaves_like 'issue tracker integration URL attribute', :external_wiki_url end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end diff --git a/spec/models/integrations/flowdock_spec.rb b/spec/models/integrations/flowdock_spec.rb index 189831fa32d..daafb1b3958 100644 --- a/spec/models/integrations/flowdock_spec.rb +++ b/spec/models/integrations/flowdock_spec.rb @@ -3,13 +3,8 @@ require 'spec_helper' RSpec.describe Integrations::Flowdock do - describe "Associations" do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } - end - describe 'Validations' do - context 'when service is active' do + context 'when integration is active' do before do subject.active = true end @@ -17,7 +12,7 @@ RSpec.describe Integrations::Flowdock do it { is_expected.to validate_presence_of(:token) } end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end @@ -38,7 +33,6 @@ RSpec.describe Integrations::Flowdock do allow(flowdock_integration).to receive_messages( project_id: project.id, project: project, - service_hook: true, token: 'verySecret' ) WebMock.stub_request(:post, api_url) diff --git a/spec/models/integrations/irker_spec.rb b/spec/models/integrations/irker_spec.rb index a69be1292e0..8b207e8b43e 100644 --- a/spec/models/integrations/irker_spec.rb +++ b/spec/models/integrations/irker_spec.rb @@ -5,13 +5,8 @@ require 'socket' require 'json' RSpec.describe Integrations::Irker do - describe 'Associations' do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } - end - describe 'Validations' do - context 'when service is active' do + context 'when integration is active' do before do subject.active = true end @@ -19,7 +14,7 @@ RSpec.describe Integrations::Irker do it { is_expected.to validate_presence_of(:recipients) } end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end @@ -46,7 +41,6 @@ RSpec.describe Integrations::Irker do active: true, project: project, project_id: project.id, - service_hook: true, server_host: @irker_server.addr[2], server_port: @irker_server.addr[1], default_irc_uri: 'irc://chat.freenode.net/', diff --git a/spec/models/integrations/jenkins_spec.rb b/spec/models/integrations/jenkins_spec.rb index 2374dfe4480..9eb2a7fc098 100644 --- a/spec/models/integrations/jenkins_spec.rb +++ b/spec/models/integrations/jenkins_spec.rb @@ -24,14 +24,14 @@ RSpec.describe Integrations::Jenkins do let(:jenkins_authorization) { "Basic " + ::Base64.strict_encode64(jenkins_username + ':' + jenkins_password) } - describe 'Associations' do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } + it_behaves_like Integrations::HasWebHook do + let(:integration) { described_class.new(jenkins_params) } + let(:hook_url) { "http://#{ERB::Util.url_encode jenkins_username}:#{ERB::Util.url_encode jenkins_password}@jenkins.example.com/project/my_project" } end describe 'username validation' do - before do - @jenkins_service = described_class.create!( + let(:jenkins_integration) do + described_class.create!( active: active, project: project, properties: { @@ -43,9 +43,9 @@ RSpec.describe Integrations::Jenkins do ) end - subject { @jenkins_service } + subject { jenkins_integration } - context 'when the service is active' do + context 'when the integration is active' do let(:active) { true } context 'when password was not touched' do @@ -74,7 +74,7 @@ RSpec.describe Integrations::Jenkins do end end - context 'when the service is inactive' do + context 'when the integration is inactive' do let(:active) { false } it { is_expected.not_to validate_presence_of :username } @@ -84,7 +84,7 @@ RSpec.describe Integrations::Jenkins do describe '#hook_url' do let(:username) { nil } let(:password) { nil } - let(:jenkins_service) do + let(:jenkins_integration) do described_class.new( project: project, properties: { @@ -96,7 +96,7 @@ RSpec.describe Integrations::Jenkins do ) end - subject { jenkins_service.hook_url } + subject { jenkins_integration.hook_url } context 'when the jenkins_url has no relative path' do let(:jenkins_url) { 'http://jenkins.example.com/' } @@ -138,10 +138,10 @@ RSpec.describe Integrations::Jenkins do user = create(:user, username: 'username') project = create(:project, name: 'project') push_sample_data = Gitlab::DataBuilder::Push.build_sample(project, user) - jenkins_service = described_class.create!(jenkins_params) + jenkins_integration = described_class.create!(jenkins_params) stub_request(:post, jenkins_hook_url).with(headers: { 'Authorization' => jenkins_authorization }) - result = jenkins_service.test(push_sample_data) + result = jenkins_integration.test(push_sample_data) expect(result).to eq({ success: true, result: '' }) end @@ -152,20 +152,20 @@ RSpec.describe Integrations::Jenkins do let(:namespace) { create(:group, :private) } let(:project) { create(:project, :private, name: 'project', namespace: namespace) } let(:push_sample_data) { Gitlab::DataBuilder::Push.build_sample(project, user) } - let(:jenkins_service) { described_class.create!(jenkins_params) } + let(:jenkins_integration) { described_class.create!(jenkins_params) } before do stub_request(:post, jenkins_hook_url) end it 'invokes the Jenkins API' do - jenkins_service.execute(push_sample_data) + jenkins_integration.execute(push_sample_data) expect(a_request(:post, jenkins_hook_url)).to have_been_made.once end it 'adds default web hook headers to the request' do - jenkins_service.execute(push_sample_data) + jenkins_integration.execute(push_sample_data) expect( a_request(:post, jenkins_hook_url) @@ -174,7 +174,7 @@ RSpec.describe Integrations::Jenkins do end it 'request url contains properly serialized username and password' do - jenkins_service.execute(push_sample_data) + jenkins_integration.execute(push_sample_data) expect( a_request(:post, 'http://jenkins.example.com/project/my_project') @@ -187,8 +187,8 @@ RSpec.describe Integrations::Jenkins do let(:project) { create(:project) } context 'when a password was previously set' do - before do - @jenkins_service = described_class.create!( + let(:jenkins_integration) do + described_class.create!( project: project, properties: { jenkins_url: 'http://jenkins.example.com/', @@ -199,42 +199,47 @@ RSpec.describe Integrations::Jenkins do end it 'resets password if url changed' do - @jenkins_service.jenkins_url = 'http://jenkins-edited.example.com/' - @jenkins_service.save! - expect(@jenkins_service.password).to be_nil + jenkins_integration.jenkins_url = 'http://jenkins-edited.example.com/' + jenkins_integration.save! + + expect(jenkins_integration.password).to be_nil end it 'resets password if username is blank' do - @jenkins_service.username = '' - @jenkins_service.save! - expect(@jenkins_service.password).to be_nil + jenkins_integration.username = '' + jenkins_integration.save! + + expect(jenkins_integration.password).to be_nil end it 'does not reset password if username changed' do - @jenkins_service.username = 'some_name' - @jenkins_service.save! - expect(@jenkins_service.password).to eq('password') + jenkins_integration.username = 'some_name' + jenkins_integration.save! + + expect(jenkins_integration.password).to eq('password') end it 'does not reset password if new url is set together with password, even if it\'s the same password' do - @jenkins_service.jenkins_url = 'http://jenkins_edited.example.com/' - @jenkins_service.password = 'password' - @jenkins_service.save! - expect(@jenkins_service.password).to eq('password') - expect(@jenkins_service.jenkins_url).to eq('http://jenkins_edited.example.com/') + jenkins_integration.jenkins_url = 'http://jenkins_edited.example.com/' + jenkins_integration.password = 'password' + jenkins_integration.save! + + expect(jenkins_integration.password).to eq('password') + expect(jenkins_integration.jenkins_url).to eq('http://jenkins_edited.example.com/') end it 'resets password if url changed, even if setter called multiple times' do - @jenkins_service.jenkins_url = 'http://jenkins1.example.com/' - @jenkins_service.jenkins_url = 'http://jenkins1.example.com/' - @jenkins_service.save! - expect(@jenkins_service.password).to be_nil + jenkins_integration.jenkins_url = 'http://jenkins1.example.com/' + jenkins_integration.jenkins_url = 'http://jenkins1.example.com/' + jenkins_integration.save! + + expect(jenkins_integration.password).to be_nil end end context 'when no password was previously set' do - before do - @jenkins_service = described_class.create!( + let(:jenkins_integration) do + described_class.create!( project: create(:project), properties: { jenkins_url: 'http://jenkins.example.com/', @@ -244,11 +249,12 @@ RSpec.describe Integrations::Jenkins do end it 'saves password if new url is set together with password' do - @jenkins_service.jenkins_url = 'http://jenkins_edited.example.com/' - @jenkins_service.password = 'password' - @jenkins_service.save! - expect(@jenkins_service.password).to eq('password') - expect(@jenkins_service.jenkins_url).to eq('http://jenkins_edited.example.com/') + jenkins_integration.jenkins_url = 'http://jenkins_edited.example.com/' + jenkins_integration.password = 'password' + jenkins_integration.save! + + expect(jenkins_integration.password).to eq('password') + expect(jenkins_integration.jenkins_url).to eq('http://jenkins_edited.example.com/') end end end diff --git a/spec/models/integrations/jira_spec.rb b/spec/models/integrations/jira_spec.rb index 23ade570383..6ca72d68bbb 100644 --- a/spec/models/integrations/jira_spec.rb +++ b/spec/models/integrations/jira_spec.rb @@ -14,7 +14,7 @@ RSpec.describe Integrations::Jira do let(:password) { 'jira-password' } let(:transition_id) { 'test27' } let(:server_info_results) { { 'deploymentType' => 'Cloud' } } - let(:jira_service) do + let(:jira_integration) do described_class.new( project: project, url: url, @@ -100,20 +100,15 @@ RSpec.describe Integrations::Jira do end describe '#fields' do - let(:service) { create(:jira_service) } + let(:integration) { create(:jira_integration) } - subject(:fields) { service.fields } + subject(:fields) { integration.fields } it 'returns custom fields' do expect(fields.pluck(:name)).to eq(%w[url api_url username password]) end end - describe 'Associations' do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } - end - describe '.reference_pattern' do using RSpec::Parameterized::TableSyntax @@ -146,39 +141,35 @@ RSpec.describe Integrations::Jira do } end - subject { described_class.create!(params) } + subject(:integration) { described_class.create!(params) } it 'does not store data into properties' do - expect(subject.properties).to be_nil + expect(integration.properties).to be_nil end it 'stores data in data_fields correctly' do - service = subject - - expect(service.jira_tracker_data.url).to eq(url) - expect(service.jira_tracker_data.api_url).to eq(api_url) - expect(service.jira_tracker_data.username).to eq(username) - expect(service.jira_tracker_data.password).to eq(password) - expect(service.jira_tracker_data.jira_issue_transition_id).to eq(transition_id) - expect(service.jira_tracker_data.deployment_cloud?).to be_truthy + expect(integration.jira_tracker_data.url).to eq(url) + expect(integration.jira_tracker_data.api_url).to eq(api_url) + expect(integration.jira_tracker_data.username).to eq(username) + expect(integration.jira_tracker_data.password).to eq(password) + expect(integration.jira_tracker_data.jira_issue_transition_id).to eq(transition_id) + expect(integration.jira_tracker_data.deployment_cloud?).to be_truthy end context 'when loading serverInfo' do - let(:jira_service) { subject } - - context 'from a Cloud instance' do + context 'with a Cloud instance' do let(:server_info_results) { { 'deploymentType' => 'Cloud' } } it 'is detected' do - expect(jira_service.jira_tracker_data.deployment_cloud?).to be_truthy + expect(integration.jira_tracker_data).to be_deployment_cloud end end - context 'from a Server instance' do + context 'with a Server instance' do let(:server_info_results) { { 'deploymentType' => 'Server' } } it 'is detected' do - expect(jira_service.jira_tracker_data.deployment_server?).to be_truthy + expect(integration.jira_tracker_data).to be_deployment_server end end @@ -189,7 +180,7 @@ RSpec.describe Integrations::Jira do let(:api_url) { 'http://example-api.atlassian.net' } it 'deployment_type is set to cloud' do - expect(jira_service.jira_tracker_data.deployment_cloud?).to be_truthy + expect(integration.jira_tracker_data).to be_deployment_cloud end end @@ -197,7 +188,7 @@ RSpec.describe Integrations::Jira do let(:api_url) { 'http://my-jira-api.someserver.com' } it 'deployment_type is set to server' do - expect(jira_service.jira_tracker_data.deployment_server?).to be_truthy + expect(integration.jira_tracker_data).to be_deployment_server end end end @@ -210,7 +201,7 @@ RSpec.describe Integrations::Jira do it 'deployment_type is set to cloud' do expect(Gitlab::AppLogger).to receive(:warn).with(message: "Jira API returned no ServerInfo, setting deployment_type from URL", server_info: server_info_results, url: api_url) - expect(jira_service.jira_tracker_data.deployment_cloud?).to be_truthy + expect(integration.jira_tracker_data).to be_deployment_cloud end end @@ -219,7 +210,7 @@ RSpec.describe Integrations::Jira do it 'deployment_type is set to server' do expect(Gitlab::AppLogger).to receive(:warn).with(message: "Jira API returned no ServerInfo, setting deployment_type from URL", server_info: server_info_results, url: api_url) - expect(jira_service.jira_tracker_data.deployment_server?).to be_truthy + expect(integration.jira_tracker_data).to be_deployment_server end end end @@ -253,11 +244,11 @@ RSpec.describe Integrations::Jira do context 'reading data' do it 'reads data correctly' do - expect(service.url).to eq(url) - expect(service.api_url).to eq(api_url) - expect(service.username).to eq(username) - expect(service.password).to eq(password) - expect(service.jira_issue_transition_id).to eq(transition_id) + expect(integration.url).to eq(url) + expect(integration.api_url).to eq(api_url) + expect(integration.username).to eq(username) + expect(integration.password).to eq(password) + expect(integration.jira_issue_transition_id).to eq(transition_id) end end @@ -267,15 +258,11 @@ RSpec.describe Integrations::Jira do let_it_be(:new_url) { 'http://jira-new.example.com' } before do - service.update!(username: new_username, url: new_url) - end - - it 'leaves properties field emtpy' do - # expect(service.reload.properties).to be_empty + integration.update!(username: new_username, url: new_url) end it 'stores updated data in jira_tracker_data table' do - data = service.jira_tracker_data.reload + data = integration.jira_tracker_data.reload expect(data.url).to eq(new_url) expect(data.api_url).to eq(api_url) @@ -288,15 +275,15 @@ RSpec.describe Integrations::Jira do context 'when updating the url, api_url, username, or password' do context 'when updating the integration' do it 'updates deployment type' do - service.update!(url: 'http://first.url') - service.jira_tracker_data.update!(deployment_type: 'server') + integration.update!(url: 'http://first.url') + integration.jira_tracker_data.update!(deployment_type: 'server') - expect(service.jira_tracker_data.deployment_server?).to be_truthy + expect(integration.jira_tracker_data.deployment_server?).to be_truthy - service.update!(api_url: 'http://another.url') - service.jira_tracker_data.reload + integration.update!(api_url: 'http://another.url') + integration.jira_tracker_data.reload - expect(service.jira_tracker_data.deployment_cloud?).to be_truthy + expect(integration.jira_tracker_data.deployment_cloud?).to be_truthy expect(WebMock).to have_requested(:get, /serverInfo/).twice end end @@ -305,34 +292,34 @@ RSpec.describe Integrations::Jira do let(:server_info_results) { {} } it 'updates deployment type' do - service.update!(url: nil, api_url: nil, active: false) + integration.update!(url: nil, api_url: nil, active: false) - service.jira_tracker_data.reload + integration.jira_tracker_data.reload - expect(service.jira_tracker_data.deployment_unknown?).to be_truthy + expect(integration.jira_tracker_data.deployment_unknown?).to be_truthy end end it 'calls serverInfo for url' do - service.update!(url: 'http://first.url') + integration.update!(url: 'http://first.url') expect(WebMock).to have_requested(:get, /serverInfo/) end it 'calls serverInfo for api_url' do - service.update!(api_url: 'http://another.url') + integration.update!(api_url: 'http://another.url') expect(WebMock).to have_requested(:get, /serverInfo/) end it 'calls serverInfo for username' do - service.update!(username: 'test-user') + integration.update!(username: 'test-user') expect(WebMock).to have_requested(:get, /serverInfo/) end it 'calls serverInfo for password' do - service.update!(password: 'test-password') + integration.update!(password: 'test-password') expect(WebMock).to have_requested(:get, /serverInfo/) end @@ -340,7 +327,8 @@ RSpec.describe Integrations::Jira do context 'when not updating the url, api_url, username, or password' do it 'does not update deployment type' do - expect {service.update!(jira_issue_transition_id: 'jira_issue_transition_id')}.to raise_error(ActiveRecord::RecordInvalid) + expect { integration.update!(jira_issue_transition_id: 'jira_issue_transition_id') } + .to raise_error(ActiveRecord::RecordInvalid) expect(WebMock).not_to have_requested(:get, /serverInfo/) end @@ -348,9 +336,9 @@ RSpec.describe Integrations::Jira do context 'when not allowed to test an instance or group' do it 'does not update deployment type' do - allow(service).to receive(:can_test?).and_return(false) + allow(integration).to receive(:testable?).and_return(false) - service.update!(url: 'http://first.url') + integration.update!(url: 'http://first.url') expect(WebMock).not_to have_requested(:get, /serverInfo/) end @@ -368,68 +356,68 @@ RSpec.describe Integrations::Jira do end it 'resets password if url changed' do - service - service.url = 'http://jira_edited.example.com' - service.save! + integration + integration.url = 'http://jira_edited.example.com' + integration.save! - expect(service.reload.url).to eq('http://jira_edited.example.com') - expect(service.password).to be_nil + expect(integration.reload.url).to eq('http://jira_edited.example.com') + expect(integration.password).to be_nil end it 'does not reset password if url "changed" to the same url as before' do - service.url = 'http://jira.example.com' - service.save! + integration.url = 'http://jira.example.com' + integration.save! - expect(service.reload.url).to eq('http://jira.example.com') - expect(service.password).not_to be_nil + expect(integration.reload.url).to eq('http://jira.example.com') + expect(integration.password).not_to be_nil end it 'resets password if url not changed but api url added' do - service.api_url = 'http://jira_edited.example.com/rest/api/2' - service.save! + integration.api_url = 'http://jira_edited.example.com/rest/api/2' + integration.save! - expect(service.reload.api_url).to eq('http://jira_edited.example.com/rest/api/2') - expect(service.password).to be_nil + expect(integration.reload.api_url).to eq('http://jira_edited.example.com/rest/api/2') + expect(integration.password).to be_nil end it 'does not reset password if new url is set together with password, even if it\'s the same password' do - service.url = 'http://jira_edited.example.com' - service.password = password - service.save! + integration.url = 'http://jira_edited.example.com' + integration.password = password + integration.save! - expect(service.password).to eq(password) - expect(service.url).to eq('http://jira_edited.example.com') + expect(integration.password).to eq(password) + expect(integration.url).to eq('http://jira_edited.example.com') end it 'resets password if url changed, even if setter called multiple times' do - service.url = 'http://jira1.example.com/rest/api/2' - service.url = 'http://jira1.example.com/rest/api/2' - service.save! + integration.url = 'http://jira1.example.com/rest/api/2' + integration.url = 'http://jira1.example.com/rest/api/2' + integration.save! - expect(service.password).to be_nil + expect(integration.password).to be_nil end it 'does not reset password if username changed' do - service.username = 'some_name' - service.save! + integration.username = 'some_name' + integration.save! - expect(service.reload.password).to eq(password) + expect(integration.reload.password).to eq(password) end it 'does not reset password if password changed' do - service.url = 'http://jira_edited.example.com' - service.password = 'new_password' - service.save! + integration.url = 'http://jira_edited.example.com' + integration.password = 'new_password' + integration.save! - expect(service.reload.password).to eq('new_password') + expect(integration.reload.password).to eq('new_password') end it 'does not reset password if the password is touched and same as before' do - service.url = 'http://jira_edited.example.com' - service.password = password - service.save! + integration.url = 'http://jira_edited.example.com' + integration.password = password + integration.save! - expect(service.reload.password).to eq(password) + expect(integration.reload.password).to eq(password) end end @@ -443,23 +431,23 @@ RSpec.describe Integrations::Jira do end it 'resets password if api url changed' do - service.api_url = 'http://jira_edited.example.com/rest/api/2' - service.save! + integration.api_url = 'http://jira_edited.example.com/rest/api/2' + integration.save! - expect(service.password).to be_nil + expect(integration.password).to be_nil end it 'does not reset password if url changed' do - service.url = 'http://jira_edited.example.com' - service.save! + integration.url = 'http://jira_edited.example.com' + integration.save! - expect(service.password).to eq(password) + expect(integration.password).to eq(password) end it 'resets password if api url set to empty' do - service.update!(api_url: '') + integration.update!(api_url: '') - expect(service.reload.password).to be_nil + expect(integration.reload.password).to be_nil end end end @@ -472,11 +460,11 @@ RSpec.describe Integrations::Jira do end it 'saves password if new url is set together with password' do - service.url = 'http://jira_edited.example.com/rest/api/2' - service.password = 'password' - service.save! - expect(service.reload.password).to eq('password') - expect(service.reload.url).to eq('http://jira_edited.example.com/rest/api/2') + integration.url = 'http://jira_edited.example.com/rest/api/2' + integration.password = 'password' + integration.save! + expect(integration.reload.password).to eq('password') + expect(integration.reload.url).to eq('http://jira_edited.example.com/rest/api/2') end end end @@ -486,16 +474,16 @@ RSpec.describe Integrations::Jira do # this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404 context 'when data are stored in properties' do let(:properties) { data_params } - let!(:service) do - create(:jira_service, :without_properties_callback, properties: properties.merge(additional: 'something')) + let!(:integration) do + create(:jira_integration, :without_properties_callback, properties: properties.merge(additional: 'something')) end it_behaves_like 'handles jira fields' end context 'when data are stored in separated fields' do - let(:service) do - create(:jira_service, data_params.merge(properties: {})) + let(:integration) do + create(:jira_integration, data_params.merge(properties: {})) end it_behaves_like 'handles jira fields' @@ -503,8 +491,8 @@ RSpec.describe Integrations::Jira do context 'when data are stored in both properties and separated fields' do let(:properties) { data_params } - let(:service) do - create(:jira_service, :without_properties_callback, active: false, properties: properties).tap do |integration| + let(:integration) do + create(:jira_integration, :without_properties_callback, active: false, properties: properties).tap do |integration| create(:jira_tracker_data, data_params.merge(integration: integration)) end end @@ -522,7 +510,7 @@ RSpec.describe Integrations::Jira do end it 'call the Jira API to get the issue' do - jira_service.find_issue(issue_key) + jira_integration.find_issue(issue_key) expect(WebMock).to have_requested(:get, issue_url) end @@ -531,7 +519,7 @@ RSpec.describe Integrations::Jira do let(:issue_url) { "#{url}/rest/api/2/issue/#{issue_key}?expand=renderedFields,transitions" } it 'calls the Jira API with the options to get the issue' do - jira_service.find_issue(issue_key, rendered_fields: true, transitions: true) + jira_integration.find_issue(issue_key, rendered_fields: true, transitions: true) expect(WebMock).to have_requested(:get, issue_url) end @@ -558,16 +546,16 @@ RSpec.describe Integrations::Jira do end subject(:close_issue) do - jira_service.close_issue(resource, ExternalIssue.new(issue_key, project)) + jira_integration.close_issue(resource, ExternalIssue.new(issue_key, project)) end before do - jira_service.jira_issue_transition_id = '999' + jira_integration.jira_issue_transition_id = '999' # These stubs are needed to test Integrations::Jira#close_issue. # We close the issue then do another request to API to check if it got closed. # Here is stubbed the API return with a closed and an opened issues. - open_issue = JIRA::Resource::Issue.new(jira_service.client, attrs: issue_fields.deep_stringify_keys) + open_issue = JIRA::Resource::Issue.new(jira_integration.client, attrs: issue_fields.deep_stringify_keys) closed_issue = open_issue.dup allow(open_issue).to receive(:resolution).and_return(false) allow(closed_issue).to receive(:resolution).and_return(true) @@ -585,7 +573,7 @@ RSpec.describe Integrations::Jira do let(:external_issue) { ExternalIssue.new('JIRA-123', project) } def close_issue - jira_service.close_issue(resource, external_issue, current_user) + jira_integration.close_issue(resource, external_issue, current_user) end it 'calls Jira API' do @@ -636,7 +624,7 @@ RSpec.describe Integrations::Jira do context 'when "comment_on_event_enabled" is set to false' do it 'creates Remote Link reference but does not create comment' do - allow(jira_service).to receive_messages(comment_on_event_enabled: false) + allow(jira_integration).to receive_messages(comment_on_event_enabled: false) close_issue expect(WebMock).not_to have_requested(:post, comment_url) @@ -709,12 +697,12 @@ RSpec.describe Integrations::Jira do end it 'logs exception when transition id is not valid' do - allow(jira_service).to receive(:log_error) + allow(jira_integration).to receive(:log_error) WebMock.stub_request(:post, transitions_url).with(basic_auth: %w(jira-username jira-password)).and_raise("Bad Request") close_issue - expect(jira_service).to have_received(:log_error).with( + expect(jira_integration).to have_received(:log_error).with( "Issue transition failed", error: hash_including( exception_class: 'StandardError', @@ -734,7 +722,7 @@ RSpec.describe Integrations::Jira do context 'when custom transition IDs are blank' do before do - jira_service.jira_issue_transition_id = '' + jira_integration.jira_issue_transition_id = '' end it 'does not transition the issue' do @@ -755,7 +743,7 @@ RSpec.describe Integrations::Jira do end before do - jira_service.jira_issue_transition_automatic = true + jira_integration.jira_issue_transition_automatic = true close_issue end @@ -789,7 +777,7 @@ RSpec.describe Integrations::Jira do context 'when using multiple transition ids' do before do - allow(jira_service).to receive_messages(jira_issue_transition_id: '1,2,3') + allow(jira_integration).to receive_messages(jira_issue_transition_id: '1,2,3') end it 'calls the api with transition ids separated by comma' do @@ -805,7 +793,7 @@ RSpec.describe Integrations::Jira do end it 'calls the api with transition ids separated by semicolon' do - allow(jira_service).to receive_messages(jira_issue_transition_id: '1;2;3') + allow(jira_integration).to receive_messages(jira_issue_transition_id: '1;2;3') close_issue @@ -864,7 +852,7 @@ RSpec.describe Integrations::Jira do let(:jira_issue) { ExternalIssue.new('JIRA-123', project) } - subject { jira_service.create_cross_reference_note(jira_issue, resource, user) } + subject { jira_integration.create_cross_reference_note(jira_issue, resource, user) } shared_examples 'creates a comment on Jira' do let(:issue_url) { "#{url}/rest/api/2/issue/JIRA-123" } @@ -936,7 +924,7 @@ RSpec.describe Integrations::Jira do let(:server_info_results) { { 'url' => 'http://url', 'deploymentType' => 'Cloud' } } def server_info - jira_service.test(nil) + jira_integration.test(nil) end context 'when the test succeeds' do @@ -946,7 +934,7 @@ RSpec.describe Integrations::Jira do end it 'gets Jira project with API URL if set' do - jira_service.update!(api_url: 'http://jira.api.com') + jira_integration.update!(api_url: 'http://jira.api.com') expect(server_info).to eq(success: true, result: server_info_results) expect(WebMock).to have_requested(:get, /jira.api.com/) @@ -961,13 +949,13 @@ RSpec.describe Integrations::Jira do WebMock.stub_request(:get, test_url).with(basic_auth: [username, password]) .to_raise(JIRA::HTTPError.new(double(message: error_message))) - expect(jira_service).to receive(:log_error).with( + expect(jira_integration).to receive(:log_error).with( 'Error sending message', client_url: 'http://jira.example.com', error: error_message ) - expect(jira_service.test(nil)).to eq(success: false, result: error_message) + expect(jira_integration.test(nil)).to eq(success: false, result: error_message) end end end @@ -983,17 +971,17 @@ RSpec.describe Integrations::Jira do } allow(Gitlab.config).to receive(:issues_tracker).and_return(settings) - service = project.create_jira_service(active: true) + integration = project.create_jira_integration(active: true) - expect(service.url).to eq('http://jira.sample/projects/project_a') - expect(service.api_url).to eq('http://jira.sample/api') + expect(integration.url).to eq('http://jira.sample/projects/project_a') + expect(integration.api_url).to eq('http://jira.sample/api') end end it 'removes trailing slashes from url' do - service = described_class.new(url: 'http://jira.test.com/path/') + integration = described_class.new(url: 'http://jira.test.com/path/') - expect(service.url).to eq('http://jira.test.com/path') + expect(integration.url).to eq('http://jira.test.com/path') end end @@ -1093,19 +1081,65 @@ RSpec.describe Integrations::Jira do describe '#issue_transition_enabled?' do it 'returns true if automatic transitions are enabled' do - jira_service.jira_issue_transition_automatic = true + jira_integration.jira_issue_transition_automatic = true - expect(jira_service.issue_transition_enabled?).to be(true) + expect(jira_integration.issue_transition_enabled?).to be(true) end it 'returns true if custom transitions are set' do - jira_service.jira_issue_transition_id = '1, 2, 3' + jira_integration.jira_issue_transition_id = '1, 2, 3' - expect(jira_service.issue_transition_enabled?).to be(true) + expect(jira_integration.issue_transition_enabled?).to be(true) end it 'returns false if automatic and custom transitions are disabled' do - expect(jira_service.issue_transition_enabled?).to be(false) + expect(jira_integration.issue_transition_enabled?).to be(false) + end + end + + describe 'valid_connection? and configured?' do + before do + allow(jira_integration).to receive(:test).with(nil).and_return(test_result) + end + + context 'when the test fails' do + let(:test_result) { { success: false } } + + it 'is falsey' do + expect(jira_integration).not_to be_valid_connection + end + + it 'implies that configured? is also falsey' do + expect(jira_integration).not_to be_configured + end + end + + context 'when the test succeeds' do + let(:test_result) { { success: true } } + + it 'is truthy' do + expect(jira_integration).to be_valid_connection + end + + context 'when the integration is active' do + before do + jira_integration.active = true + end + + it 'implies that configured? is also truthy' do + expect(jira_integration).to be_configured + end + end + + context 'when the integration is inactive' do + before do + jira_integration.active = false + end + + it 'implies that configured? is falsey' do + expect(jira_integration).not_to be_configured + end + end end end end diff --git a/spec/models/integrations/mattermost_slash_commands_spec.rb b/spec/models/integrations/mattermost_slash_commands_spec.rb index c8a6584591c..b6abe00469b 100644 --- a/spec/models/integrations/mattermost_slash_commands_spec.rb +++ b/spec/models/integrations/mattermost_slash_commands_spec.rb @@ -5,27 +5,29 @@ require 'spec_helper' RSpec.describe Integrations::MattermostSlashCommands do it_behaves_like Integrations::BaseSlashCommands - context 'Mattermost API' do + describe 'Mattermost API' do let(:project) { create(:project) } - let(:service) { project.build_mattermost_slash_commands_service } + let(:integration) { project.build_mattermost_slash_commands_integration } let(:user) { create(:user) } before do session = ::Mattermost::Session.new(nil) session.base_uri = 'http://mattermost.example.com' - allow_any_instance_of(::Mattermost::Client).to receive(:with_session) - .and_yield(session) + allow(session).to receive(:with_session).and_yield(session) + allow(::Mattermost::Session).to receive(:new).and_return(session) end describe '#configure' do subject do - service.configure(user, team_id: 'abc', - trigger: 'gitlab', url: 'http://trigger.url', - icon_url: 'http://icon.url/icon.png') + integration.configure(user, + team_id: 'abc', + trigger: 'gitlab', + url: 'http://trigger.url', + icon_url: 'http://icon.url/icon.png') end - context 'the requests succeeds' do + context 'when the request succeeds' do before do stub_request(:post, 'http://mattermost.example.com/api/v4/commands') .with(body: { @@ -48,18 +50,18 @@ RSpec.describe Integrations::MattermostSlashCommands do ) end - it 'saves the service' do + it 'saves the integration' do expect { subject }.to change { project.integrations.count }.by(1) end it 'saves the token' do subject - expect(service.reload.token).to eq('token') + expect(integration.reload.token).to eq('token') end end - context 'an error is received' do + context 'when an error is received' do before do stub_request(:post, 'http://mattermost.example.com/api/v4/commands') .to_return( @@ -86,10 +88,10 @@ RSpec.describe Integrations::MattermostSlashCommands do describe '#list_teams' do subject do - service.list_teams(user) + integration.list_teams(user) end - context 'the requests succeeds' do + context 'when the request succeeds' do before do stub_request(:get, 'http://mattermost.example.com/api/v4/users/me/teams') .to_return( @@ -104,7 +106,7 @@ RSpec.describe Integrations::MattermostSlashCommands do end end - context 'an error is received' do + context 'when an error is received' do before do stub_request(:get, 'http://mattermost.example.com/api/v4/users/me/teams') .to_return( diff --git a/spec/models/integrations/microsoft_teams_spec.rb b/spec/models/integrations/microsoft_teams_spec.rb index 2f1be233eb2..21b9a005746 100644 --- a/spec/models/integrations/microsoft_teams_spec.rb +++ b/spec/models/integrations/microsoft_teams_spec.rb @@ -3,25 +3,20 @@ require 'spec_helper' RSpec.describe Integrations::MicrosoftTeams do - let(:chat_service) { described_class.new } + let(:chat_integration) { described_class.new } let(:webhook_url) { 'https://example.gitlab.com/' } - describe "Associations" do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } - end - describe 'Validations' do - context 'when service is active' do + context 'when integration is active' do before do subject.active = true end it { is_expected.to validate_presence_of(:webhook) } - it_behaves_like 'issue tracker service URL attribute', :webhook + it_behaves_like 'issue tracker integration URL attribute', :webhook end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end @@ -42,10 +37,9 @@ RSpec.describe Integrations::MicrosoftTeams do let_it_be(:project) { create(:project, :repository, :wiki_repo) } before do - allow(chat_service).to receive_messages( + allow(chat_integration).to receive_messages( project: project, project_id: project.id, - service_hook: true, webhook: webhook_url ) @@ -58,28 +52,29 @@ RSpec.describe Integrations::MicrosoftTeams do end it "calls Microsoft Teams API for push events" do - chat_service.execute(push_sample_data) + chat_integration.execute(push_sample_data) expect(WebMock).to have_requested(:post, webhook_url).once end it 'specifies the webhook when it is configured' do - expect(::MicrosoftTeams::Notifier).to receive(:new).with(webhook_url).and_return(double(:microsoft_teams_service).as_null_object) + integration = double(:microsoft_teams_integration).as_null_object + expect(::MicrosoftTeams::Notifier).to receive(:new).with(webhook_url).and_return(integration) - chat_service.execute(push_sample_data) + chat_integration.execute(push_sample_data) end end context 'with issue events' do let(:opts) { { title: 'Awesome issue', description: 'please fix' } } let(:issues_sample_data) do - service = Issues::CreateService.new(project: project, current_user: user, params: opts) + service = Issues::CreateService.new(project: project, current_user: user, params: opts, spam_params: nil) issue = service.execute service.hook_data(issue, 'open') end it "calls Microsoft Teams API" do - chat_service.execute(issues_sample_data) + chat_integration.execute(issues_sample_data) expect(WebMock).to have_requested(:post, webhook_url).once end @@ -106,7 +101,7 @@ RSpec.describe Integrations::MicrosoftTeams do end it "calls Microsoft Teams API" do - chat_service.execute(merge_sample_data) + chat_integration.execute(merge_sample_data) expect(WebMock).to have_requested(:post, webhook_url).once end @@ -126,7 +121,7 @@ RSpec.describe Integrations::MicrosoftTeams do let(:wiki_page_sample_data) { Gitlab::DataBuilder::WikiPage.build(wiki_page, user, 'create') } it "calls Microsoft Teams API" do - chat_service.execute(wiki_page_sample_data) + chat_integration.execute(wiki_page_sample_data) expect(WebMock).to have_requested(:post, webhook_url).once end @@ -138,10 +133,9 @@ RSpec.describe Integrations::MicrosoftTeams do let(:project) { create(:project, :repository, creator: user) } before do - allow(chat_service).to receive_messages( + allow(chat_integration).to receive_messages( project: project, project_id: project.id, - service_hook: true, webhook: webhook_url ) @@ -159,7 +153,7 @@ RSpec.describe Integrations::MicrosoftTeams do it "calls Microsoft Teams API for commit comment events" do data = Gitlab::DataBuilder::Note.build(commit_note, user) - chat_service.execute(data) + chat_integration.execute(data) expect(WebMock).to have_requested(:post, webhook_url).once end @@ -174,7 +168,7 @@ RSpec.describe Integrations::MicrosoftTeams do it "calls Microsoft Teams API for merge request comment events" do data = Gitlab::DataBuilder::Note.build(merge_request_note, user) - chat_service.execute(data) + chat_integration.execute(data) expect(WebMock).to have_requested(:post, webhook_url).once end @@ -188,7 +182,7 @@ RSpec.describe Integrations::MicrosoftTeams do it "calls Microsoft Teams API for issue comment events" do data = Gitlab::DataBuilder::Note.build(issue_note, user) - chat_service.execute(data) + chat_integration.execute(data) expect(WebMock).to have_requested(:post, webhook_url).once end @@ -203,7 +197,7 @@ RSpec.describe Integrations::MicrosoftTeams do it "calls Microsoft Teams API for snippet comment events" do data = Gitlab::DataBuilder::Note.build(snippet_note, user) - chat_service.execute(data) + chat_integration.execute(data) expect(WebMock).to have_requested(:post, webhook_url).once end @@ -221,9 +215,8 @@ RSpec.describe Integrations::MicrosoftTeams do end before do - allow(chat_service).to receive_messages( + allow(chat_integration).to receive_messages( project: project, - service_hook: true, webhook: webhook_url ) end @@ -231,14 +224,14 @@ RSpec.describe Integrations::MicrosoftTeams do shared_examples 'call Microsoft Teams API' do |branches_to_be_notified: nil| before do WebMock.stub_request(:post, webhook_url) - chat_service.branches_to_be_notified = branches_to_be_notified if branches_to_be_notified + chat_integration.branches_to_be_notified = branches_to_be_notified if branches_to_be_notified end it 'calls Microsoft Teams API for pipeline events' do data = Gitlab::DataBuilder::Pipeline.build(pipeline) data[:markdown] = true - chat_service.execute(data) + chat_integration.execute(data) message = Integrations::ChatMessage::PipelineMessage.new(data) @@ -250,11 +243,11 @@ RSpec.describe Integrations::MicrosoftTeams do shared_examples 'does not call Microsoft Teams API' do |branches_to_be_notified: nil| before do - chat_service.branches_to_be_notified = branches_to_be_notified if branches_to_be_notified + chat_integration.branches_to_be_notified = branches_to_be_notified if branches_to_be_notified end it 'does not call Microsoft Teams API for pipeline events' do data = Gitlab::DataBuilder::Pipeline.build(pipeline) - result = chat_service.execute(data) + result = chat_integration.execute(data) expect(result).to be_falsy end @@ -272,7 +265,7 @@ RSpec.describe Integrations::MicrosoftTeams do context 'with default to notify_only_broken_pipelines' do it 'does not call Microsoft Teams API for pipeline events' do data = Gitlab::DataBuilder::Pipeline.build(pipeline) - result = chat_service.execute(data) + result = chat_integration.execute(data) expect(result).to be_falsy end @@ -280,7 +273,7 @@ RSpec.describe Integrations::MicrosoftTeams do context 'with setting notify_only_broken_pipelines to false' do before do - chat_service.notify_only_broken_pipelines = false + chat_integration.notify_only_broken_pipelines = false end it_behaves_like 'call Microsoft Teams API' diff --git a/spec/models/integrations/open_project_spec.rb b/spec/models/integrations/open_project_spec.rb index e5b976dc91d..789911acae8 100644 --- a/spec/models/integrations/open_project_spec.rb +++ b/spec/models/integrations/open_project_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe Integrations::OpenProject do describe 'Validations' do - context 'when service is active' do + context 'when integration is active' do before do subject.active = true end @@ -13,11 +13,11 @@ RSpec.describe Integrations::OpenProject do it { is_expected.to validate_presence_of(:token) } it { is_expected.to validate_presence_of(:project_identifier_code) } - it_behaves_like 'issue tracker service URL attribute', :url - it_behaves_like 'issue tracker service URL attribute', :api_url + it_behaves_like 'issue tracker integration URL attribute', :url + it_behaves_like 'issue tracker integration URL attribute', :api_url end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end @@ -27,9 +27,4 @@ RSpec.describe Integrations::OpenProject do it { is_expected.not_to validate_presence_of(:project_identifier_code) } end end - - describe 'Associations' do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } - end end diff --git a/spec/models/integrations/packagist_spec.rb b/spec/models/integrations/packagist_spec.rb index 48f7e81adca..dce96890522 100644 --- a/spec/models/integrations/packagist_spec.rb +++ b/spec/models/integrations/packagist_spec.rb @@ -24,23 +24,23 @@ RSpec.describe Integrations::Packagist do let(:packagist_server) { 'https://packagist.example.com' } let(:project) { create(:project) } - describe "Associations" do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } + it_behaves_like Integrations::HasWebHook do + let(:integration) { described_class.new(packagist_params) } + let(:hook_url) { "#{packagist_server}/api/update-package?username=#{packagist_username}&apiToken=#{packagist_token}" } end describe '#execute' do let(:user) { create(:user) } let(:project) { create(:project, :repository) } let(:push_sample_data) { Gitlab::DataBuilder::Push.build_sample(project, user) } - let(:packagist_service) { described_class.create!(packagist_params) } + let(:packagist_integration) { described_class.create!(packagist_params) } before do stub_request(:post, packagist_hook_url) end it 'calls Packagist API' do - packagist_service.execute(push_sample_data) + packagist_integration.execute(push_sample_data) expect(a_request(:post, packagist_hook_url)).to have_been_made.once end diff --git a/spec/models/integrations/pipelines_email_spec.rb b/spec/models/integrations/pipelines_email_spec.rb index 90055b04bb8..761049f25fe 100644 --- a/spec/models/integrations/pipelines_email_spec.rb +++ b/spec/models/integrations/pipelines_email_spec.rb @@ -20,7 +20,7 @@ RSpec.describe Integrations::PipelinesEmail, :mailer do end describe 'Validations' do - context 'when service is active' do + context 'when integration is active' do before do subject.active = true end @@ -28,7 +28,7 @@ RSpec.describe Integrations::PipelinesEmail, :mailer do it { is_expected.to validate_presence_of(:recipients) } end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end diff --git a/spec/models/integrations/pivotaltracker_spec.rb b/spec/models/integrations/pivotaltracker_spec.rb index 2ce90b6f739..bf8458a376c 100644 --- a/spec/models/integrations/pivotaltracker_spec.rb +++ b/spec/models/integrations/pivotaltracker_spec.rb @@ -5,13 +5,8 @@ require 'spec_helper' RSpec.describe Integrations::Pivotaltracker do include StubRequests - describe 'Associations' do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } - end - describe 'Validations' do - context 'when service is active' do + context 'when integration is active' do before do subject.active = true end @@ -19,7 +14,7 @@ RSpec.describe Integrations::Pivotaltracker do it { is_expected.to validate_presence_of(:token) } end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end @@ -29,9 +24,9 @@ RSpec.describe Integrations::Pivotaltracker do end describe 'Execute' do - let(:service) do - described_class.new.tap do |service| - service.token = 'secret_api_token' + let(:integration) do + described_class.new.tap do |integration| + integration.token = 'secret_api_token' end end @@ -59,7 +54,7 @@ RSpec.describe Integrations::Pivotaltracker do end it 'posts correct message' do - service.execute(push_data) + integration.execute(push_data) expect(WebMock).to have_requested(:post, stubbed_hostname(url)).with( body: { 'source_commit' => { @@ -77,22 +72,22 @@ RSpec.describe Integrations::Pivotaltracker do end context 'when allowed branches is specified' do - let(:service) do - super().tap do |service| - service.restrict_to_branch = 'master,v10' + let(:integration) do + super().tap do |integration| + integration.restrict_to_branch = 'master,v10' end end it 'posts message if branch is in the list' do - service.execute(push_data(branch: 'master')) - service.execute(push_data(branch: 'v10')) + integration.execute(push_data(branch: 'master')) + integration.execute(push_data(branch: 'v10')) expect(WebMock).to have_requested(:post, stubbed_hostname(url)).twice end it 'does not post message if branch is not in the list' do - service.execute(push_data(branch: 'mas')) - service.execute(push_data(branch: 'v11')) + integration.execute(push_data(branch: 'mas')) + integration.execute(push_data(branch: 'v11')) expect(WebMock).not_to have_requested(:post, stubbed_hostname(url)) end diff --git a/spec/models/project_services/prometheus_service_spec.rb b/spec/models/integrations/prometheus_spec.rb index a2025388fab..f6f242bf58e 100644 --- a/spec/models/project_services/prometheus_service_spec.rb +++ b/spec/models/integrations/prometheus_spec.rb @@ -4,17 +4,13 @@ require 'spec_helper' require 'googleauth' -RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowplow do +RSpec.describe Integrations::Prometheus, :use_clean_rails_memory_store_caching, :snowplow do include PrometheusHelpers include ReactiveCachingHelpers let_it_be_with_reload(:project) { create(:prometheus_project) } - let(:service) { project.prometheus_service } - - describe "Associations" do - it { is_expected.to belong_to :project } - end + let(:integration) { project.prometheus_integration } context 'redirects' do it 'does not follow redirects' do @@ -22,7 +18,7 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl redirect_req_stub = stub_prometheus_request(prometheus_query_url('1'), status: 302, headers: { location: redirect_to }) redirected_req_stub = stub_prometheus_request(redirect_to, body: { 'status': 'success' }) - result = service.test + result = integration.test # result = { success: false, result: error } expect(result[:success]).to be_falsy @@ -36,22 +32,22 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl describe 'Validations' do context 'when manual_configuration is enabled' do before do - service.manual_configuration = true + integration.manual_configuration = true end it 'validates presence of api_url' do - expect(service).to validate_presence_of(:api_url) + expect(integration).to validate_presence_of(:api_url) end end context 'when manual configuration is disabled' do before do - service.manual_configuration = false + integration.manual_configuration = false end it 'does not validate presence of api_url' do - expect(service).not_to validate_presence_of(:api_url) - expect(service.valid?).to eq(true) + expect(integration).not_to validate_presence_of(:api_url) + expect(integration.valid?).to eq(true) end context 'local connections allowed' do @@ -60,23 +56,23 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl end it 'does not validate presence of api_url' do - expect(service).not_to validate_presence_of(:api_url) - expect(service.valid?).to eq(true) + expect(integration).not_to validate_presence_of(:api_url) + expect(integration.valid?).to eq(true) end end end context 'when the api_url domain points to localhost or local network' do - let(:domain) { Addressable::URI.parse(service.api_url).hostname } + let(:domain) { Addressable::URI.parse(integration.api_url).hostname } it 'cannot query' do - expect(service.can_query?).to be true + expect(integration.can_query?).to be true aggregate_failures do ['127.0.0.1', '192.168.2.3'].each do |url| allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([Addrinfo.tcp(url, 80)]) - expect(service.can_query?).to be false + expect(integration.can_query?).to be false end end end @@ -88,14 +84,14 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl ['127.0.0.1', '192.168.2.3'].each do |url| allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([Addrinfo.tcp(url, 80)]) - expect(service.can_query?).to be true + expect(integration.can_query?).to be true end end end context 'with self-monitoring project and internal Prometheus' do before do - service.api_url = 'http://localhost:9090' + integration.api_url = 'http://localhost:9090' stub_application_setting(self_monitoring_project_id: project.id) stub_config(prometheus: { enable: true, server_address: 'localhost:9090' }) @@ -106,19 +102,19 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl ['127.0.0.1', '192.168.2.3'].each do |url| allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([Addrinfo.tcp(url, 80)]) - expect(service.can_query?).to be true + expect(integration.can_query?).to be true end end end it 'does not allow self-monitoring project to connect to other local URLs' do - service.api_url = 'http://localhost:8000' + integration.api_url = 'http://localhost:8000' aggregate_failures do ['127.0.0.1', '192.168.2.3'].each do |url| allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([Addrinfo.tcp(url, 80)]) - expect(service.can_query?).to be false + expect(integration.can_query?).to be false end end end @@ -129,26 +125,26 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl describe 'callbacks' do context 'after_create' do let(:project) { create(:project) } - let(:service) { build(:prometheus_service, project: project) } + let(:integration) { build(:prometheus_integration, project: project) } - subject(:create_service) { service.save! } + subject(:create_integration) { integration.save! } it 'creates default alerts' do expect(Prometheus::CreateDefaultAlertsWorker) .to receive(:perform_async) .with(project.id) - create_service + create_integration end context 'no project exists' do - let(:service) { build(:prometheus_service, :instance) } + let(:integration) { build(:prometheus_integration, :instance) } it 'does not create default alerts' do expect(Prometheus::CreateDefaultAlertsWorker) .not_to receive(:perform_async) - create_service + create_integration end end end @@ -156,15 +152,15 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl describe '#test' do before do - service.manual_configuration = true + integration.manual_configuration = true end let!(:req_stub) { stub_prometheus_request(prometheus_query_url('1'), body: prometheus_value_body('vector')) } context 'success' do it 'reads the discovery endpoint' do - expect(service.test[:result]).to eq('Checked API endpoint') - expect(service.test[:success]).to be_truthy + expect(integration.test[:result]).to eq('Checked API endpoint') + expect(integration.test[:success]).to be_truthy expect(req_stub).to have_been_requested.twice end end @@ -173,7 +169,7 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl let!(:req_stub) { stub_prometheus_request(prometheus_query_url('1'), status: 404) } it 'fails to read the discovery endpoint' do - expect(service.test[:success]).to be_falsy + expect(integration.test[:success]).to be_falsy expect(req_stub).to have_been_requested end end @@ -183,20 +179,20 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl let(:api_url) { 'http://some_url' } before do - service.active = true - service.api_url = api_url - service.manual_configuration = manual_configuration + integration.active = true + integration.api_url = api_url + integration.manual_configuration = manual_configuration end context 'manual configuration is enabled' do let(:manual_configuration) { true } it 'calls valid?' do - allow(service).to receive(:valid?).and_call_original + allow(integration).to receive(:valid?).and_call_original - expect(service.prometheus_client).not_to be_nil + expect(integration.prometheus_client).not_to be_nil - expect(service).to have_received(:valid?) + expect(integration).to have_received(:valid?) end end @@ -204,7 +200,7 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl let(:manual_configuration) { false } it 'no client provided' do - expect(service.prometheus_client).to be_nil + expect(integration.prometheus_client).to be_nil end end @@ -219,8 +215,8 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl end it 'allows local requests' do - expect(service.prometheus_client).not_to be_nil - expect { service.prometheus_client.ping }.not_to raise_error + expect(integration.prometheus_client).not_to be_nil + expect { integration.prometheus_client.ping }.not_to raise_error end end @@ -235,7 +231,7 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl end it 'blocks local requests' do - expect(service.prometheus_client).to be_nil + expect(integration.prometheus_client).to be_nil end context 'with self monitoring project and internal Prometheus URL' do @@ -250,8 +246,8 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl end it 'allows local requests' do - expect(service.prometheus_client).not_to be_nil - expect { service.prometheus_client.ping }.not_to raise_error + expect(integration.prometheus_client).not_to be_nil + expect { integration.prometheus_client.ping }.not_to raise_error end end end @@ -278,8 +274,8 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl end def stub_iap_request - service.google_iap_service_account_json = Gitlab::Json.generate(google_iap_service_account) - service.google_iap_audience_client_id = 'IAP_CLIENT_ID.apps.googleusercontent.com' + integration.google_iap_service_account_json = Gitlab::Json.generate(google_iap_service_account) + integration.google_iap_audience_client_id = 'IAP_CLIENT_ID.apps.googleusercontent.com' stub_request(:post, 'https://oauth2.googleapis.com/token') .to_return( @@ -292,9 +288,9 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl it 'includes the authorization header' do stub_iap_request - expect(service.prometheus_client).not_to be_nil - expect(service.prometheus_client.send(:options)).to have_key(:headers) - expect(service.prometheus_client.send(:options)[:headers]).to eq(authorization: "Bearer FOO") + expect(integration.prometheus_client).not_to be_nil + expect(integration.prometheus_client.send(:options)).to have_key(:headers) + expect(integration.prometheus_client.send(:options)[:headers]).to eq(authorization: "Bearer FOO") end context 'when passed with token_credential_uri', issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/284819' do @@ -315,7 +311,7 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl stub_iap_request stub_request(:any, malicious_host).to_raise('Making additional HTTP requests is forbidden!') - expect(service.prometheus_client).not_to be_nil + expect(integration.prometheus_client).not_to be_nil end end end @@ -332,7 +328,7 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl let(:cluster) { create(:cluster, projects: [project]) } it 'returns true' do - expect(service.prometheus_available?).to be(true) + expect(integration.prometheus_available?).to be(true) end end @@ -343,16 +339,16 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl let(:cluster) { create(:cluster_for_group, groups: [group]) } it 'returns true' do - expect(service.prometheus_available?).to be(true) + expect(integration.prometheus_available?).to be(true) end it 'avoids N+1 queries' do - service + integration 5.times do |i| other_cluster = create(:cluster_for_group, groups: [group], environment_scope: i) create(:clusters_integrations_prometheus, cluster: other_cluster) end - expect { service.prometheus_available? }.not_to exceed_query_limit(1) + expect { integration.prometheus_available? }.not_to exceed_query_limit(1) end end @@ -360,7 +356,7 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl let(:cluster) { create(:cluster, :instance) } it 'returns true' do - expect(service.prometheus_available?).to be(true) + expect(integration.prometheus_available?).to be(true) end end end @@ -370,7 +366,7 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl let!(:prometheus) { create(:clusters_integrations_prometheus, :disabled, cluster: cluster) } it 'returns false' do - expect(service.prometheus_available?).to be(false) + expect(integration.prometheus_available?).to be(false) end end @@ -378,78 +374,78 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl let(:cluster) { create(:cluster, projects: [project]) } it 'returns false' do - expect(service.prometheus_available?).to be(false) + expect(integration.prometheus_available?).to be(false) end end context 'no clusters' do it 'returns false' do - expect(service.prometheus_available?).to be(false) + expect(integration.prometheus_available?).to be(false) end end end describe '#synchronize_service_state before_save callback' do context 'no clusters with prometheus are installed' do - context 'when service is inactive' do + context 'when integration is inactive' do before do - service.active = false + integration.active = false end - it 'activates service when manual_configuration is enabled' do - expect { service.update!(manual_configuration: true) }.to change { service.active }.from(false).to(true) + it 'activates integration when manual_configuration is enabled' do + expect { integration.update!(manual_configuration: true) }.to change { integration.active }.from(false).to(true) end - it 'keeps service inactive when manual_configuration is disabled' do - expect { service.update!(manual_configuration: false) }.not_to change { service.active }.from(false) + it 'keeps integration inactive when manual_configuration is disabled' do + expect { integration.update!(manual_configuration: false) }.not_to change { integration.active }.from(false) end end - context 'when service is active' do + context 'when integration is active' do before do - service.active = true + integration.active = true end - it 'keeps the service active when manual_configuration is enabled' do - expect { service.update!(manual_configuration: true) }.not_to change { service.active }.from(true) + it 'keeps the integration active when manual_configuration is enabled' do + expect { integration.update!(manual_configuration: true) }.not_to change { integration.active }.from(true) end - it 'inactivates the service when manual_configuration is disabled' do - expect { service.update!(manual_configuration: false) }.to change { service.active }.from(true).to(false) + it 'inactivates the integration when manual_configuration is disabled' do + expect { integration.update!(manual_configuration: false) }.to change { integration.active }.from(true).to(false) end end end context 'with prometheus installed in the cluster' do before do - allow(service).to receive(:prometheus_available?).and_return(true) + allow(integration).to receive(:prometheus_available?).and_return(true) end - context 'when service is inactive' do + context 'when integration is inactive' do before do - service.active = false + integration.active = false end - it 'activates service when manual_configuration is enabled' do - expect { service.update!(manual_configuration: true) }.to change { service.active }.from(false).to(true) + it 'activates integration when manual_configuration is enabled' do + expect { integration.update!(manual_configuration: true) }.to change { integration.active }.from(false).to(true) end - it 'activates service when manual_configuration is disabled' do - expect { service.update!(manual_configuration: false) }.to change { service.active }.from(false).to(true) + it 'activates integration when manual_configuration is disabled' do + expect { integration.update!(manual_configuration: false) }.to change { integration.active }.from(false).to(true) end end - context 'when service is active' do + context 'when integration is active' do before do - service.active = true + integration.active = true end - it 'keeps service active when manual_configuration is enabled' do - expect { service.update!(manual_configuration: true) }.not_to change { service.active }.from(true) + it 'keeps integration active when manual_configuration is enabled' do + expect { integration.update!(manual_configuration: true) }.not_to change { integration.active }.from(true) end - it 'keeps service active when manual_configuration is disabled' do - expect { service.update!(manual_configuration: false) }.not_to change { service.active }.from(true) + it 'keeps integration active when manual_configuration is disabled' do + expect { integration.update!(manual_configuration: false) }.not_to change { integration.active }.from(true) end end end @@ -457,20 +453,20 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl describe '#track_events after_commit callback' do before do - allow(service).to receive(:prometheus_available?).and_return(true) + allow(integration).to receive(:prometheus_available?).and_return(true) end context "enabling manual_configuration" do it "tracks enable event" do - service.update!(manual_configuration: false) - service.update!(manual_configuration: true) + integration.update!(manual_configuration: false) + integration.update!(manual_configuration: true) expect_snowplow_event(category: 'cluster:services:prometheus', action: 'enabled_manual_prometheus') end it "tracks disable event" do - service.update!(manual_configuration: true) - service.update!(manual_configuration: false) + integration.update!(manual_configuration: true) + integration.update!(manual_configuration: false) expect_snowplow_event(category: 'cluster:services:prometheus', action: 'disabled_manual_prometheus') end @@ -479,20 +475,20 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl describe '#editable?' do it 'is editable' do - expect(service.editable?).to be(true) + expect(integration.editable?).to be(true) end context 'when cluster exists with prometheus enabled' do let(:cluster) { create(:cluster, projects: [project]) } before do - service.update!(manual_configuration: false) + integration.update!(manual_configuration: false) create(:clusters_integrations_prometheus, cluster: cluster) end it 'remains editable' do - expect(service.editable?).to be(true) + expect(integration.editable?).to be(true) end end end @@ -536,7 +532,7 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl end it 'returns fields' do - expect(service.fields).to eq(expected_fields) + expect(integration.fields).to eq(expected_fields) end end end diff --git a/spec/models/integrations/pushover_spec.rb b/spec/models/integrations/pushover_spec.rb index be8dc5634a0..716a00c5bcf 100644 --- a/spec/models/integrations/pushover_spec.rb +++ b/spec/models/integrations/pushover_spec.rb @@ -5,13 +5,8 @@ require 'spec_helper' RSpec.describe Integrations::Pushover do include StubRequests - describe 'Associations' do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } - end - describe 'Validations' do - context 'when service is active' do + context 'when integration is active' do before do subject.active = true end @@ -21,7 +16,7 @@ RSpec.describe Integrations::Pushover do it { is_expected.to validate_presence_of(:priority) } end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end @@ -51,7 +46,6 @@ RSpec.describe Integrations::Pushover do allow(pushover).to receive_messages( project: project, project_id: project.id, - service_hook: true, api_key: api_key, user_key: user_key, device: device, diff --git a/spec/models/integrations/redmine_spec.rb b/spec/models/integrations/redmine_spec.rb index 083585d4fed..59997d2b6f6 100644 --- a/spec/models/integrations/redmine_spec.rb +++ b/spec/models/integrations/redmine_spec.rb @@ -3,11 +3,6 @@ require 'spec_helper' RSpec.describe Integrations::Redmine do - describe 'Associations' do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } - end - describe 'Validations' do # if redmine is set in setting the urls are set to defaults # therefore the validation passes as the values are not nil @@ -18,7 +13,7 @@ RSpec.describe Integrations::Redmine do allow(Gitlab.config).to receive(:issues_tracker).and_return(settings) end - context 'when service is active' do + context 'when integration is active' do before do subject.active = true end @@ -27,12 +22,12 @@ RSpec.describe Integrations::Redmine do it { is_expected.to validate_presence_of(:issues_url) } it { is_expected.to validate_presence_of(:new_issue_url) } - it_behaves_like 'issue tracker service URL attribute', :project_url - it_behaves_like 'issue tracker service URL attribute', :issues_url - it_behaves_like 'issue tracker service URL attribute', :new_issue_url + it_behaves_like 'issue tracker integration URL attribute', :project_url + it_behaves_like 'issue tracker integration URL attribute', :issues_url + it_behaves_like 'issue tracker integration URL attribute', :new_issue_url end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end diff --git a/spec/models/integrations/slack_slash_commands_spec.rb b/spec/models/integrations/slack_slash_commands_spec.rb index a9d3c820a3c..ff89d2c6a40 100644 --- a/spec/models/integrations/slack_slash_commands_spec.rb +++ b/spec/models/integrations/slack_slash_commands_spec.rb @@ -18,8 +18,8 @@ RSpec.describe Integrations::SlackSlashCommands do } end - let(:service) do - project.create_slack_slash_commands_service( + let(:integration) do + project.create_slack_slash_commands_integration( properties: { token: 'token' }, active: true ) @@ -30,11 +30,11 @@ RSpec.describe Integrations::SlackSlashCommands do end before do - allow(service).to receive(:authorize_chat_name_url).and_return(authorize_url) + allow(integration).to receive(:authorize_chat_name_url).and_return(authorize_url) end it 'uses slack compatible links' do - response = service.trigger(params) + response = integration.trigger(params) expect(response[:text]).to include("<#{authorize_url}|connect your GitLab account>") end diff --git a/spec/models/integrations/slack_spec.rb b/spec/models/integrations/slack_spec.rb index e598c528967..4661d9c8291 100644 --- a/spec/models/integrations/slack_spec.rb +++ b/spec/models/integrations/slack_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Integrations::Slack do stub_request(:post, "https://slack.service.url/") end - let_it_be(:slack_service) { create(:slack_service, branches_to_be_notified: 'all') } + let_it_be(:slack_integration) { create(:integrations_slack, branches_to_be_notified: 'all') } it 'uses only known events', :aggregate_failures do described_class::SUPPORTED_EVENTS_FOR_USAGE_LOG.each do |action| @@ -26,7 +26,7 @@ RSpec.describe Integrations::Slack do it 'increases the usage data counter' do expect(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event).with(event_name, values: user.id).and_call_original - slack_service.execute(data) + slack_integration.execute(data) end end @@ -38,7 +38,7 @@ RSpec.describe Integrations::Slack do it 'does not increase the usage data counter' do expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event).with('i_ecosystem_slack_service_pipeline_notification', values: user.id) - slack_service.execute(data) + slack_integration.execute(data) end end @@ -126,7 +126,7 @@ RSpec.describe Integrations::Slack do it 'does not increase the usage data counter' do expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event) - slack_service.execute(data) + slack_integration.execute(data) end end end diff --git a/spec/models/integrations/teamcity_spec.rb b/spec/models/integrations/teamcity_spec.rb index b88a4722ad4..d425357aef0 100644 --- a/spec/models/integrations/teamcity_spec.rb +++ b/spec/models/integrations/teamcity_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching do let(:teamcity_full_url) { 'http://gitlab.com/teamcity/httpAuth/app/rest/builds/branch:unspecified:any,revision:123' } let(:project) { create(:project) } - subject(:service) do + subject(:integration) do described_class.create!( project: project, properties: { @@ -22,20 +22,15 @@ RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching do ) end - describe 'Associations' do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } - end - describe 'Validations' do - context 'when service is active' do + context 'when integration is active' do before do subject.active = true end it { is_expected.to validate_presence_of(:build_type) } it { is_expected.to validate_presence_of(:teamcity_url) } - it_behaves_like 'issue tracker service URL attribute', :teamcity_url + it_behaves_like 'issue tracker integration URL attribute', :teamcity_url describe '#username' do it 'does not validate the presence of username if password is nil' do @@ -66,7 +61,7 @@ RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching do end end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end @@ -79,71 +74,66 @@ RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching do end describe 'Callbacks' do + let(:teamcity_integration) { integration } + describe 'before_update :reset_password' do context 'when a password was previously set' do it 'resets password if url changed' do - teamcity_service = service + teamcity_integration.teamcity_url = 'http://gitlab1.com' + teamcity_integration.save! - teamcity_service.teamcity_url = 'http://gitlab1.com' - teamcity_service.save! - - expect(teamcity_service.password).to be_nil + expect(teamcity_integration.password).to be_nil end it 'does not reset password if username changed' do - teamcity_service = service - - teamcity_service.username = 'some_name' - teamcity_service.save! + teamcity_integration.username = 'some_name' + teamcity_integration.save! - expect(teamcity_service.password).to eq('password') + expect(teamcity_integration.password).to eq('password') end it "does not reset password if new url is set together with password, even if it's the same password" do - teamcity_service = service - - teamcity_service.teamcity_url = 'http://gitlab_edited.com' - teamcity_service.password = 'password' - teamcity_service.save! + teamcity_integration.teamcity_url = 'http://gitlab_edited.com' + teamcity_integration.password = 'password' + teamcity_integration.save! - expect(teamcity_service.password).to eq('password') - expect(teamcity_service.teamcity_url).to eq('http://gitlab_edited.com') + expect(teamcity_integration.password).to eq('password') + expect(teamcity_integration.teamcity_url).to eq('http://gitlab_edited.com') end end it 'saves password if new url is set together with password when no password was previously set' do - teamcity_service = service - teamcity_service.password = nil + teamcity_integration.password = nil - teamcity_service.teamcity_url = 'http://gitlab_edited.com' - teamcity_service.password = 'password' - teamcity_service.save! + teamcity_integration.teamcity_url = 'http://gitlab_edited.com' + teamcity_integration.password = 'password' + teamcity_integration.save! - expect(teamcity_service.password).to eq('password') - expect(teamcity_service.teamcity_url).to eq('http://gitlab_edited.com') + expect(teamcity_integration.password).to eq('password') + expect(teamcity_integration.teamcity_url).to eq('http://gitlab_edited.com') end end end describe '#build_page' do it 'returns the contents of the reactive cache' do - stub_reactive_cache(service, { build_page: 'foo' }, 'sha', 'ref') + stub_reactive_cache(integration, { build_page: 'foo' }, 'sha', 'ref') - expect(service.build_page('sha', 'ref')).to eq('foo') + expect(integration.build_page('sha', 'ref')).to eq('foo') end end describe '#commit_status' do it 'returns the contents of the reactive cache' do - stub_reactive_cache(service, { commit_status: 'foo' }, 'sha', 'ref') + stub_reactive_cache(integration, { commit_status: 'foo' }, 'sha', 'ref') - expect(service.commit_status('sha', 'ref')).to eq('foo') + expect(integration.commit_status('sha', 'ref')).to eq('foo') end end describe '#calculate_reactive_cache' do context 'build_page' do - subject { service.calculate_reactive_cache('123', 'unused')[:build_page] } + subject { integration.calculate_reactive_cache('123', 'unused')[:build_page] } it 'returns a specific URL when status is 500' do stub_request(status: 500) @@ -179,7 +169,7 @@ RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching do end context 'commit_status' do - subject { service.calculate_reactive_cache('123', 'unused')[:commit_status] } + subject { integration.calculate_reactive_cache('123', 'unused')[:commit_status] } it 'sets commit status to :error when status is 500' do stub_request(status: 500) @@ -243,25 +233,25 @@ RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching do it 'handles push request correctly' do stub_post_to_build_queue(branch: 'dev-123_branch') - expect(service.execute(data)).to include('Ok') + expect(integration.execute(data)).to include('Ok') end it 'returns nil when ref is blank' do data[:after] = Gitlab::Git::BLANK_SHA - expect(service.execute(data)).to be_nil + expect(integration.execute(data)).to be_nil end it 'returns nil when there is no content' do data[:total_commits_count] = 0 - expect(service.execute(data)).to be_nil + expect(integration.execute(data)).to be_nil end it 'returns nil when a merge request is opened for the same ref' do create(:merge_request, source_project: project, source_branch: 'dev-123_branch') - expect(service.execute(data)).to be_nil + expect(integration.execute(data)).to be_nil end end @@ -283,26 +273,26 @@ RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching do it 'handles merge request correctly' do stub_post_to_build_queue(branch: 'dev-123_branch') - expect(service.execute(data)).to include('Ok') + expect(integration.execute(data)).to include('Ok') end it 'returns nil when merge request is not opened' do data[:object_attributes][:state] = 'closed' - expect(service.execute(data)).to be_nil + expect(integration.execute(data)).to be_nil end it 'returns nil unless merge request is marked as unchecked' do data[:object_attributes][:merge_status] = 'can_be_merged' - expect(service.execute(data)).to be_nil + expect(integration.execute(data)).to be_nil end end it 'returns nil when event is not supported' do data = { object_kind: 'foo' } - expect(service.execute(data)).to be_nil + expect(integration.execute(data)).to be_nil end end diff --git a/spec/models/integrations/youtrack_spec.rb b/spec/models/integrations/youtrack_spec.rb index 314204f6fb4..f6a9dd8ef37 100644 --- a/spec/models/integrations/youtrack_spec.rb +++ b/spec/models/integrations/youtrack_spec.rb @@ -3,13 +3,8 @@ require 'spec_helper' RSpec.describe Integrations::Youtrack do - describe 'Associations' do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } - end - describe 'Validations' do - context 'when service is active' do + context 'when integration is active' do before do subject.active = true end @@ -17,11 +12,11 @@ RSpec.describe Integrations::Youtrack do it { is_expected.to validate_presence_of(:project_url) } it { is_expected.to validate_presence_of(:issues_url) } - it_behaves_like 'issue tracker service URL attribute', :project_url - it_behaves_like 'issue tracker service URL attribute', :issues_url + it_behaves_like 'issue tracker integration URL attribute', :project_url + it_behaves_like 'issue tracker integration URL attribute', :issues_url end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end diff --git a/spec/models/internal_id_spec.rb b/spec/models/internal_id_spec.rb index 390d1552c16..696b5b48cbf 100644 --- a/spec/models/internal_id_spec.rb +++ b/spec/models/internal_id_spec.rb @@ -39,216 +39,217 @@ RSpec.describe InternalId do end end - describe '.generate_next' do - subject { described_class.generate_next(id_subject, scope, usage, init) } + shared_examples_for 'a monotonically increasing id generator' do + describe '.generate_next' do + subject { described_class.generate_next(id_subject, scope, usage, init) } - context 'in the absence of a record' do - it 'creates a record if not yet present' do - expect { subject }.to change { described_class.count }.from(0).to(1) - end + context 'in the absence of a record' do + it 'creates a record if not yet present' do + expect { subject }.to change { described_class.count }.from(0).to(1) + end - it 'stores record attributes' do - subject + it 'stores record attributes' do + subject - described_class.first.tap do |record| - expect(record.project).to eq(project) - expect(record.usage).to eq(usage.to_s) + described_class.first.tap do |record| + expect(record.project).to eq(project) + expect(record.usage).to eq(usage.to_s) + end end - end - context 'with existing issues' do - before do - create_list(:issue, 2, project: project) - described_class.delete_all - end + context 'with existing issues' do + before do + create_list(:issue, 2, project: project) + described_class.delete_all + end - it 'calculates last_value values automatically' do - expect(subject).to eq(project.issues.size + 1) + it 'calculates last_value values automatically' do + expect(subject).to eq(project.issues.size + 1) + end end end - context 'with concurrent inserts on table' do - it 'looks up the record if it was created concurrently' do - args = { **scope, usage: described_class.usages[usage.to_s] } - record = double - expect(described_class).to receive(:find_by).with(args).and_return(nil) # first call, record not present - expect(described_class).to receive(:find_by).with(args).and_return(record) # second call, record was created by another process - expect(described_class).to receive(:create!).and_raise(ActiveRecord::RecordNotUnique, 'record not unique') - expect(record).to receive(:increment_and_save!) - - subject + it 'generates a strictly monotone, gapless sequence' do + seq = Array.new(10).map do + described_class.generate_next(issue, scope, usage, init) end - end - end + normalized = seq.map { |i| i - seq.min } - it 'generates a strictly monotone, gapless sequence' do - seq = Array.new(10).map do - described_class.generate_next(issue, scope, usage, init) + expect(normalized).to eq((0..seq.size - 1).to_a) end - normalized = seq.map { |i| i - seq.min } - - expect(normalized).to eq((0..seq.size - 1).to_a) - end - context 'there are no instances to pass in' do - let(:id_subject) { Issue } + context 'there are no instances to pass in' do + let(:id_subject) { Issue } - it 'accepts classes instead' do - expect(subject).to eq(1) + it 'accepts classes instead' do + expect(subject).to eq(1) + end end - end - context 'when executed outside of transaction' do - it 'increments counter with in_transaction: "false"' do - allow(ActiveRecord::Base.connection).to receive(:transaction_open?) { false } + context 'when executed outside of transaction' do + it 'increments counter with in_transaction: "false"' do + allow(ActiveRecord::Base.connection).to receive(:transaction_open?) { false } - expect(InternalId::InternalIdGenerator.internal_id_transactions_total).to receive(:increment) - .with(operation: :generate, usage: 'issues', in_transaction: 'false').and_call_original + expect(InternalId.internal_id_transactions_total).to receive(:increment) + .with(operation: :generate, usage: 'issues', in_transaction: 'false').and_call_original - subject + subject + end end - end - context 'when executed within transaction' do - it 'increments counter with in_transaction: "true"' do - expect(InternalId::InternalIdGenerator.internal_id_transactions_total).to receive(:increment) - .with(operation: :generate, usage: 'issues', in_transaction: 'true').and_call_original + context 'when executed within transaction' do + it 'increments counter with in_transaction: "true"' do + expect(InternalId.internal_id_transactions_total).to receive(:increment) + .with(operation: :generate, usage: 'issues', in_transaction: 'true').and_call_original - InternalId.transaction { subject } + InternalId.transaction { subject } + end end end - end - describe '.reset' do - subject { described_class.reset(issue, scope, usage, value) } + describe '.reset' do + subject { described_class.reset(issue, scope, usage, value) } - context 'in the absence of a record' do - let(:value) { 2 } + context 'in the absence of a record' do + let(:value) { 2 } - it 'does not revert back the value' do - expect { subject }.not_to change { described_class.count } - expect(subject).to be_falsey + it 'does not revert back the value' do + expect { subject }.not_to change { described_class.count } + expect(subject).to be_falsey + end end - end - context 'when valid iid is used to reset' do - let!(:value) { generate_next } + context 'when valid iid is used to reset' do + let!(:value) { generate_next } - context 'and iid is a latest one' do - it 'does rewind and next generated value is the same' do - expect(subject).to be_truthy - expect(generate_next).to eq(value) + context 'and iid is a latest one' do + it 'does rewind and next generated value is the same' do + expect(subject).to be_truthy + expect(generate_next).to eq(value) + end end - end - context 'and iid is not a latest one' do - it 'does not rewind' do - generate_next + context 'and iid is not a latest one' do + it 'does not rewind' do + generate_next - expect(subject).to be_falsey - expect(generate_next).to be > value + expect(subject).to be_falsey + expect(generate_next).to be > value + end end - end - def generate_next - described_class.generate_next(issue, scope, usage, init) + def generate_next + described_class.generate_next(issue, scope, usage, init) + end end - end - context 'when executed outside of transaction' do - let(:value) { 2 } + context 'when executed outside of transaction' do + let(:value) { 2 } - it 'increments counter with in_transaction: "false"' do - allow(ActiveRecord::Base.connection).to receive(:transaction_open?) { false } + it 'increments counter with in_transaction: "false"' do + allow(ActiveRecord::Base.connection).to receive(:transaction_open?) { false } - expect(InternalId::InternalIdGenerator.internal_id_transactions_total).to receive(:increment) - .with(operation: :reset, usage: 'issues', in_transaction: 'false').and_call_original + expect(InternalId.internal_id_transactions_total).to receive(:increment) + .with(operation: :reset, usage: 'issues', in_transaction: 'false').and_call_original - subject + subject + end end - end - context 'when executed within transaction' do - let(:value) { 2 } + context 'when executed within transaction' do + let(:value) { 2 } - it 'increments counter with in_transaction: "true"' do - expect(InternalId::InternalIdGenerator.internal_id_transactions_total).to receive(:increment) - .with(operation: :reset, usage: 'issues', in_transaction: 'true').and_call_original + it 'increments counter with in_transaction: "true"' do + expect(InternalId.internal_id_transactions_total).to receive(:increment) + .with(operation: :reset, usage: 'issues', in_transaction: 'true').and_call_original - InternalId.transaction { subject } + InternalId.transaction { subject } + end end end - end - describe '.track_greatest' do - let(:value) { 9001 } + describe '.track_greatest' do + let(:value) { 9001 } - subject { described_class.track_greatest(id_subject, scope, usage, value, init) } + subject { described_class.track_greatest(id_subject, scope, usage, value, init) } - context 'in the absence of a record' do - it 'creates a record if not yet present' do - expect { subject }.to change { described_class.count }.from(0).to(1) + context 'in the absence of a record' do + it 'creates a record if not yet present' do + expect { subject }.to change { described_class.count }.from(0).to(1) + end end - end - it 'stores record attributes' do - subject + it 'stores record attributes' do + subject - described_class.first.tap do |record| - expect(record.project).to eq(project) - expect(record.usage).to eq(usage.to_s) - expect(record.last_value).to eq(value) + described_class.first.tap do |record| + expect(record.project).to eq(project) + expect(record.usage).to eq(usage.to_s) + expect(record.last_value).to eq(value) + end end - end - context 'with existing issues' do - before do - create(:issue, project: project) - described_class.delete_all - end + context 'with existing issues' do + before do + create(:issue, project: project) + described_class.delete_all + end - it 'still returns the last value to that of the given value' do - expect(subject).to eq(value) + it 'still returns the last value to that of the given value' do + expect(subject).to eq(value) + end end - end - context 'when value is less than the current last_value' do - it 'returns the current last_value' do - described_class.create!(**scope, usage: usage, last_value: 10_001) + context 'when value is less than the current last_value' do + it 'returns the current last_value' do + described_class.create!(**scope, usage: usage, last_value: 10_001) - expect(subject).to eq 10_001 + expect(subject).to eq 10_001 + end end - end - context 'there are no instances to pass in' do - let(:id_subject) { Issue } + context 'there are no instances to pass in' do + let(:id_subject) { Issue } - it 'accepts classes instead' do - expect(subject).to eq(value) + it 'accepts classes instead' do + expect(subject).to eq(value) + end end - end - context 'when executed outside of transaction' do - it 'increments counter with in_transaction: "false"' do - allow(ActiveRecord::Base.connection).to receive(:transaction_open?) { false } + context 'when executed outside of transaction' do + it 'increments counter with in_transaction: "false"' do + allow(ActiveRecord::Base.connection).to receive(:transaction_open?) { false } - expect(InternalId::InternalIdGenerator.internal_id_transactions_total).to receive(:increment) - .with(operation: :track_greatest, usage: 'issues', in_transaction: 'false').and_call_original + expect(InternalId.internal_id_transactions_total).to receive(:increment) + .with(operation: :track_greatest, usage: 'issues', in_transaction: 'false').and_call_original - subject + subject + end end - end - context 'when executed within transaction' do - it 'increments counter with in_transaction: "true"' do - expect(InternalId::InternalIdGenerator.internal_id_transactions_total).to receive(:increment) - .with(operation: :track_greatest, usage: 'issues', in_transaction: 'true').and_call_original + context 'when executed within transaction' do + it 'increments counter with in_transaction: "true"' do + expect(InternalId.internal_id_transactions_total).to receive(:increment) + .with(operation: :track_greatest, usage: 'issues', in_transaction: 'true').and_call_original - InternalId.transaction { subject } + InternalId.transaction { subject } + end end end end + context 'when the feature flag is disabled' do + stub_feature_flags(generate_iids_without_explicit_locking: false) + + it_behaves_like 'a monotonically increasing id generator' + end + + context 'when the feature flag is enabled' do + stub_feature_flags(generate_iids_without_explicit_locking: true) + + it_behaves_like 'a monotonically increasing id generator' + end + describe '#increment_and_save!' do let(:id) { create(:internal_id) } diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index edb93ecf4b6..441446bae60 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -128,6 +128,24 @@ RSpec.describe Issue do end end + context 'order by upvotes' do + let!(:issue) { create(:issue) } + let!(:issue2) { create(:issue) } + let!(:award_emoji) { create(:award_emoji, :upvote, awardable: issue2) } + + describe '.order_upvotes_desc' do + it 'orders on upvotes' do + expect(described_class.order_upvotes_desc.to_a).to eq [issue2, issue] + end + end + + describe '.order_upvotes_asc' do + it 'orders on upvotes' do + expect(described_class.order_upvotes_asc.to_a).to eq [issue, issue2] + end + end + end + describe '.with_alert_management_alerts' do subject { described_class.with_alert_management_alerts } @@ -1051,23 +1069,53 @@ RSpec.describe Issue do describe '#check_for_spam?' do using RSpec::Parameterized::TableSyntax - - where(:visibility_level, :confidential, :new_attributes, :check_for_spam?) do - Gitlab::VisibilityLevel::PUBLIC | false | { description: 'woo' } | true - Gitlab::VisibilityLevel::PUBLIC | false | { title: 'woo' } | true - Gitlab::VisibilityLevel::PUBLIC | true | { confidential: false } | true - Gitlab::VisibilityLevel::PUBLIC | true | { description: 'woo' } | false - Gitlab::VisibilityLevel::PUBLIC | false | { title: 'woo', confidential: true } | false - Gitlab::VisibilityLevel::PUBLIC | false | { description: 'original description' } | false - Gitlab::VisibilityLevel::INTERNAL | false | { description: 'woo' } | false - Gitlab::VisibilityLevel::PRIVATE | false | { description: 'woo' } | false + let_it_be(:support_bot) { ::User.support_bot } + + where(:support_bot?, :visibility_level, :confidential, :new_attributes, :check_for_spam?) do + ### non-support-bot cases + # spammable attributes changing + false | Gitlab::VisibilityLevel::PUBLIC | false | { description: 'new' } | true + false | Gitlab::VisibilityLevel::PUBLIC | false | { title: 'new' } | true + # confidential to non-confidential + false | Gitlab::VisibilityLevel::PUBLIC | true | { confidential: false } | true + # non-confidential to confidential + false | Gitlab::VisibilityLevel::PUBLIC | false | { confidential: true } | false + # spammable attributes changing on confidential + false | Gitlab::VisibilityLevel::PUBLIC | true | { description: 'new' } | false + # spammable attributes changing while changing to confidential + false | Gitlab::VisibilityLevel::PUBLIC | false | { title: 'new', confidential: true } | false + # spammable attribute not changing + false | Gitlab::VisibilityLevel::PUBLIC | false | { description: 'original description' } | false + # non-spammable attribute changing + false | Gitlab::VisibilityLevel::PUBLIC | false | { weight: 3 } | false + # spammable attributes changing on non-public + false | Gitlab::VisibilityLevel::INTERNAL | false | { description: 'new' } | false + false | Gitlab::VisibilityLevel::PRIVATE | false | { description: 'new' } | false + + ### support-bot cases + # confidential to non-confidential + true | Gitlab::VisibilityLevel::PUBLIC | true | { confidential: false } | true + # non-confidential to confidential + true | Gitlab::VisibilityLevel::PUBLIC | false | { confidential: true } | false + # spammable attributes changing on confidential + true | Gitlab::VisibilityLevel::PUBLIC | true | { description: 'new' } | true + # spammable attributes changing while changing to confidential + true | Gitlab::VisibilityLevel::PUBLIC | false | { title: 'new', confidential: true } | true + # spammable attributes changing on non-public + true | Gitlab::VisibilityLevel::INTERNAL | false | { description: 'new' } | true + true | Gitlab::VisibilityLevel::PRIVATE | false | { title: 'new' } | true + # spammable attribute not changing + true | Gitlab::VisibilityLevel::PUBLIC | false | { description: 'original description' } | false + # non-spammable attribute changing + true | Gitlab::VisibilityLevel::PRIVATE | true | { weight: 3 } | false end with_them do - it 'checks for spam on issues that can be seen anonymously' do + it 'checks for spam when necessary' do + author = support_bot? ? support_bot : user project = reusable_project project.update!(visibility_level: visibility_level) - issue = create(:issue, project: project, confidential: confidential, description: 'original description') + issue = create(:issue, project: project, confidential: confidential, description: 'original description', author: author) issue.assign_attributes(new_attributes) diff --git a/spec/models/label_note_spec.rb b/spec/models/label_note_spec.rb index 0bf202ce2b1..ee4822c653d 100644 --- a/spec/models/label_note_spec.rb +++ b/spec/models/label_note_spec.rb @@ -7,6 +7,7 @@ RSpec.describe LabelNote do let_it_be(:user) { create(:user) } let_it_be(:label) { create(:label, project: project) } let_it_be(:label2) { create(:label, project: project) } + let(:resource_parent) { project } context 'when resource is issue' do diff --git a/spec/models/lfs_file_lock_spec.rb b/spec/models/lfs_file_lock_spec.rb index d3f79c7c7cf..5afad6c184f 100644 --- a/spec/models/lfs_file_lock_spec.rb +++ b/spec/models/lfs_file_lock_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe LfsFileLock do let_it_be(:lfs_file_lock, reload: true) { create(:lfs_file_lock) } + subject { lfs_file_lock } it { is_expected.to belong_to(:project) } diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 372fc40afcc..5824c2085ce 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -30,6 +30,7 @@ RSpec.describe Member do context "when an invite email is provided" do let_it_be(:project) { create(:project) } + let(:member) { build(:project_member, source: project, invite_email: "user@example.com", user: nil) } it "doesn't require a user" do @@ -98,6 +99,7 @@ RSpec.describe Member do context 'project bots' do let_it_be(:project_bot) { create(:user, :project_bot) } + let(:new_member) { build(:project_member, user_id: project_bot.id) } context 'not a member of any group or project' do @@ -476,6 +478,20 @@ RSpec.describe Member do it { is_expected.to include @blocked_maintainer } it { is_expected.to include @blocked_developer } it { is_expected.to include @member_with_minimal_access } + + context 'with where conditions' do + let_it_be(:example_member) { create(:group_member, invite_email: 'user@example.com') } + + subject do + described_class + .default_scoped + .where(invite_email: 'user@example.com') + .distinct_on_user_with_max_access_level + .to_a + end + + it { is_expected.to eq [example_member] } + end end end @@ -494,282 +510,6 @@ RSpec.describe Member do end end - describe '.add_user' do - %w[project group].each do |source_type| - context "when source is a #{source_type}" do - let_it_be(:source, reload: true) { create(source_type, :public) } - let_it_be(:user) { create(:user) } - let_it_be(:admin) { create(:admin) } - - it 'returns a <Source>Member object' do - member = described_class.add_user(source, user, :maintainer) - - expect(member).to be_a "#{source_type.classify}Member".constantize - expect(member).to be_persisted - end - - context 'when admin mode is enabled', :enable_admin_mode do - it 'sets members.created_by to the given admin current_user' do - member = described_class.add_user(source, user, :maintainer, current_user: admin) - - expect(member.created_by).to eq(admin) - end - end - - context 'when admin mode is disabled' do - it 'rejects setting members.created_by to the given admin current_user' do - member = described_class.add_user(source, user, :maintainer, current_user: admin) - - expect(member.created_by).to be_nil - end - end - - it 'sets members.expires_at to the given expires_at' do - member = described_class.add_user(source, user, :maintainer, expires_at: Date.new(2016, 9, 22)) - - expect(member.expires_at).to eq(Date.new(2016, 9, 22)) - end - - described_class.access_levels.each do |sym_key, int_access_level| - it "accepts the :#{sym_key} symbol as access level" do - expect(source.users).not_to include(user) - - member = described_class.add_user(source, user.id, sym_key) - - expect(member.access_level).to eq(int_access_level) - expect(source.users.reload).to include(user) - end - - it "accepts the #{int_access_level} integer as access level" do - expect(source.users).not_to include(user) - - member = described_class.add_user(source, user.id, int_access_level) - - expect(member.access_level).to eq(int_access_level) - expect(source.users.reload).to include(user) - end - end - - context 'with no current_user' do - context 'when called with a known user id' do - it 'adds the user as a member' do - expect(source.users).not_to include(user) - - described_class.add_user(source, user.id, :maintainer) - - expect(source.users.reload).to include(user) - end - end - - context 'when called with an unknown user id' do - it 'adds the user as a member' do - expect(source.users).not_to include(user) - - described_class.add_user(source, non_existing_record_id, :maintainer) - - expect(source.users.reload).not_to include(user) - end - end - - context 'when called with a user object' do - it 'adds the user as a member' do - expect(source.users).not_to include(user) - - described_class.add_user(source, user, :maintainer) - - expect(source.users.reload).to include(user) - end - end - - context 'when called with a requester user object' do - before do - source.request_access(user) - end - - it 'adds the requester as a member' do - expect(source.users).not_to include(user) - expect(source.requesters.exists?(user_id: user)).to be_truthy - - expect { described_class.add_user(source, user, :maintainer) } - .to raise_error(Gitlab::Access::AccessDeniedError) - - expect(source.users.reload).not_to include(user) - expect(source.requesters.reload.exists?(user_id: user)).to be_truthy - end - end - - context 'when called with a known user email' do - it 'adds the user as a member' do - expect(source.users).not_to include(user) - - described_class.add_user(source, user.email, :maintainer) - - expect(source.users.reload).to include(user) - end - end - - context 'when called with a known user secondary email' do - let(:secondary_email) { create(:email, email: 'secondary@example.com', user: user) } - - it 'adds the user as a member' do - expect(source.users).not_to include(user) - - described_class.add_user(source, secondary_email.email, :maintainer) - - expect(source.users.reload).to include(user) - end - end - - context 'when called with an unknown user email' do - it 'creates an invited member' do - expect(source.users).not_to include(user) - - described_class.add_user(source, 'user@example.com', :maintainer) - - expect(source.members.invite.pluck(:invite_email)).to include('user@example.com') - end - end - - context 'when called with an unknown user email starting with a number' do - it 'creates an invited member', :aggregate_failures do - email_starting_with_number = "#{user.id}_email@example.com" - - described_class.add_user(source, email_starting_with_number, :maintainer) - - expect(source.members.invite.pluck(:invite_email)).to include(email_starting_with_number) - expect(source.users.reload).not_to include(user) - end - end - end - - context 'when current_user can update member', :enable_admin_mode do - it 'creates the member' do - expect(source.users).not_to include(user) - - described_class.add_user(source, user, :maintainer, current_user: admin) - - expect(source.users.reload).to include(user) - end - - context 'when called with a requester user object' do - before do - source.request_access(user) - end - - it 'adds the requester as a member' do - expect(source.users).not_to include(user) - expect(source.requesters.exists?(user_id: user)).to be_truthy - - described_class.add_user(source, user, :maintainer, current_user: admin) - - expect(source.users.reload).to include(user) - expect(source.requesters.reload.exists?(user_id: user)).to be_falsy - end - end - end - - context 'when current_user cannot update member' do - it 'does not create the member' do - expect(source.users).not_to include(user) - - member = described_class.add_user(source, user, :maintainer, current_user: user) - - expect(source.users.reload).not_to include(user) - expect(member).not_to be_persisted - end - - context 'when called with a requester user object' do - before do - source.request_access(user) - end - - it 'does not destroy the requester' do - expect(source.users).not_to include(user) - expect(source.requesters.exists?(user_id: user)).to be_truthy - - described_class.add_user(source, user, :maintainer, current_user: user) - - expect(source.users.reload).not_to include(user) - expect(source.requesters.exists?(user_id: user)).to be_truthy - end - end - end - - context 'when member already exists' do - before do - source.add_user(user, :developer) - end - - context 'with no current_user' do - it 'updates the member' do - expect(source.users).to include(user) - - described_class.add_user(source, user, :maintainer) - - expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::MAINTAINER) - end - end - - context 'when current_user can update member', :enable_admin_mode do - it 'updates the member' do - expect(source.users).to include(user) - - described_class.add_user(source, user, :maintainer, current_user: admin) - - expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::MAINTAINER) - end - end - - context 'when current_user cannot update member' do - it 'does not update the member' do - expect(source.users).to include(user) - - described_class.add_user(source, user, :maintainer, current_user: user) - - expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::DEVELOPER) - end - end - end - end - end - end - - describe '.add_users' do - %w[project group].each do |source_type| - context "when source is a #{source_type}" do - let_it_be(:source) { create(source_type, :public) } - let_it_be(:admin) { create(:admin) } - let_it_be(:user1) { create(:user) } - let_it_be(:user2) { create(:user) } - - it 'returns a <Source>Member objects' do - members = described_class.add_users(source, [user1, user2], :maintainer) - - expect(members).to be_a Array - expect(members.size).to eq(2) - expect(members.first).to be_a "#{source_type.classify}Member".constantize - expect(members.first).to be_persisted - end - - it 'returns an empty array' do - members = described_class.add_users(source, [], :maintainer) - - expect(members).to be_a Array - expect(members).to be_empty - end - - it 'supports differents formats' do - list = ['joe@local.test', admin, user1.id, user2.id.to_s] - - members = described_class.add_users(source, list, :maintainer) - - expect(members.size).to eq(4) - expect(members.first).to be_invite - end - end - end - end - describe '#accept_request' do let(:member) { create(:project_member, requested_at: Time.current.utc) } @@ -966,7 +706,8 @@ RSpec.describe Member do end context 'when after_commit :update_highest_role' do - let!(:user) { create(:user) } + let_it_be(:user) { create(:user) } + let(:user_id) { user.id } where(:member_type, :source_type) do @@ -1001,7 +742,7 @@ RSpec.describe Member do end describe 'destroy member' do - subject { member.destroy! } + subject { member.reload.destroy! } include_examples 'update highest role with exclusive lease' end diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb index 8c942228059..472f4280d26 100644 --- a/spec/models/members/group_member_spec.rb +++ b/spec/models/members/group_member_spec.rb @@ -47,27 +47,6 @@ RSpec.describe GroupMember do end end - describe '.access_levels' do - it 'returns Gitlab::Access.options_with_owner' do - expect(described_class.access_levels).to eq(Gitlab::Access.sym_options_with_owner) - end - end - - describe '.add_users' do - it 'adds the given users to the given group' do - group = create(:group) - users = create_list(:user, 2) - - described_class.add_users( - group, - [users.first.id, users.second], - described_class::MAINTAINER - ) - - expect(group.users).to include(users.first, users.second) - end - end - it_behaves_like 'members notifications', :group describe '#namespace_id' do diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index b84b408cb4b..4c59bda856f 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -23,19 +23,6 @@ RSpec.describe ProjectMember do end end - describe '.add_user' do - it 'adds the user as a member' do - user = create(:user) - project = create(:project) - - expect(project.users).not_to include(user) - - described_class.add_user(project, user, :maintainer, current_user: project.owner) - - expect(project.users.reload).to include(user) - end - end - describe '#real_source_type' do subject { create(:project_member).real_source_type } diff --git a/spec/models/merge_request/cleanup_schedule_spec.rb b/spec/models/merge_request/cleanup_schedule_spec.rb index 925d287088b..85208f901fd 100644 --- a/spec/models/merge_request/cleanup_schedule_spec.rb +++ b/spec/models/merge_request/cleanup_schedule_spec.rb @@ -11,22 +11,125 @@ RSpec.describe MergeRequest::CleanupSchedule do it { is_expected.to validate_presence_of(:scheduled_at) } end - describe '.scheduled_merge_request_ids' do - let_it_be(:mr_cleanup_schedule_1) { create(:merge_request_cleanup_schedule, scheduled_at: 2.days.ago) } - let_it_be(:mr_cleanup_schedule_2) { create(:merge_request_cleanup_schedule, scheduled_at: 1.day.ago) } - let_it_be(:mr_cleanup_schedule_3) { create(:merge_request_cleanup_schedule, scheduled_at: 1.day.ago, completed_at: Time.current) } - let_it_be(:mr_cleanup_schedule_4) { create(:merge_request_cleanup_schedule, scheduled_at: 4.days.ago) } - let_it_be(:mr_cleanup_schedule_5) { create(:merge_request_cleanup_schedule, scheduled_at: 3.days.ago) } - let_it_be(:mr_cleanup_schedule_6) { create(:merge_request_cleanup_schedule, scheduled_at: 1.day.from_now) } - let_it_be(:mr_cleanup_schedule_7) { create(:merge_request_cleanup_schedule, scheduled_at: 5.days.ago) } - - it 'only includes incomplete schedule within the specified limit' do - expect(described_class.scheduled_merge_request_ids(4)).to eq([ - mr_cleanup_schedule_2.merge_request_id, - mr_cleanup_schedule_1.merge_request_id, - mr_cleanup_schedule_5.merge_request_id, - mr_cleanup_schedule_4.merge_request_id + describe 'state machine transitions' do + let(:cleanup_schedule) { create(:merge_request_cleanup_schedule) } + + it 'sets status to unstarted by default' do + expect(cleanup_schedule).to be_unstarted + end + + describe '#run' do + it 'sets the status to running' do + cleanup_schedule.run + + expect(cleanup_schedule.reload).to be_running + end + + context 'when previous status is not unstarted' do + let(:cleanup_schedule) { create(:merge_request_cleanup_schedule, :running) } + + it 'does not change status' do + expect { cleanup_schedule.run }.not_to change(cleanup_schedule, :status) + end + end + end + + describe '#retry' do + let(:cleanup_schedule) { create(:merge_request_cleanup_schedule, :running) } + + it 'sets the status to unstarted' do + cleanup_schedule.retry + + expect(cleanup_schedule.reload).to be_unstarted + end + + it 'increments failed_count' do + expect { cleanup_schedule.retry }.to change(cleanup_schedule, :failed_count).by(1) + end + + context 'when previous status is not running' do + let(:cleanup_schedule) { create(:merge_request_cleanup_schedule) } + + it 'does not change status' do + expect { cleanup_schedule.retry }.not_to change(cleanup_schedule, :status) + end + end + end + + describe '#complete' do + let(:cleanup_schedule) { create(:merge_request_cleanup_schedule, :running) } + + it 'sets the status to completed' do + cleanup_schedule.complete + + expect(cleanup_schedule.reload).to be_completed + end + + it 'sets the completed_at' do + expect { cleanup_schedule.complete }.to change(cleanup_schedule, :completed_at) + end + + context 'when previous status is not running' do + let(:cleanup_schedule) { create(:merge_request_cleanup_schedule, :completed) } + + it 'does not change status' do + expect { cleanup_schedule.complete }.not_to change(cleanup_schedule, :status) + end + end + end + + describe '#mark_as_failed' do + let(:cleanup_schedule) { create(:merge_request_cleanup_schedule, :running) } + + it 'sets the status to failed' do + cleanup_schedule.mark_as_failed + + expect(cleanup_schedule.reload).to be_failed + end + + it 'increments failed_count' do + expect { cleanup_schedule.mark_as_failed }.to change(cleanup_schedule, :failed_count).by(1) + end + + context 'when previous status is not running' do + let(:cleanup_schedule) { create(:merge_request_cleanup_schedule, :failed) } + + it 'does not change status' do + expect { cleanup_schedule.mark_as_failed }.not_to change(cleanup_schedule, :status) + end + end + end + end + + describe '.scheduled_and_unstarted' do + let!(:cleanup_schedule_1) { create(:merge_request_cleanup_schedule, scheduled_at: 2.days.ago) } + let!(:cleanup_schedule_2) { create(:merge_request_cleanup_schedule, scheduled_at: 1.day.ago) } + let!(:cleanup_schedule_3) { create(:merge_request_cleanup_schedule, :completed, scheduled_at: 1.day.ago) } + let!(:cleanup_schedule_4) { create(:merge_request_cleanup_schedule, scheduled_at: 4.days.ago) } + let!(:cleanup_schedule_5) { create(:merge_request_cleanup_schedule, scheduled_at: 3.days.ago) } + let!(:cleanup_schedule_6) { create(:merge_request_cleanup_schedule, scheduled_at: 1.day.from_now) } + let!(:cleanup_schedule_7) { create(:merge_request_cleanup_schedule, :failed, scheduled_at: 5.days.ago) } + + it 'returns records that are scheduled before or on current time and unstarted (ordered by scheduled first)' do + expect(described_class.scheduled_and_unstarted).to eq([ + cleanup_schedule_2, + cleanup_schedule_1, + cleanup_schedule_5, + cleanup_schedule_4 ]) end end + + describe '.start_next' do + let!(:cleanup_schedule_1) { create(:merge_request_cleanup_schedule, :completed, scheduled_at: 1.day.ago) } + let!(:cleanup_schedule_2) { create(:merge_request_cleanup_schedule, scheduled_at: 2.days.ago) } + let!(:cleanup_schedule_3) { create(:merge_request_cleanup_schedule, :running, scheduled_at: 1.day.ago) } + let!(:cleanup_schedule_4) { create(:merge_request_cleanup_schedule, scheduled_at: 3.days.ago) } + let!(:cleanup_schedule_5) { create(:merge_request_cleanup_schedule, :failed, scheduled_at: 3.days.ago) } + + it 'finds the next scheduled and unstarted then marked it as running' do + expect(described_class.start_next).to eq(cleanup_schedule_2) + expect(cleanup_schedule_2.reload).to be_running + end + end end diff --git a/spec/models/merge_request/diff_commit_user_spec.rb b/spec/models/merge_request/diff_commit_user_spec.rb new file mode 100644 index 00000000000..08e073568f9 --- /dev/null +++ b/spec/models/merge_request/diff_commit_user_spec.rb @@ -0,0 +1,127 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe MergeRequest::DiffCommitUser do + describe 'validations' do + it 'requires that names are less than 512 characters long' do + expect(described_class.new(name: 'a' * 1000)).not_to be_valid + end + + it 'requires that Emails are less than 512 characters long' do + expect(described_class.new(email: 'a' * 1000)).not_to be_valid + end + + it 'requires either a name or Email' do + expect(described_class.new).not_to be_valid + end + + it 'allows setting of just a name' do + expect(described_class.new(name: 'Alice')).to be_valid + end + + it 'allows setting of just an Email' do + expect(described_class.new(email: 'alice@example.com')).to be_valid + end + + it 'allows setting of both a name and Email' do + expect(described_class.new(name: 'Alice', email: 'alice@example.com')) + .to be_valid + end + end + + describe '.prepare' do + it 'trims a value to at most 512 characters' do + expect(described_class.prepare('€' * 1_000)).to eq('€' * 512) + end + + it 'returns nil if the value is an empty string' do + expect(described_class.prepare('')).to be_nil + end + end + + describe '.find_or_create' do + it 'creates a new row if none exist' do + alice = described_class.find_or_create('Alice', 'alice@example.com') + + expect(alice.name).to eq('Alice') + expect(alice.email).to eq('alice@example.com') + end + + it 'returns an existing row if one exists' do + user1 = create(:merge_request_diff_commit_user) + user2 = described_class.find_or_create(user1.name, user1.email) + + expect(user1).to eq(user2) + end + + it 'handles concurrent inserts' do + user = create(:merge_request_diff_commit_user) + + expect(described_class) + .to receive(:find_or_create_by!) + .ordered + .with(name: user.name, email: user.email) + .and_raise(ActiveRecord::RecordNotUnique) + + expect(described_class) + .to receive(:find_or_create_by!) + .ordered + .with(name: user.name, email: user.email) + .and_return(user) + + expect(described_class.find_or_create(user.name, user.email)).to eq(user) + end + end + + describe '.bulk_find_or_create' do + it 'bulk creates missing rows and reuses existing rows' do + bob = create( + :merge_request_diff_commit_user, + name: 'Bob', + email: 'bob@example.com' + ) + + users = described_class.bulk_find_or_create( + [%w[Alice alice@example.com], %w[Bob bob@example.com]] + ) + alice = described_class.find_by(name: 'Alice') + + expect(users[%w[Alice alice@example.com]]).to eq(alice) + expect(users[%w[Bob bob@example.com]]).to eq(bob) + end + + it 'does not insert any data when all users exist' do + bob = create( + :merge_request_diff_commit_user, + name: 'Bob', + email: 'bob@example.com' + ) + + users = described_class.bulk_find_or_create([%w[Bob bob@example.com]]) + + expect(described_class).not_to receive(:insert_all) + expect(users[%w[Bob bob@example.com]]).to eq(bob) + end + + it 'handles concurrently inserted rows' do + bob = create( + :merge_request_diff_commit_user, + name: 'Bob', + email: 'bob@example.com' + ) + + input = [%w[Bob bob@example.com]] + + expect(described_class) + .to receive(:bulk_find) + .twice + .with(input) + .and_return([], [bob]) + + users = described_class.bulk_find_or_create(input) + + expect(users[%w[Bob bob@example.com]]).to eq(bob) + end + end +end diff --git a/spec/models/merge_request_diff_commit_spec.rb b/spec/models/merge_request_diff_commit_spec.rb index a24628b0f9d..6290468d4a7 100644 --- a/spec/models/merge_request_diff_commit_spec.rb +++ b/spec/models/merge_request_diff_commit_spec.rb @@ -16,6 +16,11 @@ RSpec.describe MergeRequestDiffCommit do let(:invalid_items_for_bulk_insertion) { [] } # class does not have any validations defined end + describe 'associations' do + it { is_expected.to belong_to(:commit_author) } + it { is_expected.to belong_to(:committer) } + end + describe '#to_hash' do subject { merge_request.commits.first } @@ -46,6 +51,8 @@ RSpec.describe MergeRequestDiffCommit do "committed_date": "2014-02-27T10:01:38.000+01:00".to_time, "committer_name": "Dmitriy Zaporozhets", "committer_email": "dmitriy.zaporozhets@gmail.com", + "commit_author_id": an_instance_of(Integer), + "committer_id": an_instance_of(Integer), "merge_request_diff_id": merge_request_diff_id, "relative_order": 0, "sha": Gitlab::Database::ShaAttribute.serialize("5937ac0a7beb003549fc5fd26fc247adbce4a52e"), @@ -59,6 +66,8 @@ RSpec.describe MergeRequestDiffCommit do "committed_date": "2014-02-27T09:57:31.000+01:00".to_time, "committer_name": "Dmitriy Zaporozhets", "committer_email": "dmitriy.zaporozhets@gmail.com", + "commit_author_id": an_instance_of(Integer), + "committer_id": an_instance_of(Integer), "merge_request_diff_id": merge_request_diff_id, "relative_order": 1, "sha": Gitlab::Database::ShaAttribute.serialize("570e7b2abdd848b95f2f578043fc23bd6f6fd24d"), @@ -76,6 +85,21 @@ RSpec.describe MergeRequestDiffCommit do subject end + it 'creates diff commit users' do + diff = create(:merge_request_diff, merge_request: merge_request) + + described_class.create_bulk(diff.id, [commits.first]) + + commit_row = MergeRequestDiffCommit + .find_by(merge_request_diff_id: diff.id, relative_order: 0) + + commit_user_row = + MergeRequest::DiffCommitUser.find_by(name: 'Dmitriy Zaporozhets') + + expect(commit_row.commit_author).to eq(commit_user_row) + expect(commit_row.committer).to eq(commit_user_row) + end + context 'with dates larger than the DB limit' do let(:commits) do # This commit's date is "Sun Aug 17 07:12:55 292278994 +0000" @@ -92,6 +116,8 @@ RSpec.describe MergeRequestDiffCommit do "committed_date": timestamp, "committer_name": "Alejandro RodrÃguez", "committer_email": "alejorro70@gmail.com", + "commit_author_id": an_instance_of(Integer), + "committer_id": an_instance_of(Integer), "merge_request_diff_id": merge_request_diff_id, "relative_order": 0, "sha": Gitlab::Database::ShaAttribute.serialize("ba3343bc4fa403a8dfbfcab7fc1a8c29ee34bd69"), @@ -107,4 +133,28 @@ RSpec.describe MergeRequestDiffCommit do end end end + + describe '.prepare_commits_for_bulk_insert' do + it 'returns the commit hashes and unique user tuples' do + commit = double(:commit, to_hash: { + parent_ids: %w[foo bar], + author_name: 'a' * 1000, + author_email: 'a' * 1000, + committer_name: 'Alice', + committer_email: 'alice@example.com' + }) + + hashes, tuples = described_class.prepare_commits_for_bulk_insert([commit]) + + expect(hashes).to eq([{ + author_name: 'a' * 512, + author_email: 'a' * 512, + committer_name: 'Alice', + committer_email: 'alice@example.com' + }]) + + expect(tuples) + .to include(['a' * 512, 'a' * 512], %w[Alice alice@example.com]) + end + end end diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index 3741e01e99a..e0e25031589 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -9,10 +9,6 @@ RSpec.describe MergeRequestDiff do let(:diff_with_commits) { create(:merge_request).merge_request_diff } - before do - stub_feature_flags(diffs_gradual_load: false) - end - describe 'validations' do subject { diff_with_commits } @@ -115,6 +111,7 @@ RSpec.describe MergeRequestDiff do let(:closed_recently) { recently_closed_mr.merge_request_diff } let_it_be(:recently_merged_mr) { create(:merge_request, :merged) } + let(:merged_recently) { recently_merged_mr.merge_request_diff } before do @@ -436,9 +433,7 @@ RSpec.describe MergeRequestDiff do it 'returns empty pagination data' do diffs = diff_with_commits.diffs_in_batch(1, 10, diff_options: diff_options) - expect(diffs.pagination_data).to eq(current_page: nil, - next_page: nil, - total_pages: nil) + expect(diffs.pagination_data).to eq(total_pages: nil) end end @@ -460,19 +455,17 @@ RSpec.describe MergeRequestDiff do context 'when persisted files available' do it 'returns paginated diffs' do - diffs = diff_with_commits.diffs_in_batch(1, 10, diff_options: diff_options) + diffs = diff_with_commits.diffs_in_batch(0, 10, diff_options: diff_options) expect(diffs).to be_a(Gitlab::Diff::FileCollection::MergeRequestDiffBatch) expect(diffs.diff_files.size).to eq(10) - expect(diffs.pagination_data).to eq(current_page: 1, - next_page: 2, - total_pages: 2) + expect(diffs.pagination_data).to eq(total_pages: 20) end it 'sorts diff files directory first' do diff_with_commits.update!(sorted: false) # Mark as unsorted so it'll re-order - expect(diff_with_commits.diffs_in_batch(1, 10, diff_options: diff_options).diff_file_paths).to eq([ + expect(diff_with_commits.diffs_in_batch(0, 10, diff_options: diff_options).diff_file_paths).to eq([ 'bar/branch-test.txt', 'custom-highlighting/test.gitlab-custom', 'encoding/iso8859.txt', @@ -491,43 +484,21 @@ RSpec.describe MergeRequestDiff do { ignore_whitespace_change: true } end - it 'returns a Gitlab::Diff::FileCollection::Compare with paginated diffs' do + it 'returns pagination data from MergeRequestDiffBatch' do diffs = diff_with_commits.diffs_in_batch(1, 10, diff_options: diff_options) + file_count = diff_with_commits.merge_request_diff_files.count expect(diffs).to be_a(Gitlab::Diff::FileCollection::Compare) expect(diffs.diff_files.size).to eq 10 - expect(diffs.pagination_data).to eq(current_page: 1, next_page: 2, total_pages: 2) + expect(diffs.pagination_data).to eq(total_pages: file_count) end it 'returns an empty MergeRequestBatch with empty pagination data when the batch is empty' do - diffs = diff_with_commits.diffs_in_batch(3, 10, diff_options: diff_options) + diffs = diff_with_commits.diffs_in_batch(30, 10, diff_options: diff_options) expect(diffs).to be_a(Gitlab::Diff::FileCollection::MergeRequestDiffBatch) expect(diffs.diff_files.size).to eq 0 - expect(diffs.pagination_data).to eq(current_page: nil, next_page: nil, total_pages: nil) - end - - context 'with gradual load enabled' do - before do - stub_feature_flags(diffs_gradual_load: true) - end - - it 'returns pagination data from MergeRequestDiffBatch' do - diffs = diff_with_commits.diffs_in_batch(1, 10, diff_options: diff_options) - file_count = diff_with_commits.merge_request_diff_files.count - - expect(diffs).to be_a(Gitlab::Diff::FileCollection::Compare) - expect(diffs.diff_files.size).to eq 10 - expect(diffs.pagination_data).to eq(current_page: nil, next_page: nil, total_pages: file_count) - end - - it 'returns an empty MergeRequestBatch with empty pagination data when the batch is empty' do - diffs = diff_with_commits.diffs_in_batch(30, 10, diff_options: diff_options) - - expect(diffs).to be_a(Gitlab::Diff::FileCollection::MergeRequestDiffBatch) - expect(diffs.diff_files.size).to eq 0 - expect(diffs.pagination_data).to eq(current_page: nil, next_page: nil, total_pages: nil) - end + expect(diffs.pagination_data).to eq(total_pages: nil) end end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 73b1cb13f19..edd543854cb 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -441,6 +441,22 @@ RSpec.describe MergeRequest, factory_default: :keep do end end + describe '.join_metrics' do + let_it_be(:join_condition) { '"merge_request_metrics"."target_project_id" = 1' } + + context 'when a no target_project_id is available' do + it 'moves target_project_id condition to the merge request metrics' do + expect(described_class.join_metrics(1).to_sql).to include(join_condition) + end + end + + context 'when a target_project_id is present in the where conditions' do + it 'moves target_project_id condition to the merge request metrics' do + expect(described_class.where(target_project_id: 1).join_metrics.to_sql).to include(join_condition) + end + end + end + describe '.by_related_commit_sha' do subject { described_class.by_related_commit_sha(sha) } @@ -779,7 +795,7 @@ RSpec.describe MergeRequest, factory_default: :keep do context 'when both internal and external issue trackers are enabled' do before do - create(:jira_service, project: subject.project) + create(:jira_integration, project: subject.project) subject.project.reload end @@ -1310,7 +1326,7 @@ RSpec.describe MergeRequest, factory_default: :keep do subject.project.add_developer(subject.author) commit = double(:commit, safe_message: 'Fixes TEST-3') - create(:jira_service, project: subject.project) + create(:jira_integration, project: subject.project) subject.project.reload allow(subject).to receive(:commits).and_return([commit]) @@ -1898,7 +1914,7 @@ RSpec.describe MergeRequest, factory_default: :keep do context 'has ci' do it 'returns true if MR has head_pipeline_id and commits' do - allow(merge_request).to receive_message_chain(:source_project, :ci_service) { nil } + allow(merge_request).to receive_message_chain(:source_project, :ci_integration) { nil } allow(merge_request).to receive(:head_pipeline_id) { double } allow(merge_request).to receive(:has_no_commits?) { false } @@ -1906,7 +1922,7 @@ RSpec.describe MergeRequest, factory_default: :keep do end it 'returns true if MR has any pipeline and commits' do - allow(merge_request).to receive_message_chain(:source_project, :ci_service) { nil } + allow(merge_request).to receive_message_chain(:source_project, :ci_integration) { nil } allow(merge_request).to receive(:head_pipeline_id) { nil } allow(merge_request).to receive(:has_no_commits?) { false } allow(merge_request).to receive(:all_pipelines) { [double] } @@ -1914,8 +1930,8 @@ RSpec.describe MergeRequest, factory_default: :keep do expect(merge_request.has_ci?).to be(true) end - it 'returns true if MR has CI service and commits' do - allow(merge_request).to receive_message_chain(:source_project, :ci_service) { double } + it 'returns true if MR has CI integration and commits' do + allow(merge_request).to receive_message_chain(:source_project, :ci_integration) { double } allow(merge_request).to receive(:head_pipeline_id) { nil } allow(merge_request).to receive(:has_no_commits?) { false } allow(merge_request).to receive(:all_pipelines) { [] } @@ -1925,8 +1941,8 @@ RSpec.describe MergeRequest, factory_default: :keep do end context 'has no ci' do - it 'returns false if MR has no CI service nor pipeline, and no commits' do - allow(merge_request).to receive_message_chain(:source_project, :ci_service) { nil } + it 'returns false if MR has no CI integration nor pipeline, and no commits' do + allow(merge_request).to receive_message_chain(:source_project, :ci_integration) { nil } allow(merge_request).to receive(:head_pipeline_id) { nil } allow(merge_request).to receive(:all_pipelines) { [] } allow(merge_request).to receive(:has_no_commits?) { true } @@ -2067,14 +2083,6 @@ RSpec.describe MergeRequest, factory_default: :keep do let(:merge_request) { create(:merge_request, :with_codequality_mr_diff_reports) } it { is_expected.to be_truthy } - - context 'when feature flag is disabled' do - before do - stub_feature_flags(codequality_mr_diff: false) - end - - it { is_expected.to be_falsey } - end end context 'when head pipeline does not have codeqquality mr diff report' do diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index 7cf7c360dff..bc592acc80f 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -3,10 +3,9 @@ require 'spec_helper' RSpec.describe Milestone do - let(:user) { create(:user) } - let(:issue) { create(:issue, project: project) } - let(:milestone) { create(:milestone, project: project) } - let(:project) { create(:project, :public) } + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :public) } + let_it_be(:issue) { create(:issue, project: project) } it_behaves_like 'a timebox', :milestone do describe "#uniqueness_of_title" do @@ -92,6 +91,8 @@ RSpec.describe Milestone do end describe '.predefined_id?' do + let_it_be(:milestone) { create(:milestone, project: project) } + it 'returns true for a predefined Milestone ID' do expect(Milestone.predefined_id?(described_class::Upcoming.id)).to be true end @@ -129,6 +130,8 @@ RSpec.describe Milestone do end describe "#percent_complete" do + let(:milestone) { create(:milestone, project: project) } + it "does not count open issues" do milestone.issues << issue expect(milestone.percent_complete).to eq(0) @@ -145,24 +148,22 @@ RSpec.describe Milestone do end end - describe '#expired?' do + describe '#expired? and #expired' do context "expired" do - before do - allow(milestone).to receive(:due_date).and_return(Date.today.prev_year) - end + let(:milestone) { build(:milestone, project: project, due_date: Date.today.prev_year) } - it 'returns true when due_date is in the past' do + it 'returns true when due_date is in the past', :aggregate_failures do expect(milestone.expired?).to be_truthy + expect(milestone.expired).to eq true end end context "not expired" do - before do - allow(milestone).to receive(:due_date).and_return(Date.today.next_year) - end + let(:milestone) { build(:milestone, project: project, due_date: Date.today.next_year) } - it 'returns false when due_date is in the future' do + it 'returns false when due_date is in the future', :aggregate_failures do expect(milestone.expired?).to be_falsey + expect(milestone.expired).to eq false end end end @@ -180,10 +181,8 @@ RSpec.describe Milestone do end describe '#can_be_closed?' do - it { expect(milestone.can_be_closed?).to be_truthy } - end + let_it_be(:milestone) { build(:milestone, project: project) } - describe '#can_be_closed?' do before do milestone = create :milestone, project: project create :closed_issue, milestone: milestone, project: project @@ -335,10 +334,10 @@ RSpec.describe Milestone do it_behaves_like '#for_projects_and_groups' describe '.upcoming_ids' do - let(:group_1) { create(:group) } - let(:group_2) { create(:group) } - let(:group_3) { create(:group) } - let(:groups) { [group_1, group_2, group_3] } + let_it_be(:group_1) { create(:group) } + let_it_be(:group_2) { create(:group) } + let_it_be(:group_3) { create(:group) } + let_it_be(:groups) { [group_1, group_2, group_3] } let!(:past_milestone_group_1) { create(:milestone, group: group_1, due_date: Time.current - 1.day) } let!(:current_milestone_group_1) { create(:milestone, group: group_1, due_date: Time.current + 1.day) } @@ -350,10 +349,10 @@ RSpec.describe Milestone do let!(:past_milestone_group_3) { create(:milestone, group: group_3, due_date: Time.current - 1.day) } - let(:project_1) { create(:project) } - let(:project_2) { create(:project) } - let(:project_3) { create(:project) } - let(:projects) { [project_1, project_2, project_3] } + let_it_be(:project_1) { create(:project) } + let_it_be(:project_2) { create(:project) } + let_it_be(:project_3) { create(:project) } + let_it_be(:projects) { [project_1, project_2, project_3] } let!(:past_milestone_project_1) { create(:milestone, project: project_1, due_date: Time.current - 1.day) } let!(:current_milestone_project_1) { create(:milestone, project: project_1, due_date: Time.current + 1.day) } @@ -451,6 +450,32 @@ RSpec.describe Milestone do end end + describe '.sort_with_expired_last' do + let_it_be(:milestone) { create(:milestone, title: 'Due today', due_date: Date.current) } + let_it_be(:milestone_1) { create(:milestone, title: 'Current 1', due_date: Date.current + 1.day) } + let_it_be(:milestone_2) { create(:milestone, title: 'Current 2', due_date: Date.current + 2.days) } + let_it_be(:milestone_3) { create(:milestone, title: 'Without due date') } + let_it_be(:milestone_4) { create(:milestone, title: 'Expired 1', due_date: Date.current - 2.days) } + let_it_be(:milestone_5) { create(:milestone, title: 'Expired 2', due_date: Date.current - 1.day) } + let_it_be(:milestone_6) { create(:milestone, title: 'Without due date2') } + + context 'ordering by due_date ascending' do + it 'sorts by due date in ascending order (ties broken by id in desc order)', :aggregate_failures do + expect(milestone_3.id).to be < (milestone_6.id) + expect(described_class.sort_with_expired_last(:expired_last_due_date_asc)) + .to eq([milestone, milestone_1, milestone_2, milestone_6, milestone_3, milestone_4, milestone_5]) + end + end + + context 'ordering by due_date descending' do + it 'sorts by due date in descending order (ties broken by id in desc order)', :aggregate_failures do + expect(milestone_3.id).to be < (milestone_6.id) + expect(described_class.sort_with_expired_last(:expired_last_due_date_desc)) + .to eq([milestone_2, milestone_1, milestone, milestone_6, milestone_3, milestone_5, milestone_4]) + end + end + end + describe '.sort_by_attribute' do let_it_be(:milestone_1) { create(:milestone, title: 'Foo') } let_it_be(:milestone_2) { create(:milestone, title: 'Bar') } diff --git a/spec/models/namespace/root_storage_statistics_spec.rb b/spec/models/namespace/root_storage_statistics_spec.rb index b725d2366a1..51c191069ec 100644 --- a/spec/models/namespace/root_storage_statistics_spec.rb +++ b/spec/models/namespace/root_storage_statistics_spec.rb @@ -99,6 +99,7 @@ RSpec.describe Namespace::RootStorageStatistics, type: :model do context 'with a personal namespace' do let_it_be(:user) { create(:user) } + let(:namespace) { user.namespace } it_behaves_like 'data refresh' diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 373f3a89e14..ea1ce067e4d 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -156,7 +156,7 @@ RSpec.describe Namespace do end end - describe 'scopes' do + describe 'scopes', :aggregate_failures do let_it_be(:namespace1) { create(:group, name: 'Namespace 1', path: 'namespace-1') } let_it_be(:namespace2) { create(:group, name: 'Namespace 2', path: 'namespace-2') } let_it_be(:namespace1sub) { create(:group, name: 'Sub Namespace', path: 'sub-namespace', parent: namespace1) } @@ -181,6 +181,15 @@ RSpec.describe Namespace do expect(described_class.filter_by_path(namespace1.path.upcase)).to eq([namespace1]) end end + + describe '.sorted_by_similarity_and_parent_id_desc' do + it 'returns exact matches and top level groups first' do + expect(described_class.sorted_by_similarity_and_parent_id_desc(namespace1.path)).to eq([namespace1, namespace2, namespace2sub, namespace1sub, namespace]) + expect(described_class.sorted_by_similarity_and_parent_id_desc(namespace2.path)).to eq([namespace2, namespace1, namespace2sub, namespace1sub, namespace]) + expect(described_class.sorted_by_similarity_and_parent_id_desc(namespace2sub.name)).to eq([namespace2sub, namespace1sub, namespace2, namespace1, namespace]) + expect(described_class.sorted_by_similarity_and_parent_id_desc('Namespace')).to eq([namespace2, namespace1, namespace2sub, namespace1sub, namespace]) + end + end end describe 'delegate' do @@ -965,6 +974,14 @@ RSpec.describe Namespace do end end + shared_examples 'disabled feature flag when traversal_ids is blank' do + before do + namespace.traversal_ids = [] + end + + it { is_expected.to eq false } + end + describe '#use_traversal_ids?' do let_it_be(:namespace, reload: true) { create(:namespace) } @@ -976,6 +993,8 @@ RSpec.describe Namespace do end it { is_expected.to eq true } + + it_behaves_like 'disabled feature flag when traversal_ids is blank' end context 'when use_traversal_ids feature flag is false' do @@ -987,6 +1006,62 @@ RSpec.describe Namespace do end end + describe '#use_traversal_ids_for_root_ancestor?' do + let_it_be(:namespace, reload: true) { create(:namespace) } + + subject { namespace.use_traversal_ids_for_root_ancestor? } + + context 'when use_traversal_ids_for_root_ancestor feature flag is true' do + before do + stub_feature_flags(use_traversal_ids_for_root_ancestor: true) + end + + it { is_expected.to eq true } + + it_behaves_like 'disabled feature flag when traversal_ids is blank' + end + + context 'when use_traversal_ids_for_root_ancestor feature flag is false' do + before do + stub_feature_flags(use_traversal_ids_for_root_ancestor: false) + end + + it { is_expected.to eq false } + end + end + + describe '#use_traversal_ids_for_ancestors?' do + let_it_be(:namespace, reload: true) { create(:namespace) } + + subject { namespace.use_traversal_ids_for_ancestors? } + + context 'when use_traversal_ids_for_ancestors? feature flag is true' do + before do + stub_feature_flags(use_traversal_ids_for_ancestors: true) + end + + it { is_expected.to eq true } + + it_behaves_like 'disabled feature flag when traversal_ids is blank' + end + + context 'when use_traversal_ids_for_ancestors? feature flag is false' do + before do + stub_feature_flags(use_traversal_ids_for_ancestors: false) + end + + it { is_expected.to eq false } + end + + context 'when use_traversal_ids? feature flag is false' do + before do + stub_feature_flags(use_traversal_ids: false) + end + + it { is_expected.to eq false } + end + end + describe '#users_with_descendants' do let(:user_a) { create(:user) } let(:user_b) { create(:user) } @@ -1058,6 +1133,14 @@ RSpec.describe Namespace do end include_examples '#all_projects' + + # Using #self_and_descendant instead of #self_and_descendant_ids can produce + # very slow queries. + it 'calls self_and_descendant_ids' do + namespace = create(:group) + expect(namespace).to receive(:self_and_descendant_ids) + namespace.all_projects + end end context 'with use_traversal_ids feature flag disabled' do diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index d9f566f9383..2afe9a0f29b 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -455,6 +455,7 @@ RSpec.describe Note do describe "#system_note_viewable_by?(user)" do let_it_be(:note) { create(:note) } let_it_be(:user) { create(:user) } + let!(:metadata) { create(:system_note_metadata, note: note, action: "branch") } context "when system_note_metadata is not present" do @@ -536,6 +537,7 @@ RSpec.describe Note do context "when there is a reference to a label" do let_it_be(:private_label) { create(:label, project: private_project) } + let(:note) do create :note, noteable: ext_issue, project: ext_proj, @@ -550,6 +552,7 @@ RSpec.describe Note do context "when there are two references in note" do let_it_be(:ext_issue2) { create(:issue, project: ext_proj) } + let(:note) do create :note, noteable: ext_issue2, project: ext_proj, @@ -1239,6 +1242,7 @@ RSpec.describe Note do describe 'expiring ETag cache' do let_it_be(:issue) { create(:issue) } + let(:note) { build(:note, project: issue.project, noteable: issue) } def expect_expiration(noteable) diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb index 010b7455f85..3f1684327e7 100644 --- a/spec/models/notification_setting_spec.rb +++ b/spec/models/notification_setting_spec.rb @@ -51,6 +51,7 @@ RSpec.describe NotificationSetting do context 'notification_email' do let_it_be(:user) { create(:user) } + subject { described_class.new(source_id: 1, source_type: 'Project', user_id: user.id) } it 'allows to change email to verified one' do diff --git a/spec/models/operations/feature_flag_spec.rb b/spec/models/operations/feature_flag_spec.rb index 55682e12642..cb9da2aea34 100644 --- a/spec/models/operations/feature_flag_spec.rb +++ b/spec/models/operations/feature_flag_spec.rb @@ -251,6 +251,7 @@ RSpec.describe Operations::FeatureFlag do describe '.for_unleash_client' do let_it_be(:project) { create(:project) } + let!(:feature_flag) do create(:operations_feature_flag, project: project, name: 'feature1', active: true, version: 2) diff --git a/spec/models/packages/package_file_spec.rb b/spec/models/packages/package_file_spec.rb index 7f2f22c815c..ee0aeb26d50 100644 --- a/spec/models/packages/package_file_spec.rb +++ b/spec/models/packages/package_file_spec.rb @@ -5,6 +5,7 @@ RSpec.describe Packages::PackageFile, type: :model do let_it_be(:project) { create(:project) } let_it_be(:package_file1) { create(:package_file, :xml, file_name: 'FooBar') } let_it_be(:package_file2) { create(:package_file, :xml, file_name: 'ThisIsATest') } + let_it_be(:package_file3) { create(:package_file, :xml, file_name: 'formatted.zip') } let_it_be(:debian_package) { create(:debian_package, project: project) } describe 'relationships' do @@ -36,6 +37,12 @@ RSpec.describe Packages::PackageFile, type: :model do it { is_expected.to match_array([package_file1]) } end + + describe '.with_format' do + subject { described_class.with_format('zip') } + + it { is_expected.to contain_exactly(package_file3) } + end end context 'updating project statistics' do diff --git a/spec/models/packages/package_spec.rb b/spec/models/packages/package_spec.rb index b2c1d51e4af..449e30f9fb7 100644 --- a/spec/models/packages/package_spec.rb +++ b/spec/models/packages/package_spec.rb @@ -1006,37 +1006,4 @@ RSpec.describe Packages::Package, type: :model do it_behaves_like 'not enqueuing a sync worker job' end end - - context 'destroying a composer package' do - let_it_be(:package_name) { 'composer-package-name' } - let_it_be(:json) { { 'name' => package_name } } - let_it_be(:project) { create(:project, :custom_repo, files: { 'composer.json' => json.to_json } ) } - - let!(:package) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '1.0.0', json: json) } - - before do - Gitlab::Composer::Cache.new(project: project, name: package_name).execute - package.composer_metadatum.reload - end - - context 'with feature flag disabled' do - before do - stub_feature_flags(disable_composer_callback: false) - end - - it 'schedule the update job' do - expect(::Packages::Composer::CacheUpdateWorker).to receive(:perform_async).with(project.id, package_name, package.composer_metadatum.version_cache_sha) - - package.destroy! - end - end - - context 'with feature flag enabled' do - it 'does nothing' do - expect(::Packages::Composer::CacheUpdateWorker).not_to receive(:perform_async) - - package.destroy! - end - end - end end diff --git a/spec/models/plan_limits_spec.rb b/spec/models/plan_limits_spec.rb index cf8e30023eb..72fda2280e5 100644 --- a/spec/models/plan_limits_spec.rb +++ b/spec/models/plan_limits_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' RSpec.describe PlanLimits do let_it_be(:project) { create(:project) } let_it_be(:plan_limits) { create(:plan_limits) } + let(:project_hooks_count) { 2 } before do @@ -184,6 +185,7 @@ RSpec.describe PlanLimits do ci_max_artifact_size_junit ci_max_artifact_size_sast ci_max_artifact_size_dast + ci_max_artifact_size_cluster_image_scanning ci_max_artifact_size_codequality ci_max_artifact_size_license_management ci_max_artifact_size_performance diff --git a/spec/models/plan_spec.rb b/spec/models/plan_spec.rb index 490c6b1bbf7..73e88a17e24 100644 --- a/spec/models/plan_spec.rb +++ b/spec/models/plan_spec.rb @@ -15,6 +15,29 @@ RSpec.describe Plan do end end + describe '#default' do + context 'when default plan exists' do + let!(:default_plan) { create(:default_plan) } + + it 'returns default plan' do + expect(described_class.default).to eq(default_plan) + end + end + + context 'when default plan does not exist' do + it 'creates default plan' do + expect { described_class.default }.to change { Plan.count }.by(1) + end + + it 'creates plan with correct attributes' do + plan = described_class.default + + expect(plan.name).to eq(Plan::DEFAULT) + expect(plan.title).to eq(Plan::DEFAULT.titleize) + end + end + end + context 'when updating plan limits' do let(:plan) { described_class.default } diff --git a/spec/models/project_ci_cd_setting_spec.rb b/spec/models/project_ci_cd_setting_spec.rb index c206ba27ec1..caab182cda8 100644 --- a/spec/models/project_ci_cd_setting_spec.rb +++ b/spec/models/project_ci_cd_setting_spec.rb @@ -22,8 +22,8 @@ RSpec.describe ProjectCiCdSetting do end describe '#job_token_scope_enabled' do - it 'is true by default' do - expect(described_class.new.job_token_scope_enabled).to be_truthy + it 'is false by default' do + expect(described_class.new.job_token_scope_enabled).to be_falsey end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 144b00e1d2e..efa269cdb5c 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -35,14 +35,14 @@ RSpec.describe Project, factory_default: :keep do it { is_expected.to have_many(:hooks) } it { is_expected.to have_many(:protected_branches) } it { is_expected.to have_many(:exported_protected_branches) } - it { is_expected.to have_one(:slack_service) } - it { is_expected.to have_one(:microsoft_teams_service) } - it { is_expected.to have_one(:mattermost_service) } + it { is_expected.to have_one(:slack_integration) } + it { is_expected.to have_one(:microsoft_teams_integration) } + it { is_expected.to have_one(:mattermost_integration) } it { is_expected.to have_one(:hangouts_chat_integration) } - it { is_expected.to have_one(:unify_circuit_service) } - it { is_expected.to have_one(:webex_teams_service) } - it { is_expected.to have_one(:packagist_service) } - it { is_expected.to have_one(:pushover_service) } + it { is_expected.to have_one(:unify_circuit_integration) } + it { is_expected.to have_one(:webex_teams_integration) } + it { is_expected.to have_one(:packagist_integration) } + it { is_expected.to have_one(:pushover_integration) } it { is_expected.to have_one(:asana_integration) } it { is_expected.to have_many(:boards) } it { is_expected.to have_one(:campfire_integration) } @@ -50,19 +50,19 @@ RSpec.describe Project, factory_default: :keep do it { is_expected.to have_one(:discord_integration) } it { is_expected.to have_one(:drone_ci_integration) } it { is_expected.to have_one(:emails_on_push_integration) } - it { is_expected.to have_one(:pipelines_email_service) } + it { is_expected.to have_one(:pipelines_email_integration) } it { is_expected.to have_one(:irker_integration) } - it { is_expected.to have_one(:pivotaltracker_service) } + it { is_expected.to have_one(:pivotaltracker_integration) } it { is_expected.to have_one(:flowdock_integration) } it { is_expected.to have_one(:assembla_integration) } - it { is_expected.to have_one(:slack_slash_commands_service) } - it { is_expected.to have_one(:mattermost_slash_commands_service) } + it { is_expected.to have_one(:slack_slash_commands_integration) } + it { is_expected.to have_one(:mattermost_slash_commands_integration) } it { is_expected.to have_one(:buildkite_integration) } it { is_expected.to have_one(:bamboo_integration) } - it { is_expected.to have_one(:teamcity_service) } - it { is_expected.to have_one(:jira_service) } - it { is_expected.to have_one(:redmine_service) } - it { is_expected.to have_one(:youtrack_service) } + it { is_expected.to have_one(:teamcity_integration) } + it { is_expected.to have_one(:jira_integration) } + it { is_expected.to have_one(:redmine_integration) } + it { is_expected.to have_one(:youtrack_integration) } it { is_expected.to have_one(:custom_issue_tracker_integration) } it { is_expected.to have_one(:bugzilla_integration) } it { is_expected.to have_one(:ewm_integration) } @@ -80,6 +80,8 @@ RSpec.describe Project, factory_default: :keep do it { is_expected.to have_one(:error_tracking_setting).class_name('ErrorTracking::ProjectErrorTrackingSetting') } it { is_expected.to have_one(:project_setting) } it { is_expected.to have_one(:alerting_setting).class_name('Alerting::ProjectAlertingSetting') } + it { is_expected.to have_one(:mock_ci_integration) } + it { is_expected.to have_one(:mock_monitoring_integration) } it { is_expected.to have_many(:commit_statuses) } it { is_expected.to have_many(:ci_pipelines) } it { is_expected.to have_many(:ci_refs) } @@ -656,12 +658,51 @@ RSpec.describe Project, factory_default: :keep do it { is_expected.to delegate_method(:container_registry_enabled?).to(:project_feature) } it { is_expected.to delegate_method(:container_registry_access_level).to(:project_feature) } - context 'when read_container_registry_access_level is disabled' do - before do - stub_feature_flags(read_container_registry_access_level: false) + include_examples 'ci_cd_settings delegation' do + # Skip attributes defined in EE code + let(:exclude_attributes) do + %w( + merge_pipelines_enabled + merge_trains_enabled + auto_rollback_enabled + ) + end + end + + describe '#ci_forward_deployment_enabled?' do + it_behaves_like 'a ci_cd_settings predicate method', prefix: 'ci_' do + let(:delegated_method) { :forward_deployment_enabled? } + end + end + + describe '#ci_job_token_scope_enabled?' do + it_behaves_like 'a ci_cd_settings predicate method', prefix: 'ci_' do + let(:delegated_method) { :job_token_scope_enabled? } + end + end + + describe '#restrict_user_defined_variables?' do + it_behaves_like 'a ci_cd_settings predicate method' do + let(:delegated_method) { :restrict_user_defined_variables? } + end + end + + describe '#keep_latest_artifacts_available?' do + it_behaves_like 'a ci_cd_settings predicate method' do + let(:delegated_method) { :keep_latest_artifacts_available? } + end + end + + describe '#keep_latest_artifact?' do + it_behaves_like 'a ci_cd_settings predicate method' do + let(:delegated_method) { :keep_latest_artifact? } end + end - it { is_expected.not_to delegate_method(:container_registry_enabled?).to(:project_feature) } + describe '#group_runners_enabled?' do + it_behaves_like 'a ci_cd_settings predicate method' do + let(:delegated_method) { :group_runners_enabled? } + end end end @@ -1444,13 +1485,13 @@ RSpec.describe Project, factory_default: :keep do end end - describe '.with_active_jira_services' do - it 'returns the correct project' do - active_jira_service = create(:jira_service) + describe '.with_active_jira_integrations' do + it 'returns the correct integrations' do + active_jira_integration = create(:jira_integration) active_service = create(:service, active: true) - expect(described_class.with_active_jira_services).to include(active_jira_service.project) - expect(described_class.with_active_jira_services).not_to include(active_service.project) + expect(described_class.with_active_jira_integrations).to include(active_jira_integration.project) + expect(described_class.with_active_jira_integrations).not_to include(active_service.project) end end @@ -1555,13 +1596,16 @@ RSpec.describe Project, factory_default: :keep do end end - describe '.with_service' do + describe '.with_integration' do before do create_list(:prometheus_project, 2) end - it 'avoid n + 1' do - expect { described_class.with_service(:prometheus_service).map(&:prometheus_service) }.not_to exceed_query_limit(1) + let(:integration) { :prometheus_integration } + + it 'avoids n + 1' do + expect { described_class.with_integration(integration).map(&integration) } + .not_to exceed_query_limit(1) end end @@ -2403,20 +2447,6 @@ RSpec.describe Project, factory_default: :keep do expect(project.container_registry_enabled).to eq(false) expect(project.container_registry_enabled?).to eq(false) end - - context 'with read_container_registry_access_level disabled' do - before do - stub_feature_flags(read_container_registry_access_level: false) - end - - it 'reads project.container_registry_enabled' do - project.update_column(:container_registry_enabled, true) - project.project_feature.update_column(:container_registry_access_level, ProjectFeature::DISABLED) - - expect(project.container_registry_enabled).to eq(true) - expect(project.container_registry_enabled?).to eq(true) - end - end end describe '#has_container_registry_tags?' do @@ -3083,8 +3113,8 @@ RSpec.describe Project, factory_default: :keep do context 'LFS disabled in group' do before do + stub_lfs_setting(enabled: true) project.namespace.update_attribute(:lfs_enabled, false) - enable_lfs end it_behaves_like 'project overrides group' @@ -3092,14 +3122,18 @@ RSpec.describe Project, factory_default: :keep do context 'LFS enabled in group' do before do + stub_lfs_setting(enabled: true) project.namespace.update_attribute(:lfs_enabled, true) - enable_lfs end it_behaves_like 'project overrides group' end describe 'LFS disabled globally' do + before do + stub_lfs_setting(enabled: false) + end + shared_examples 'it always returns false' do it do expect(project.lfs_enabled?).to be_falsey @@ -3896,10 +3930,6 @@ RSpec.describe Project, factory_default: :keep do end end - def enable_lfs - allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) - end - describe '#pages_url' do let(:group) { create(:group, name: 'Group') } let(:nested_group) { create(:group, parent: group) } @@ -5350,27 +5380,27 @@ RSpec.describe Project, factory_default: :keep do end end - describe '#execute_services' do - let(:service) { create(:slack_service, push_events: true, merge_requests_events: false, active: true) } + describe '#execute_integrations' do + let(:integration) { create(:integrations_slack, push_events: true, merge_requests_events: false, active: true) } - it 'executes services with the specified scope' do + it 'executes integrations with the specified scope' do data = 'any data' expect_next_found_instance_of(Integrations::Slack) do |instance| expect(instance).to receive(:async_execute).with(data).once end - service.project.execute_services(data, :push_hooks) + integration.project.execute_integrations(data, :push_hooks) end - it 'does not execute services that don\'t match the specified scope' do + it 'does not execute integration that don\'t match the specified scope' do expect(Integrations::Slack).not_to receive(:allocate).and_wrap_original do |method| method.call.tap do |instance| expect(instance).not_to receive(:async_execute) end end - service.project.execute_services(anything, :merge_request_hooks) + integration.project.execute_integrations(anything, :merge_request_hooks) end end @@ -5401,16 +5431,16 @@ RSpec.describe Project, factory_default: :keep do end end - describe '#has_active_services?' do + describe '#has_active_integrations?' do let_it_be(:project) { create(:project) } - it { expect(project.has_active_services?).to be_falsey } + it { expect(project.has_active_integrations?).to be_falsey } it 'returns true when a matching service exists' do create(:custom_issue_tracker_integration, push_events: true, merge_requests_events: false, project: project) - expect(project.has_active_services?(:merge_request_hooks)).to be_falsey - expect(project.has_active_services?).to be_truthy + expect(project.has_active_integrations?(:merge_request_hooks)).to be_falsey + expect(project.has_active_integrations?).to be_truthy end end @@ -5820,112 +5850,92 @@ RSpec.describe Project, factory_default: :keep do end end - describe '#find_or_initialize_services' do + describe '#find_or_initialize_integrations' do let_it_be(:subject) { create(:project) } it 'avoids N+1 database queries' do - control_count = ActiveRecord::QueryRecorder.new { subject.find_or_initialize_services }.count + control_count = ActiveRecord::QueryRecorder.new { subject.find_or_initialize_integrations }.count expect(control_count).to be <= 4 end - it 'avoids N+1 database queries with more available services' do - allow(Integration).to receive(:available_services_names).and_return(%w[pushover]) - control_count = ActiveRecord::QueryRecorder.new { subject.find_or_initialize_services } - - allow(Integration).to receive(:available_services_names).and_call_original - expect { subject.find_or_initialize_services }.not_to exceed_query_limit(control_count) - end - - context 'with disabled services' do - before do - allow(Integration).to receive(:available_services_names).and_return(%w[prometheus pushover teamcity]) - allow(subject).to receive(:disabled_services).and_return(%w[prometheus]) - end - - it 'returns only enabled services sorted' do - services = subject.find_or_initialize_services + it 'avoids N+1 database queries with more available integrations' do + allow(Integration).to receive(:available_integration_names).and_return(%w[pushover]) + control_count = ActiveRecord::QueryRecorder.new { subject.find_or_initialize_integrations } - expect(services.size).to eq(2) - expect(services.map(&:title)).to eq(['JetBrains TeamCity', 'Pushover']) - end + allow(Integration).to receive(:available_integration_names).and_call_original + expect { subject.find_or_initialize_integrations }.not_to exceed_query_limit(control_count) end - end - - describe '#disabled_services' do - subject { build(:project).disabled_services } - context 'without datadog_ci_integration' do + context 'with disabled integrations' do before do - stub_feature_flags(datadog_ci_integration: false) + allow(Integration).to receive(:available_integration_names).and_return(%w[prometheus pushover teamcity]) + allow(subject).to receive(:disabled_integrations).and_return(%w[prometheus]) end - it { is_expected.to include('datadog') } - end - - context 'with datadog_ci_integration' do - before do - stub_feature_flags(datadog_ci_integration: true) + it 'returns only enabled integrations sorted' do + expect(subject.find_or_initialize_integrations).to match [ + have_attributes(title: 'JetBrains TeamCity'), + have_attributes(title: 'Pushover') + ] end - - it { is_expected.not_to include('datadog') } end end - describe '#find_or_initialize_service' do + describe '#find_or_initialize_integration' do it 'avoids N+1 database queries' do - allow(Integration).to receive(:available_services_names).and_return(%w[prometheus pushover]) + allow(Integration).to receive(:available_integration_names).and_return(%w[prometheus pushover]) - control_count = ActiveRecord::QueryRecorder.new { subject.find_or_initialize_service('prometheus') }.count + control_count = ActiveRecord::QueryRecorder.new { subject.find_or_initialize_integration('prometheus') }.count - allow(Integration).to receive(:available_services_names).and_call_original + allow(Integration).to receive(:available_integration_names).and_call_original - expect { subject.find_or_initialize_service('prometheus') }.not_to exceed_query_limit(control_count) + expect { subject.find_or_initialize_integration('prometheus') }.not_to exceed_query_limit(control_count) end it 'returns nil if integration is disabled' do - allow(subject).to receive(:disabled_services).and_return(%w[prometheus]) + allow(subject).to receive(:disabled_integrations).and_return(%w[prometheus]) - expect(subject.find_or_initialize_service('prometheus')).to be_nil + expect(subject.find_or_initialize_integration('prometheus')).to be_nil end context 'with an existing integration' do subject { create(:project) } before do - create(:prometheus_service, project: subject, api_url: 'https://prometheus.project.com/') + create(:prometheus_integration, project: subject, api_url: 'https://prometheus.project.com/') end it 'retrieves the integration' do - expect(subject.find_or_initialize_service('prometheus').api_url).to eq('https://prometheus.project.com/') + expect(subject.find_or_initialize_integration('prometheus').api_url).to eq('https://prometheus.project.com/') end end context 'with an instance-level and template integrations' do before do - create(:prometheus_service, :instance, api_url: 'https://prometheus.instance.com/') - create(:prometheus_service, :template, api_url: 'https://prometheus.template.com/') + create(:prometheus_integration, :instance, api_url: 'https://prometheus.instance.com/') + create(:prometheus_integration, :template, api_url: 'https://prometheus.template.com/') end - it 'builds the service from the instance if exists' do - expect(subject.find_or_initialize_service('prometheus').api_url).to eq('https://prometheus.instance.com/') + it 'builds the integration from the instance integration' do + expect(subject.find_or_initialize_integration('prometheus').api_url).to eq('https://prometheus.instance.com/') end end - context 'with an instance-level and template integrations' do + context 'with a template integration and no instance-level' do before do - create(:prometheus_service, :template, api_url: 'https://prometheus.template.com/') + create(:prometheus_integration, :template, api_url: 'https://prometheus.template.com/') end - it 'builds the service from the template if instance does not exists' do - expect(subject.find_or_initialize_service('prometheus').api_url).to eq('https://prometheus.template.com/') + it 'builds the integration from the template' do + expect(subject.find_or_initialize_integration('prometheus').api_url).to eq('https://prometheus.template.com/') end end - context 'without an exisiting integration, nor instance-level or template' do - it 'builds the service if instance or template does not exists' do - expect(subject.find_or_initialize_service('prometheus')).to be_a(PrometheusService) - expect(subject.find_or_initialize_service('prometheus').api_url).to be_nil + context 'without an exisiting integration, or instance-level or template' do + it 'builds the integration' do + expect(subject.find_or_initialize_integration('prometheus')).to be_a(::Integrations::Prometheus) + expect(subject.find_or_initialize_integration('prometheus').api_url).to be_nil end end end @@ -6605,25 +6615,25 @@ RSpec.describe Project, factory_default: :keep do end end - describe '#prometheus_service_active?' do + describe '#prometheus_integration_active?' do let(:project) { create(:project) } - subject { project.prometheus_service_active? } + subject { project.prometheus_integration_active? } before do - create(:prometheus_service, project: project, manual_configuration: manual_configuration) + create(:prometheus_integration, project: project, manual_configuration: manual_configuration) end - context 'when project has an activated prometheus service' do + context 'when project has an activated prometheus integration' do let(:manual_configuration) { true } it { is_expected.to be_truthy } end - context 'when project has an inactive prometheus service' do + context 'when project has an inactive prometheus integration' do let(:manual_configuration) { false } - it 'the service is marked as inactive' do + it 'the integration is marked as inactive' do expect(subject).to be_falsey end end diff --git a/spec/models/prometheus_alert_spec.rb b/spec/models/prometheus_alert_spec.rb index 8e517e1764e..bfe2c7cc2a4 100644 --- a/spec/models/prometheus_alert_spec.rb +++ b/spec/models/prometheus_alert_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe PrometheusAlert do let_it_be(:project) { build(:project) } + let(:metric) { build(:prometheus_metric) } describe '.distinct_projects' do diff --git a/spec/models/protected_branch/push_access_level_spec.rb b/spec/models/protected_branch/push_access_level_spec.rb index fa84cd660cb..13d33b95b16 100644 --- a/spec/models/protected_branch/push_access_level_spec.rb +++ b/spec/models/protected_branch/push_access_level_spec.rb @@ -40,6 +40,7 @@ RSpec.describe ProtectedBranch::PushAccessLevel do let_it_be(:protected_branch) { create(:protected_branch, :no_one_can_push, project: project) } let_it_be(:user) { create(:user) } let_it_be(:deploy_key) { create(:deploy_key, user: user) } + let!(:deploy_keys_project) { create(:deploy_keys_project, project: project, deploy_key: deploy_key, can_push: can_push) } let(:can_push) { true } diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index c896b6c0c6c..452eafe733f 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -41,6 +41,7 @@ RSpec.describe Repository do describe '#branch_names_contains' do let_it_be(:project) { create(:project, :repository) } + let(:repository) { project.repository } subject { repository.branch_names_contains(sample_commit.id) } @@ -398,6 +399,7 @@ RSpec.describe Repository do describe '#new_commits' do let_it_be(:project) { create(:project, :repository) } + let(:repository) { project.repository } subject { repository.new_commits(rev) } @@ -426,6 +428,7 @@ RSpec.describe Repository do describe '#commits_by' do let_it_be(:project) { create(:project, :repository) } + let(:oids) { TestEnv::BRANCH_SHA.values } subject { project.repository.commits_by(oids: oids) } @@ -2990,6 +2993,7 @@ RSpec.describe Repository do describe '#merge_base' do let_it_be(:project) { create(:project, :repository) } + subject(:repository) { project.repository } it 'only makes one gitaly call' do @@ -3088,6 +3092,7 @@ RSpec.describe Repository do describe "#blobs_metadata" do let_it_be(:project) { create(:project, :repository) } + let(:repository) { project.repository } def expect_metadata_blob(thing) diff --git a/spec/models/service_desk_setting_spec.rb b/spec/models/service_desk_setting_spec.rb index 8ccbd983ba1..f99ac84175c 100644 --- a/spec/models/service_desk_setting_spec.rb +++ b/spec/models/service_desk_setting_spec.rb @@ -10,7 +10,7 @@ RSpec.describe ServiceDeskSetting do it { is_expected.to validate_length_of(:outgoing_name).is_at_most(255) } it { is_expected.to validate_length_of(:project_key).is_at_most(255) } it { is_expected.to allow_value('abc123_').for(:project_key) } - it { is_expected.not_to allow_value('abc 12').for(:project_key) } + it { is_expected.not_to allow_value('abc 12').for(:project_key).with_message("can contain only lowercase letters, digits, and '_'.") } it { is_expected.not_to allow_value('Big val').for(:project_key) } describe '.valid_issue_template' do diff --git a/spec/models/snippet_repository_spec.rb b/spec/models/snippet_repository_spec.rb index 11196f06529..40a28b9e0cc 100644 --- a/spec/models/snippet_repository_spec.rb +++ b/spec/models/snippet_repository_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe SnippetRepository do let_it_be(:user) { create(:user) } + let(:snippet) { create(:personal_snippet, :repository, author: user) } let(:snippet_repository) { snippet.snippet_repository } let(:commit_opts) { { branch_name: 'master', message: 'whatever' } } diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index 06e9899c0bd..19d3895177f 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -722,6 +722,7 @@ RSpec.describe Snippet do describe '#list_files' do let_it_be(:snippet) { create(:snippet, :repository) } + let(:ref) { 'test-ref' } subject { snippet.list_files(ref) } @@ -827,14 +828,10 @@ RSpec.describe Snippet do end context 'when default branch in settings is different from "master"' do - let(:default_branch) { 'main' } + let(:default_branch) { 'custom-branch' } it 'changes the HEAD reference to the default branch' do - expect(File.read(head_path).squish).to eq 'ref: refs/heads/master' - - subject - - expect(File.read(head_path).squish).to eq "ref: refs/heads/#{default_branch}" + expect { subject }.to change { File.read(head_path).squish }.to("ref: refs/heads/#{default_branch}") end end end diff --git a/spec/models/terraform/state_spec.rb b/spec/models/terraform/state_spec.rb index 1319e2adb03..a113ae37203 100644 --- a/spec/models/terraform/state_spec.rb +++ b/spec/models/terraform/state_spec.rb @@ -16,6 +16,7 @@ RSpec.describe Terraform::State do describe 'scopes' do describe '.ordered_by_name' do let_it_be(:project) { create(:project) } + let(:names) { %w(state_d state_b state_a state_c) } subject { described_class.ordered_by_name } diff --git a/spec/models/timelog_spec.rb b/spec/models/timelog_spec.rb index bc042f7a639..9d6fda1d2a9 100644 --- a/spec/models/timelog_spec.rb +++ b/spec/models/timelog_spec.rb @@ -17,6 +17,8 @@ RSpec.describe Timelog do it { is_expected.to validate_presence_of(:time_spent) } it { is_expected.to validate_presence_of(:user) } + it { is_expected.to validate_length_of(:summary).is_at_most(255) } + it { expect(subject.project_id).not_to be_nil } describe 'Issuable validation' do diff --git a/spec/models/u2f_registration_spec.rb b/spec/models/u2f_registration_spec.rb index 1f2e4d1e447..aba2f27d104 100644 --- a/spec/models/u2f_registration_spec.rb +++ b/spec/models/u2f_registration_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe U2fRegistration do let_it_be(:user) { create(:user) } + let(:u2f_registration) do device = U2F::FakeU2F.new(FFaker::BaconIpsum.characters(5)) create(:u2f_registration, name: 'u2f_device', diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index e86a9c262d8..0eb769c65cd 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1005,6 +1005,7 @@ RSpec.describe User do let_it_be(:valid_token_and_notified) { create(:personal_access_token, user: user2, expires_at: 2.days.from_now, expire_notification_delivered: true) } let_it_be(:valid_token1) { create(:personal_access_token, user: user2, expires_at: 2.days.from_now) } let_it_be(:valid_token2) { create(:personal_access_token, user: user2, expires_at: 2.days.from_now) } + let(:users) { described_class.with_expiring_and_not_notified_personal_access_tokens(from) } context 'in one day' do @@ -1898,6 +1899,14 @@ RSpec.describe User do expect(user.deactivated?).to be_truthy end + + it 'sends deactivated user an email' do + expect_next_instance_of(NotificationService) do |notification| + allow(notification).to receive(:user_deactivated).with(user.name, user.notification_email) + end + + user.deactivate + end end context "a user who is blocked" do @@ -2826,6 +2835,14 @@ RSpec.describe User do end end + describe '#matches_identity?' do + it 'finds the identity when the DN is formatted differently' do + user = create(:omniauth_user, provider: 'ldapmain', extern_uid: 'uid=john smith,ou=people,dc=example,dc=com') + + expect(user.matches_identity?('ldapmain', 'uid=John Smith, ou=People, dc=example, dc=com')).to eq(true) + end + end + describe '#ldap_block' do let(:user) { create(:omniauth_user, provider: 'ldapmain', name: 'John Smith') } @@ -4241,6 +4258,7 @@ RSpec.describe User do describe '#source_groups_of_two_factor_authentication_requirement' do let_it_be(:group_not_requiring_2FA) { create :group } + let(:user) { create :user } before do @@ -5258,11 +5276,43 @@ RSpec.describe User do end describe '#password_expired_if_applicable?' do - let(:user) { build(:user, password_expires_at: password_expires_at) } + let(:user) { build(:user, password_expires_at: password_expires_at, password_automatically_set: set_automatically?) } subject { user.password_expired_if_applicable? } context 'when user is not ldap user' do + context 'when user has password set automatically' do + let(:set_automatically?) { true } + + context 'when password_expires_at is not set' do + let(:password_expires_at) {} + + it 'returns false' do + is_expected.to be_falsey + end + end + + context 'when password_expires_at is in the past' do + let(:password_expires_at) { 1.minute.ago } + + it 'returns true' do + is_expected.to be_truthy + end + end + + context 'when password_expires_at is in the future' do + let(:password_expires_at) { 1.minute.from_now } + + it 'returns false' do + is_expected.to be_falsey + end + end + end + end + + context 'when user has password not set automatically' do + let(:set_automatically?) { false } + context 'when password_expires_at is not set' do let(:password_expires_at) {} @@ -5274,8 +5324,8 @@ RSpec.describe User do context 'when password_expires_at is in the past' do let(:password_expires_at) { 1.minute.ago } - it 'returns true' do - is_expected.to be_truthy + it 'returns false' do + is_expected.to be_falsey end end @@ -5319,6 +5369,34 @@ RSpec.describe User do end end end + + context 'when user is a project bot' do + let(:user) { build(:user, :project_bot, password_expires_at: password_expires_at) } + + context 'when password_expires_at is not set' do + let(:password_expires_at) {} + + it 'returns false' do + is_expected.to be_falsey + end + end + + context 'when password_expires_at is in the past' do + let(:password_expires_at) { 1.minute.ago } + + it 'returns false' do + is_expected.to be_falsey + end + end + + context 'when password_expires_at is in the future' do + let(:password_expires_at) { 1.minute.from_now } + + it 'returns false' do + is_expected.to be_falsey + end + end + end end describe '#read_only_attribute?' do @@ -5787,6 +5865,20 @@ RSpec.describe User do end end + describe '#default_dashboard?' do + it 'is the default dashboard' do + user = build(:user) + + expect(user.default_dashboard?).to be true + end + + it 'is not the default dashboard' do + user = build(:user, dashboard: 'stars') + + expect(user.default_dashboard?).to be false + end + end + describe '.dormant' do it 'returns dormant users' do freeze_time do @@ -5829,4 +5921,17 @@ RSpec.describe User do end end end + + describe '.by_provider_and_extern_uid' do + it 'calls Identity model scope to ensure case-insensitive query', :aggregate_failures do + expected_user = create(:user) + create(:identity, extern_uid: 'some-other-name-id', provider: :github) + create(:identity, extern_uid: 'my_github_id', provider: :gitlab) + create(:identity) + create(:identity, user: expected_user, extern_uid: 'my_github_id', provider: :github) + + expect(Identity).to receive(:with_extern_uid).and_call_original + expect(described_class.by_provider_and_extern_uid(:github, 'my_github_id')).to match_array([expected_user]) + end + end end diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb index 579a9e664cf..699dd35196f 100644 --- a/spec/models/wiki_page_spec.rb +++ b/spec/models/wiki_page_spec.rb @@ -201,11 +201,10 @@ RSpec.describe WikiPage do expect(subject.errors.messages).to eq(title: ["can't be blank"]) end - it "validates presence of content" do + it "does not validate presence of content" do subject.attributes.delete(:content) - expect(subject).not_to be_valid - expect(subject.errors.messages).to eq(content: ["can't be blank"]) + expect(subject).to be_valid end describe '#validate_content_size_limit' do |