summaryrefslogtreecommitdiff
path: root/spec/models
diff options
context:
space:
mode:
Diffstat (limited to 'spec/models')
-rw-r--r--spec/models/alert_management/http_integration_spec.rb2
-rw-r--r--spec/models/application_setting_spec.rb39
-rw-r--r--spec/models/audit_event_archived_spec.rb (renamed from spec/models/audit_event_partitioned_spec.rb)20
-rw-r--r--spec/models/ci/bridge_spec.rb12
-rw-r--r--spec/models/ci/build_need_spec.rb2
-rw-r--r--spec/models/ci/build_spec.rb249
-rw-r--r--spec/models/ci/commit_with_pipeline_spec.rb (renamed from spec/models/commit_with_pipeline_spec.rb)2
-rw-r--r--spec/models/ci/group_spec.rb12
-rw-r--r--spec/models/ci/job_artifact_spec.rb33
-rw-r--r--spec/models/ci/ref_spec.rb60
-rw-r--r--spec/models/clusters/applications/knative_spec.rb6
-rw-r--r--spec/models/commit_spec.rb13
-rw-r--r--spec/models/commit_status_spec.rb19
-rw-r--r--spec/models/concerns/ci/artifactable_spec.rb19
-rw-r--r--spec/models/concerns/each_batch_spec.rb16
-rw-r--r--spec/models/concerns/milestoneable_spec.rb4
-rw-r--r--spec/models/concerns/noteable_spec.rb104
-rw-r--r--spec/models/cycle_analytics/code_spec.rb73
-rw-r--r--spec/models/cycle_analytics/issue_spec.rb46
-rw-r--r--spec/models/cycle_analytics/plan_spec.rb52
-rw-r--r--spec/models/cycle_analytics/project_level_spec.rb34
-rw-r--r--spec/models/cycle_analytics/project_level_stage_adapter_spec.rb38
-rw-r--r--spec/models/cycle_analytics/review_spec.rb34
-rw-r--r--spec/models/cycle_analytics/staging_spec.rb56
-rw-r--r--spec/models/cycle_analytics/test_spec.rb73
-rw-r--r--spec/models/deployment_spec.rb50
-rw-r--r--spec/models/experiment_spec.rb58
-rw-r--r--spec/models/group_spec.rb20
-rw-r--r--spec/models/list_spec.rb67
-rw-r--r--spec/models/members/group_member_spec.rb12
-rw-r--r--spec/models/merge_request_spec.rb148
-rw-r--r--spec/models/namespace/package_setting_spec.rb81
-rw-r--r--spec/models/namespace_onboarding_action_spec.rb53
-rw-r--r--spec/models/namespace_spec.rb23
-rw-r--r--spec/models/onboarding_progress_spec.rb107
-rw-r--r--spec/models/packages/debian/file_metadatum_spec.rb91
-rw-r--r--spec/models/packages/debian/group_architecture_spec.rb7
-rw-r--r--spec/models/packages/debian/group_distribution_spec.rb7
-rw-r--r--spec/models/packages/debian/project_architecture_spec.rb7
-rw-r--r--spec/models/packages/debian/project_distribution_spec.rb7
-rw-r--r--spec/models/packages/package_file_spec.rb1
-rw-r--r--spec/models/packages/package_spec.rb24
-rw-r--r--spec/models/project_import_state_spec.rb2
-rw-r--r--spec/models/project_pages_metadatum_spec.rb21
-rw-r--r--spec/models/project_services/alerts_service_spec.rb110
-rw-r--r--spec/models/project_services/jira_service_spec.rb53
-rw-r--r--spec/models/project_services/prometheus_service_spec.rb4
-rw-r--r--spec/models/project_spec.rb310
-rw-r--r--spec/models/project_wiki_spec.rb6
-rw-r--r--spec/models/protectable_dropdown_spec.rb32
-rw-r--r--spec/models/release_highlight_spec.rb2
-rw-r--r--spec/models/release_spec.rb12
-rw-r--r--spec/models/repository_spec.rb18
-rw-r--r--spec/models/snippet_repository_storage_move_spec.rb2
-rw-r--r--spec/models/snippet_spec.rb86
-rw-r--r--spec/models/terraform/state_spec.rb16
-rw-r--r--spec/models/user_spec.rb20
57 files changed, 1423 insertions, 1052 deletions
diff --git a/spec/models/alert_management/http_integration_spec.rb b/spec/models/alert_management/http_integration_spec.rb
index 910df51801a..ddd65e723eb 100644
--- a/spec/models/alert_management/http_integration_spec.rb
+++ b/spec/models/alert_management/http_integration_spec.rb
@@ -38,7 +38,7 @@ RSpec.describe AlertManagement::HttpIntegration do
context 'with valid JSON schema' do
let(:attribute_mapping) do
{
- title: { path: %w(a b c), type: 'string' },
+ title: { path: %w(a b c), type: 'string', label: 'Title' },
description: { path: %w(a), type: 'string' }
}
end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index ea03cbc3706..4755d700d72 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -72,6 +72,7 @@ RSpec.describe ApplicationSetting do
it { is_expected.not_to allow_value(nil).for(:push_event_activities_limit) }
it { is_expected.to validate_numericality_of(:container_registry_delete_tags_service_timeout).only_integer.is_greater_than_or_equal_to(0) }
+ it { is_expected.to validate_numericality_of(:container_registry_cleanup_tags_service_max_list_size).only_integer.is_greater_than_or_equal_to(0) }
it { is_expected.to validate_numericality_of(:container_registry_expiration_policies_worker_capacity).only_integer.is_greater_than_or_equal_to(0) }
it { is_expected.to validate_numericality_of(:snippet_size_limit).only_integer.is_greater_than(0) }
@@ -313,7 +314,7 @@ RSpec.describe ApplicationSetting do
it { is_expected.to validate_presence_of(:max_attachment_size) }
- it do
+ specify do
is_expected.to validate_numericality_of(:max_attachment_size)
.only_integer
.is_greater_than(0)
@@ -321,13 +322,13 @@ RSpec.describe ApplicationSetting do
it { is_expected.to validate_presence_of(:max_import_size) }
- it do
+ specify do
is_expected.to validate_numericality_of(:max_import_size)
.only_integer
.is_greater_than_or_equal_to(0)
end
- it do
+ specify do
is_expected.to validate_numericality_of(:local_markdown_version)
.only_integer
.is_greater_than_or_equal_to(0)
@@ -472,7 +473,7 @@ RSpec.describe ApplicationSetting do
end
[:gitaly_timeout_default, :gitaly_timeout_medium, :gitaly_timeout_fast].each do |timeout_name|
- it do
+ specify do
is_expected.to validate_presence_of(timeout_name)
is_expected.to validate_numericality_of(timeout_name).only_integer
.is_greater_than_or_equal_to(0)
@@ -733,6 +734,27 @@ RSpec.describe ApplicationSetting do
is_expected.to be_invalid
end
end
+
+ context 'throttle_* settings' do
+ where(:throttle_setting) do
+ %i[
+ throttle_unauthenticated_requests_per_period
+ throttle_unauthenticated_period_in_seconds
+ throttle_authenticated_api_requests_per_period
+ throttle_authenticated_api_period_in_seconds
+ throttle_authenticated_web_requests_per_period
+ throttle_authenticated_web_period_in_seconds
+ ]
+ end
+
+ with_them do
+ it { is_expected.to allow_value(3).for(throttle_setting) }
+ it { is_expected.not_to allow_value(-3).for(throttle_setting) }
+ it { is_expected.not_to allow_value(0).for(throttle_setting) }
+ it { is_expected.not_to allow_value('three').for(throttle_setting) }
+ it { is_expected.not_to allow_value(nil).for(throttle_setting) }
+ end
+ end
end
context 'restrict creating duplicates' do
@@ -821,7 +843,7 @@ RSpec.describe ApplicationSetting do
context 'validations' do
it { is_expected.to validate_presence_of(:diff_max_patch_bytes) }
- it do
+ specify do
is_expected.to validate_numericality_of(:diff_max_patch_bytes)
.only_integer
.is_greater_than_or_equal_to(Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES)
@@ -850,12 +872,13 @@ RSpec.describe ApplicationSetting do
end
end
- describe '#instance_review_permitted?', :request_store do
+ describe '#instance_review_permitted?', :request_store, :use_clean_rails_memory_store_caching do
subject { setting.instance_review_permitted? }
before do
- RequestStore.store[:current_license] = nil
- expect(Rails.cache).to receive(:fetch).and_return(
+ allow(License).to receive(:current).and_return(nil) if Gitlab.ee?
+ allow(Rails.cache).to receive(:fetch).and_call_original
+ expect(Rails.cache).to receive(:fetch).with('limited_users_count', anything).and_return(
::ApplicationSetting::INSTANCE_REVIEW_MIN_USERS + users_over_minimum
)
end
diff --git a/spec/models/audit_event_partitioned_spec.rb b/spec/models/audit_event_archived_spec.rb
index ab48e291f78..43a2e8434b0 100644
--- a/spec/models/audit_event_partitioned_spec.rb
+++ b/spec/models/audit_event_archived_spec.rb
@@ -2,35 +2,35 @@
require 'spec_helper'
-RSpec.describe AuditEventPartitioned do
+RSpec.describe AuditEventArchived do
let(:source_table) { AuditEvent }
- let(:partitioned_table) { described_class }
+ let(:destination_table) { described_class }
it 'has the same columns as the source table' do
column_names_from_source_table = column_names(source_table)
- column_names_from_partioned_table = column_names(partitioned_table)
+ column_names_from_destination_table = column_names(destination_table)
- expect(column_names_from_partioned_table).to match_array(column_names_from_source_table)
+ expect(column_names_from_destination_table).to match_array(column_names_from_source_table)
end
it 'has the same null constraints as the source table' do
constraints_from_source_table = null_constraints(source_table)
- constraints_from_partitioned_table = null_constraints(partitioned_table)
+ constraints_from_destination_table = null_constraints(destination_table)
- expect(constraints_from_partitioned_table.to_a).to match_array(constraints_from_source_table.to_a)
+ expect(constraints_from_destination_table.to_a).to match_array(constraints_from_source_table.to_a)
end
it 'inserts the same record as the one in the source table', :aggregate_failures do
- expect { create(:audit_event) }.to change { partitioned_table.count }.by(1)
+ expect { create(:audit_event) }.to change { destination_table.count }.by(1)
event_from_source_table = source_table.connection.select_one(
"SELECT * FROM #{source_table.table_name} ORDER BY created_at desc LIMIT 1"
)
- event_from_partitioned_table = partitioned_table.connection.select_one(
- "SELECT * FROM #{partitioned_table.table_name} ORDER BY created_at desc LIMIT 1"
+ event_from_destination_table = destination_table.connection.select_one(
+ "SELECT * FROM #{destination_table.table_name} ORDER BY created_at desc LIMIT 1"
)
- expect(event_from_partitioned_table).to eq(event_from_source_table)
+ expect(event_from_destination_table).to eq(event_from_source_table)
end
def column_names(table)
diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb
index 11dcecd50ca..4f09f6f1da4 100644
--- a/spec/models/ci/bridge_spec.rb
+++ b/spec/models/ci/bridge_spec.rb
@@ -356,14 +356,6 @@ RSpec.describe Ci::Bridge do
describe '#dependency_variables' do
subject { bridge.dependency_variables }
- shared_context 'when ci_bridge_dependency_variables is disabled' do
- before do
- stub_feature_flags(ci_bridge_dependency_variables: false)
- end
-
- it { is_expected.to be_empty }
- end
-
context 'when downloading from previous stages' do
let!(:prepare1) { create(:ci_build, name: 'prepare1', pipeline: pipeline, stage_idx: 0) }
let!(:bridge) { create(:ci_bridge, pipeline: pipeline, stage_idx: 1) }
@@ -374,8 +366,6 @@ RSpec.describe Ci::Bridge do
it 'inherits only dependent variables' do
expect(subject.to_hash).to eq(job_variable_1.key => job_variable_1.value)
end
-
- it_behaves_like 'when ci_bridge_dependency_variables is disabled'
end
context 'when using needs' do
@@ -397,8 +387,6 @@ RSpec.describe Ci::Bridge do
it 'inherits only needs with artifacts variables' do
expect(subject.to_hash).to eq(job_variable_1.key => job_variable_1.value)
end
-
- it_behaves_like 'when ci_bridge_dependency_variables is disabled'
end
end
end
diff --git a/spec/models/ci/build_need_spec.rb b/spec/models/ci/build_need_spec.rb
index 43cce073918..c2cf9027055 100644
--- a/spec/models/ci/build_need_spec.rb
+++ b/spec/models/ci/build_need_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Ci::BuildNeed, model: true do
let(:build_need) { build(:ci_build_need) }
- it { is_expected.to belong_to(:build) }
+ it { is_expected.to belong_to(:build).class_name('Ci::Processable') }
it { is_expected.to validate_presence_of(:build) }
it { is_expected.to validate_presence_of(:name) }
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 9f412d64d56..c2029b9240b 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -717,6 +717,22 @@ RSpec.describe Ci::Build do
end
end
+ describe '#artifacts_public?' do
+ subject { build.artifacts_public? }
+
+ context 'artifacts with defaults' do
+ let(:build) { create(:ci_build, :artifacts) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'non public artifacts' do
+ let(:build) { create(:ci_build, :artifacts, :non_public_artifacts) }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
describe '#artifacts_expired?' do
subject { build.artifacts_expired? }
@@ -1149,26 +1165,12 @@ RSpec.describe Ci::Build do
end
context 'when transits to skipped' do
- context 'when cd_skipped_deployment_status is disabled' do
- before do
- stub_feature_flags(cd_skipped_deployment_status: false)
- build.skip!
- end
-
- it 'transits deployment status to canceled' do
- expect(deployment).to be_canceled
- end
+ before do
+ build.skip!
end
- context 'when cd_skipped_deployment_status is enabled' do
- before do
- stub_feature_flags(cd_skipped_deployment_status: project)
- build.skip!
- end
-
- it 'transits deployment status to skipped' do
- expect(deployment).to be_skipped
- end
+ it 'transits deployment status to skipped' do
+ expect(deployment).to be_skipped
end
end
@@ -2456,6 +2458,7 @@ RSpec.describe Ci::Build do
{ key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true, masked: false },
{ key: 'CI_PROJECT_REPOSITORY_LANGUAGES', value: project.repository_languages.map(&:name).join(',').downcase, public: true, masked: false },
{ key: 'CI_DEFAULT_BRANCH', value: project.default_branch, public: true, masked: false },
+ { key: 'CI_PROJECT_CONFIG_PATH', value: project.ci_config_path_or_default, public: true, masked: false },
{ key: 'CI_PAGES_DOMAIN', value: Gitlab.config.pages.host, public: true, masked: false },
{ key: 'CI_PAGES_URL', value: project.pages_url, public: true, masked: false },
{ key: 'CI_DEPENDENCY_PROXY_SERVER', value: "#{Gitlab.config.gitlab.host}:#{Gitlab.config.gitlab.port}", public: true, masked: false },
@@ -2986,7 +2989,7 @@ RSpec.describe Ci::Build do
let(:ci_config_path) { { key: 'CI_CONFIG_PATH', value: 'custom', public: true, masked: false } }
before do
- expect_any_instance_of(Project).to receive(:ci_config_path) { 'custom' }
+ project.update!(ci_config_path: 'custom')
end
it { is_expected.to include(ci_config_path) }
@@ -4343,7 +4346,7 @@ RSpec.describe Ci::Build do
end
describe '#supported_runner?' do
- let_it_be(:build) { create(:ci_build) }
+ let_it_be_with_refind(:build) { create(:ci_build) }
subject { build.supported_runner?(runner_features) }
@@ -4408,6 +4411,41 @@ RSpec.describe Ci::Build do
it { is_expected.to be_falsey }
end
end
+
+ context 'when `return_exit_code` feature is required by build' do
+ let(:options) { { allow_failure_criteria: { exit_codes: [1] } } }
+
+ before do
+ build.update!(options: options)
+ end
+
+ context 'when runner provides given feature' do
+ let(:runner_features) { { return_exit_code: true } }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when runner does not provide given feature' do
+ let(:runner_features) { {} }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when the runner does not provide all of the required features' do
+ let(:options) do
+ {
+ allow_failure_criteria: { exit_codes: [1] },
+ artifacts: { reports: { junit: "junit.xml" } }
+ }
+ end
+
+ let(:runner_features) { { return_exit_code: true } }
+
+ it 'requires `upload_multiple_artifacts` too' do
+ is_expected.to be_falsey
+ end
+ end
+ end
end
describe '#deployment_status' do
@@ -4737,22 +4775,6 @@ RSpec.describe Ci::Build do
describe '#debug_mode?' do
subject { build.debug_mode? }
- context 'when feature is disabled' do
- before do
- stub_feature_flags(restrict_access_to_build_debug_mode: false)
- end
-
- it { is_expected.to eq false }
-
- context 'when in variables' do
- before do
- create(:ci_instance_variable, key: 'CI_DEBUG_TRACE', value: 'true')
- end
-
- it { is_expected.to eq false }
- end
- end
-
context 'when CI_DEBUG_TRACE=true is in variables' do
context 'when in instance variables' do
before do
@@ -4807,4 +4829,159 @@ RSpec.describe Ci::Build do
it { is_expected.to eq false }
end
end
+
+ describe '#drop_with_exit_code!' do
+ let(:exit_code) { 1 }
+ let(:options) { {} }
+
+ before do
+ build.options.merge!(options)
+ build.save!
+ end
+
+ subject(:drop_with_exit_code) do
+ build.drop_with_exit_code!(:unknown_failure, exit_code)
+ end
+
+ shared_examples 'drops the build without changing allow_failure' do
+ it 'does not change allow_failure' do
+ expect { drop_with_exit_code }
+ .not_to change { build.reload.allow_failure }
+ end
+
+ it 'drops the build' do
+ expect { drop_with_exit_code }
+ .to change { build.reload.failed? }
+ end
+ end
+
+ context 'when exit_codes are not defined' do
+ it_behaves_like 'drops the build without changing allow_failure'
+ end
+
+ context 'when allow_failure_criteria is nil' do
+ let(:options) { { allow_failure_criteria: nil } }
+
+ it_behaves_like 'drops the build without changing allow_failure'
+ end
+
+ context 'when exit_codes is nil' do
+ let(:options) do
+ {
+ allow_failure_criteria: {
+ exit_codes: nil
+ }
+ }
+ end
+
+ it_behaves_like 'drops the build without changing allow_failure'
+ end
+
+ context 'when exit_codes do not match' do
+ let(:options) do
+ {
+ allow_failure_criteria: {
+ exit_codes: [2, 3, 4]
+ }
+ }
+ end
+
+ it_behaves_like 'drops the build without changing allow_failure'
+ end
+
+ context 'with matching exit codes' do
+ let(:options) do
+ { allow_failure_criteria: { exit_codes: [1, 2, 3] } }
+ end
+
+ it 'changes allow_failure' do
+ expect { drop_with_exit_code }
+ .to change { build.reload.allow_failure }
+ end
+
+ it 'drops the build' do
+ expect { drop_with_exit_code }
+ .to change { build.reload.failed? }
+ end
+
+ it 'is executed inside a transaction' do
+ expect(build).to receive(:drop!)
+ .with(:unknown_failure)
+ .and_raise(ActiveRecord::Rollback)
+
+ expect(build).to receive(:conditionally_allow_failure!)
+ .with(1)
+ .and_call_original
+
+ expect { drop_with_exit_code }
+ .not_to change { build.reload.allow_failure }
+ end
+
+ context 'when exit_code is nil' do
+ let(:exit_code) {}
+
+ it_behaves_like 'drops the build without changing allow_failure'
+ end
+
+ context 'when ci_allow_failure_with_exit_codes is disabled' do
+ before do
+ stub_feature_flags(ci_allow_failure_with_exit_codes: false)
+ end
+
+ it_behaves_like 'drops the build without changing allow_failure'
+ end
+ end
+ end
+
+ describe '#exit_codes_defined?' do
+ let(:options) { {} }
+
+ before do
+ build.options.merge!(options)
+ end
+
+ subject(:exit_codes_defined) do
+ build.exit_codes_defined?
+ end
+
+ context 'without allow_failure_criteria' do
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when exit_codes is nil' do
+ let(:options) do
+ {
+ allow_failure_criteria: {
+ exit_codes: nil
+ }
+ }
+ end
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when exit_codes is an empty array' do
+ let(:options) do
+ {
+ allow_failure_criteria: {
+ exit_codes: []
+ }
+ }
+ end
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when exit_codes are defined' do
+ let(:options) do
+ {
+ allow_failure_criteria: {
+ exit_codes: [5, 6]
+ }
+ }
+ end
+
+ it { is_expected.to be_truthy }
+ end
+ end
end
diff --git a/spec/models/commit_with_pipeline_spec.rb b/spec/models/ci/commit_with_pipeline_spec.rb
index c4b6deebae0..4dd288bde62 100644
--- a/spec/models/commit_with_pipeline_spec.rb
+++ b/spec/models/ci/commit_with_pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe CommitWithPipeline do
+RSpec.describe Ci::CommitWithPipeline do
let(:project) { create(:project, :public, :repository) }
let(:commit) { described_class.new(project.commit) }
diff --git a/spec/models/ci/group_spec.rb b/spec/models/ci/group_spec.rb
index c20b7e61044..6c96e659a34 100644
--- a/spec/models/ci/group_spec.rb
+++ b/spec/models/ci/group_spec.rb
@@ -54,6 +54,18 @@ RSpec.describe Ci::Group do
.to be_a(Gitlab::Ci::Status::Failed)
end
end
+
+ context 'when one of the commit statuses in the group is allowed to fail' do
+ let(:jobs) do
+ [create(:ci_build, :failed, :allowed_to_fail),
+ create(:ci_build, :success)]
+ end
+
+ it 'fabricates a new detailed status object' do
+ expect(subject.detailed_status(double(:user)))
+ .to be_a(Gitlab::Ci::Status::SuccessWarning)
+ end
+ end
end
describe '.fabricate' do
diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb
index ef21ca8f100..796947be4c8 100644
--- a/spec/models/ci/job_artifact_spec.rb
+++ b/spec/models/ci/job_artifact_spec.rb
@@ -219,6 +219,39 @@ RSpec.describe Ci::JobArtifact do
end
end
+ describe '.unlocked' do
+ let_it_be(:job_artifact) { create(:ci_job_artifact) }
+
+ context 'with locked pipelines' do
+ before do
+ job_artifact.job.pipeline.artifacts_locked!
+ end
+
+ it 'returns an empty array' do
+ expect(described_class.unlocked).to be_empty
+ end
+ end
+
+ context 'with unlocked pipelines' do
+ before do
+ job_artifact.job.pipeline.unlocked!
+ end
+
+ it 'returns the artifact' do
+ expect(described_class.unlocked).to eq([job_artifact])
+ end
+ end
+ end
+
+ describe '.order_expired_desc' do
+ let_it_be(:first_artifact) { create(:ci_job_artifact, expire_at: 2.days.ago) }
+ let_it_be(:second_artifact) { create(:ci_job_artifact, expire_at: 1.day.ago) }
+
+ it 'returns ordered artifacts' do
+ expect(described_class.order_expired_desc).to eq([second_artifact, first_artifact])
+ end
+ end
+
describe 'callbacks' do
describe '#schedule_background_upload' do
subject { create(:ci_job_artifact, :archive) }
diff --git a/spec/models/ci/ref_spec.rb b/spec/models/ci/ref_spec.rb
index cb62646532c..0a9cd5ef2ec 100644
--- a/spec/models/ci/ref_spec.rb
+++ b/spec/models/ci/ref_spec.rb
@@ -16,35 +16,49 @@ RSpec.describe Ci::Ref do
stub_const('Ci::PipelineSuccessUnlockArtifactsWorker', unlock_artifacts_worker_spy)
end
- where(:initial_state, :action, :count) do
- :unknown | :succeed! | 1
- :unknown | :do_fail! | 0
- :success | :succeed! | 1
- :success | :do_fail! | 0
- :failed | :succeed! | 1
- :failed | :do_fail! | 0
- :fixed | :succeed! | 1
- :fixed | :do_fail! | 0
- :broken | :succeed! | 1
- :broken | :do_fail! | 0
- :still_failing | :succeed | 1
- :still_failing | :do_fail | 0
- end
+ context 'pipline is locked' do
+ let!(:pipeline) { create(:ci_pipeline, ci_ref_id: ci_ref.id, locked: :artifacts_locked) }
+
+ where(:initial_state, :action, :count) do
+ :unknown | :succeed! | 1
+ :unknown | :do_fail! | 0
+ :success | :succeed! | 1
+ :success | :do_fail! | 0
+ :failed | :succeed! | 1
+ :failed | :do_fail! | 0
+ :fixed | :succeed! | 1
+ :fixed | :do_fail! | 0
+ :broken | :succeed! | 1
+ :broken | :do_fail! | 0
+ :still_failing | :succeed | 1
+ :still_failing | :do_fail | 0
+ end
- with_them do
- context "when transitioning states" do
- before do
- status_value = Ci::Ref.state_machines[:status].states[initial_state].value
- ci_ref.update!(status: status_value)
- end
+ with_them do
+ context "when transitioning states" do
+ before do
+ status_value = Ci::Ref.state_machines[:status].states[initial_state].value
+ ci_ref.update!(status: status_value)
+ end
- it 'calls unlock artifacts service' do
- ci_ref.send(action)
+ it 'calls unlock artifacts service' do
+ ci_ref.send(action)
- expect(unlock_artifacts_worker_spy).to have_received(:perform_async).exactly(count).times
+ expect(unlock_artifacts_worker_spy).to have_received(:perform_async).exactly(count).times
+ end
end
end
end
+
+ context 'pipeline is unlocked' do
+ let!(:pipeline) { create(:ci_pipeline, ci_ref_id: ci_ref.id, locked: :unlocked) }
+
+ it 'does not call unlock artifacts service' do
+ ci_ref.succeed!
+
+ expect(unlock_artifacts_worker_spy).not_to have_received(:perform_async)
+ end
+ end
end
end
diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb
index 41b4ec86233..d0e470bfa42 100644
--- a/spec/models/clusters/applications/knative_spec.rb
+++ b/spec/models/clusters/applications/knative_spec.rb
@@ -150,7 +150,7 @@ RSpec.describe Clusters::Applications::Knative do
subject { knative.install_command }
it 'is initialized with latest version' do
- expect(subject.version).to eq('0.9.0')
+ expect(subject.version).to eq('0.10.0')
end
it_behaves_like 'a command'
@@ -204,8 +204,8 @@ RSpec.describe Clusters::Applications::Knative do
expect(subject.postdelete).to include(*remove_knative_istio_leftovers_script)
expect(subject.postdelete.size).to eq(full_delete_commands_size)
- expect(subject.postdelete[2]).to eq("kubectl api-resources -o name --api-group #{api_groups[0]} | xargs kubectl delete --ignore-not-found crd")
- expect(subject.postdelete[3]).to eq("kubectl api-resources -o name --api-group #{api_groups[1]} | xargs kubectl delete --ignore-not-found crd")
+ expect(subject.postdelete[2]).to include("kubectl api-resources -o name --api-group #{api_groups[0]} | xargs -r kubectl delete --ignore-not-found crd")
+ expect(subject.postdelete[3]).to include("kubectl api-resources -o name --api-group #{api_groups[1]} | xargs -r kubectl delete --ignore-not-found crd")
end
end
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index cfa87b3e39e..acbabee9383 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -428,6 +428,19 @@ eos
allow(commit).to receive(:safe_message).and_return(message)
expect(commit.description).to eq(message)
end
+
+ it 'truncates html representation if more than 1Mib' do
+ # Commit message is over 2MiB
+ huge_commit_message = ['panic', ('panic ' * 350000), 'trailing text'].join("\n")
+
+ allow(commit).to receive(:safe_message).and_return(huge_commit_message)
+
+ commit.refresh_markdown_cache
+ description_html = commit.description_html
+
+ expect(description_html.bytesize).to be < 2.megabytes
+ expect(description_html).not_to include('trailing text')
+ end
end
describe "delegation" do
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 9824eb91bc7..532f68c2f18 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -61,6 +61,22 @@ RSpec.describe CommitStatus do
expect(commit_status.started_at).to be_present
end
end
+
+ describe 'transitioning to created from skipped or manual' do
+ let(:commit_status) { create(:commit_status, :skipped) }
+
+ it 'does not update user without parameter' do
+ commit_status.process!
+
+ expect { commit_status.process }.not_to change { commit_status.reload.user }
+ end
+
+ it 'updates user with user parameter' do
+ new_user = create(:user)
+
+ expect { commit_status.process(new_user) }.to change { commit_status.reload.user }.to(new_user)
+ end
+ end
end
describe '#processed' do
@@ -503,6 +519,9 @@ RSpec.describe CommitStatus do
subject { commit_status.group_name }
where(:name, :group_name) do
+ 'rspec1' | 'rspec1'
+ 'rspec1 0 1' | 'rspec1'
+ 'rspec1 0/2' | 'rspec1'
'rspec:windows' | 'rspec:windows'
'rspec:windows 0' | 'rspec:windows 0'
'rspec:windows 0 test' | 'rspec:windows 0 test'
diff --git a/spec/models/concerns/ci/artifactable_spec.rb b/spec/models/concerns/ci/artifactable_spec.rb
index f05189abdd2..ebc838e86a6 100644
--- a/spec/models/concerns/ci/artifactable_spec.rb
+++ b/spec/models/concerns/ci/artifactable_spec.rb
@@ -54,4 +54,23 @@ RSpec.describe Ci::Artifactable do
end
end
end
+
+ context 'ActiveRecord scopes' do
+ let_it_be(:recently_expired_artifact) { create(:ci_job_artifact, expire_at: 1.day.ago) }
+ let_it_be(:later_expired_artifact) { create(:ci_job_artifact, expire_at: 2.days.ago) }
+ let_it_be(:not_expired_artifact) { create(:ci_job_artifact, expire_at: 1.day.from_now) }
+
+ describe '.expired_before' do
+ it 'returns expired artifacts' do
+ expect(Ci::JobArtifact.expired_before(1.hour.ago))
+ .to match_array([recently_expired_artifact, later_expired_artifact])
+ end
+ end
+
+ describe '.expired' do
+ it 'returns a limited number of expired artifacts' do
+ expect(Ci::JobArtifact.expired(1).order_id_asc).to eq([recently_expired_artifact])
+ end
+ end
+ end
end
diff --git a/spec/models/concerns/each_batch_spec.rb b/spec/models/concerns/each_batch_spec.rb
index 8b70753633c..5f4e5d4bd98 100644
--- a/spec/models/concerns/each_batch_spec.rb
+++ b/spec/models/concerns/each_batch_spec.rb
@@ -56,5 +56,21 @@ RSpec.describe EachBatch do
it_behaves_like 'each_batch handling', {}
it_behaves_like 'each_batch handling', { order_hint: :updated_at }
+
+ it 'orders ascending by default' do
+ ids = []
+
+ model.each_batch(of: 1) { |rel| ids.concat(rel.ids) }
+
+ expect(ids).to eq(ids.sort)
+ end
+
+ it 'accepts descending order' do
+ ids = []
+
+ model.each_batch(of: 1, order: :desc) { |rel| ids.concat(rel.ids) }
+
+ expect(ids).to eq(ids.sort.reverse)
+ end
end
end
diff --git a/spec/models/concerns/milestoneable_spec.rb b/spec/models/concerns/milestoneable_spec.rb
index c37582cb65d..5fb3b39f734 100644
--- a/spec/models/concerns/milestoneable_spec.rb
+++ b/spec/models/concerns/milestoneable_spec.rb
@@ -104,8 +104,8 @@ RSpec.describe Milestoneable do
context "for incidents" do
let(:incident) { build(:incident) }
- it 'returns false' do
- expect(incident.supports_milestone?).to be_falsy
+ it 'returns true' do
+ expect(incident.supports_milestone?).to be_truthy
end
end
end
diff --git a/spec/models/concerns/noteable_spec.rb b/spec/models/concerns/noteable_spec.rb
index bb7374bf46c..a7117af81a2 100644
--- a/spec/models/concerns/noteable_spec.rb
+++ b/spec/models/concerns/noteable_spec.rb
@@ -25,8 +25,8 @@ RSpec.describe Noteable do
let(:active_position2) do
Gitlab::Diff::Position.new(
- old_path: "files/ruby/popen.rb",
- new_path: "files/ruby/popen.rb",
+ old_path: 'files/ruby/popen.rb',
+ new_path: 'files/ruby/popen.rb',
old_line: 16,
new_line: 22,
diff_refs: subject.diff_refs
@@ -35,11 +35,11 @@ RSpec.describe Noteable do
let(:outdated_position) do
Gitlab::Diff::Position.new(
- old_path: "files/ruby/popen.rb",
- new_path: "files/ruby/popen.rb",
+ old_path: 'files/ruby/popen.rb',
+ new_path: 'files/ruby/popen.rb',
old_line: nil,
new_line: 9,
- diff_refs: project.commit("874797c3a73b60d2187ed6e2fcabd289ff75171e").diff_refs
+ diff_refs: project.commit('874797c3a73b60d2187ed6e2fcabd289ff75171e').diff_refs
)
end
@@ -80,7 +80,7 @@ RSpec.describe Noteable do
describe '#grouped_diff_discussions' do
let(:grouped_diff_discussions) { subject.grouped_diff_discussions }
- it "includes active discussions" do
+ it 'includes active discussions' do
discussions = grouped_diff_discussions.values.flatten
expect(discussions.count).to eq(2)
@@ -91,17 +91,17 @@ RSpec.describe Noteable do
expect(discussions.last.notes).to eq([active_diff_note3])
end
- it "doesn't include outdated discussions" do
+ it 'does not include outdated discussions' do
expect(grouped_diff_discussions.values.flatten.map(&:id)).not_to include(outdated_diff_note1.discussion_id)
end
- it "groups the discussions by line code" do
+ it 'groups the discussions by line code' do
expect(grouped_diff_discussions[active_diff_note1.line_code].first.id).to eq(active_diff_note1.discussion_id)
expect(grouped_diff_discussions[active_diff_note3.line_code].first.id).to eq(active_diff_note3.discussion_id)
end
end
- context "discussion status" do
+ context 'discussion status' do
let(:first_discussion) { build_stubbed(:discussion_note_on_merge_request, noteable: subject, project: project).to_discussion }
let(:second_discussion) { build_stubbed(:discussion_note_on_merge_request, noteable: subject, project: project).to_discussion }
let(:third_discussion) { build_stubbed(:discussion_note_on_merge_request, noteable: subject, project: project).to_discussion }
@@ -110,56 +110,56 @@ RSpec.describe Noteable do
allow(subject).to receive(:resolvable_discussions).and_return([first_discussion, second_discussion, third_discussion])
end
- describe "#discussions_resolvable?" do
- context "when all discussions are unresolvable" do
+ describe '#discussions_resolvable?' do
+ context 'when all discussions are unresolvable' do
before do
allow(first_discussion).to receive(:resolvable?).and_return(false)
allow(second_discussion).to receive(:resolvable?).and_return(false)
allow(third_discussion).to receive(:resolvable?).and_return(false)
end
- it "returns false" do
+ it 'returns false' do
expect(subject.discussions_resolvable?).to be false
end
end
- context "when some discussions are unresolvable and some discussions are resolvable" do
+ context 'when some discussions are unresolvable and some discussions are resolvable' do
before do
allow(first_discussion).to receive(:resolvable?).and_return(true)
allow(second_discussion).to receive(:resolvable?).and_return(false)
allow(third_discussion).to receive(:resolvable?).and_return(true)
end
- it "returns true" do
+ it 'returns true' do
expect(subject.discussions_resolvable?).to be true
end
end
- context "when all discussions are resolvable" do
+ context 'when all discussions are resolvable' do
before do
allow(first_discussion).to receive(:resolvable?).and_return(true)
allow(second_discussion).to receive(:resolvable?).and_return(true)
allow(third_discussion).to receive(:resolvable?).and_return(true)
end
- it "returns true" do
+ it 'returns true' do
expect(subject.discussions_resolvable?).to be true
end
end
end
- describe "#discussions_resolved?" do
- context "when discussions are not resolvable" do
+ describe '#discussions_resolved?' do
+ context 'when discussions are not resolvable' do
before do
allow(subject).to receive(:discussions_resolvable?).and_return(false)
end
- it "returns false" do
+ it 'returns false' do
expect(subject.discussions_resolved?).to be false
end
end
- context "when discussions are resolvable" do
+ context 'when discussions are resolvable' do
before do
allow(subject).to receive(:discussions_resolvable?).and_return(true)
@@ -168,31 +168,31 @@ RSpec.describe Noteable do
allow(third_discussion).to receive(:resolvable?).and_return(true)
end
- context "when all resolvable discussions are resolved" do
+ context 'when all resolvable discussions are resolved' do
before do
allow(first_discussion).to receive(:resolved?).and_return(true)
allow(third_discussion).to receive(:resolved?).and_return(true)
end
- it "returns true" do
+ it 'returns true' do
expect(subject.discussions_resolved?).to be true
end
end
- context "when some resolvable discussions are not resolved" do
+ context 'when some resolvable discussions are not resolved' do
before do
allow(first_discussion).to receive(:resolved?).and_return(true)
allow(third_discussion).to receive(:resolved?).and_return(false)
end
- it "returns false" do
+ it 'returns false' do
expect(subject.discussions_resolved?).to be false
end
end
end
end
- describe "#discussions_to_be_resolved" do
+ describe '#discussions_to_be_resolved' do
before do
allow(first_discussion).to receive(:to_be_resolved?).and_return(true)
allow(second_discussion).to receive(:to_be_resolved?).and_return(false)
@@ -245,6 +245,12 @@ RSpec.describe Noteable do
end
end
+ describe '.email_creatable_types' do
+ it 'exposes the email creatable types' do
+ expect(described_class.email_creatable_types).to include('Issue')
+ end
+ end
+
describe '#capped_notes_count' do
context 'notes number < 10' do
it 'the number of notes is returned' do
@@ -263,13 +269,13 @@ RSpec.describe Noteable do
end
end
- describe "#has_any_diff_note_positions?" do
- let(:source_branch) { "compare-with-merge-head-source" }
- let(:target_branch) { "compare-with-merge-head-target" }
+ describe '#has_any_diff_note_positions?' do
+ let(:source_branch) { 'compare-with-merge-head-source' }
+ let(:target_branch) { 'compare-with-merge-head-target' }
let(:merge_request) { create(:merge_request, source_branch: source_branch, target_branch: target_branch) }
let!(:note) do
- path = "files/markdown/ruby-style-guide.md"
+ path = 'files/markdown/ruby-style-guide.md'
position = Gitlab::Diff::Position.new(
old_path: path,
@@ -286,20 +292,54 @@ RSpec.describe Noteable do
Discussions::CaptureDiffNotePositionsService.new(merge_request).execute
end
- it "returns true when it has diff note positions" do
+ it 'returns true when it has diff note positions' do
expect(merge_request.has_any_diff_note_positions?).to be(true)
end
- it "returns false when it has notes but no diff note positions" do
+ it 'returns false when it has notes but no diff note positions' do
DiffNotePosition.where(note: note).find_each(&:delete)
expect(merge_request.has_any_diff_note_positions?).to be(false)
end
- it "returns false when it has no notes" do
+ it 'returns false when it has no notes' do
merge_request.notes.find_each(&:destroy)
expect(merge_request.has_any_diff_note_positions?).to be(false)
end
end
+
+ describe '#creatable_note_email_address' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:source_branch) { 'compare-with-merge-head-source' }
+
+ let(:issue) { create(:issue, project: project) }
+ let(:snippet) { build(:snippet) }
+
+ context 'incoming email enabled' do
+ before do
+ stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab")
+ end
+
+ it 'returns the address to create a note' do
+ address = "p+#{project.full_path_slug}-#{project.project_id}-#{user.incoming_email_token}-issue-#{issue.iid}@gl.ab"
+
+ expect(issue.creatable_note_email_address(user)).to eq(address)
+ end
+
+ it 'returns nil for unsupported types' do
+ expect(snippet.creatable_note_email_address(user)).to be_nil
+ end
+ end
+
+ context 'incoming email disabled' do
+ before do
+ stub_incoming_email_setting(enabled: false)
+ end
+
+ it 'returns nil' do
+ expect(issue.creatable_note_email_address(user)).to be_nil
+ end
+ end
+ end
end
diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb
deleted file mode 100644
index ca612cba654..00000000000
--- a/spec/models/cycle_analytics/code_spec.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'CycleAnalytics#code' do
- extend CycleAnalyticsHelpers::TestGeneration
-
- let_it_be(:project) { create(:project, :repository) }
- let_it_be(:from_date) { 10.days.ago }
- let_it_be(:user) { project.owner }
- let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date, current_user: user }) }
-
- subject { project_level }
-
- context 'with deployment' do
- generate_cycle_analytics_spec(
- phase: :code,
- data_fn: -> (context) { { issue: context.create(:issue, project: context.project) } },
- start_time_conditions: [["issue mentioned in a commit",
- -> (context, data) do
- context.create_commit_referencing_issue(data[:issue])
- end]],
- end_time_conditions: [["merge request that closes issue is created",
- -> (context, data) do
- context.create_merge_request_closing_issue(context.user, context.project, data[:issue])
- end]],
- post_fn: -> (context, data) do
- end)
-
- context "when a regular merge request (that doesn't close the issue) is created" do
- it "returns nil" do
- issue = create(:issue, project: project)
-
- create_commit_referencing_issue(issue)
- create_merge_request_closing_issue(user, project, issue, message: "Closes nothing")
-
- merge_merge_requests_closing_issue(user, project, issue)
- deploy_master(user, project)
-
- expect(subject[:code].project_median).to be_nil
- end
- end
- end
-
- context 'without deployment' do
- generate_cycle_analytics_spec(
- phase: :code,
- data_fn: -> (context) { { issue: context.create(:issue, project: context.project) } },
- start_time_conditions: [["issue mentioned in a commit",
- -> (context, data) do
- context.create_commit_referencing_issue(data[:issue])
- end]],
- end_time_conditions: [["merge request that closes issue is created",
- -> (context, data) do
- context.create_merge_request_closing_issue(context.user, context.project, data[:issue])
- end]],
- post_fn: -> (context, data) do
- end)
-
- context "when a regular merge request (that doesn't close the issue) is created" do
- it "returns nil" do
- issue = create(:issue, project: project)
-
- create_commit_referencing_issue(issue)
- create_merge_request_closing_issue(user, project, issue, message: "Closes nothing")
-
- merge_merge_requests_closing_issue(user, project, issue)
-
- expect(subject[:code].project_median).to be_nil
- end
- end
- end
-end
diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb
deleted file mode 100644
index 66d21f6925f..00000000000
--- a/spec/models/cycle_analytics/issue_spec.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'CycleAnalytics#issue' do
- extend CycleAnalyticsHelpers::TestGeneration
-
- let_it_be(:project) { create(:project, :repository) }
- let_it_be(:from_date) { 10.days.ago }
- let_it_be(:user) { project.owner }
- let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date, current_user: user }) }
-
- subject { project_level }
-
- generate_cycle_analytics_spec(
- phase: :issue,
- data_fn: -> (context) { { issue: context.build(:issue, project: context.project) } },
- start_time_conditions: [["issue created", -> (context, data) { data[:issue].save! }]],
- end_time_conditions: [["issue associated with a milestone",
- -> (context, data) do
- if data[:issue].persisted?
- data[:issue].update!(milestone: context.create(:milestone, project: context.project))
- end
- end],
- ["list label added to issue",
- -> (context, data) do
- if data[:issue].persisted?
- data[:issue].update!(label_ids: [context.create(:list).label_id])
- end
- end]],
- post_fn: -> (context, data) do
- end)
-
- context "when a regular label (instead of a list label) is added to the issue" do
- it "returns nil" do
- regular_label = create(:label)
- issue = create(:issue, project: project)
- issue.update!(label_ids: [regular_label.id])
-
- create_merge_request_closing_issue(user, project, issue)
- merge_merge_requests_closing_issue(user, project, issue)
-
- expect(subject[:issue].project_median).to be_nil
- end
- end
-end
diff --git a/spec/models/cycle_analytics/plan_spec.rb b/spec/models/cycle_analytics/plan_spec.rb
deleted file mode 100644
index acaf767db01..00000000000
--- a/spec/models/cycle_analytics/plan_spec.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'CycleAnalytics#plan' do
- extend CycleAnalyticsHelpers::TestGeneration
-
- let_it_be(:project) { create(:project, :repository) }
- let_it_be(:from_date) { 10.days.ago }
- let_it_be(:user) { project.owner }
- let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date, current_user: user }) }
-
- subject { project_level }
-
- generate_cycle_analytics_spec(
- phase: :plan,
- data_fn: -> (context) do
- {
- issue: context.build(:issue, project: context.project),
- branch_name: context.generate(:branch)
- }
- end,
- start_time_conditions: [["issue associated with a milestone",
- -> (context, data) do
- data[:issue].update!(milestone: context.create(:milestone, project: context.project))
- end],
- ["list label added to issue",
- -> (context, data) do
- data[:issue].update!(label_ids: [context.create(:list).label_id])
- end]],
- end_time_conditions: [["issue mentioned in a commit",
- -> (context, data) do
- context.create_commit_referencing_issue(data[:issue], branch_name: data[:branch_name])
- end]],
- post_fn: -> (context, data) do
- end)
-
- context "when a regular label (instead of a list label) is added to the issue" do
- it "returns nil" do
- branch_name = generate(:branch)
- label = create(:label)
- issue = create(:issue, project: project)
- issue.update!(label_ids: [label.id])
- create_commit_referencing_issue(issue, branch_name: branch_name)
-
- create_merge_request_closing_issue(user, project, issue, source_branch: branch_name)
- merge_merge_requests_closing_issue(user, project, issue)
-
- expect(subject[:issue].project_median).to be_nil
- end
- end
-end
diff --git a/spec/models/cycle_analytics/project_level_spec.rb b/spec/models/cycle_analytics/project_level_spec.rb
deleted file mode 100644
index c2d421c03d8..00000000000
--- a/spec/models/cycle_analytics/project_level_spec.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe CycleAnalytics::ProjectLevel do
- let_it_be(:project) { create(:project, :repository) }
- let_it_be(:from_date) { 10.days.ago }
- let_it_be(:user) { project.owner }
- let_it_be(:issue) { create(:issue, project: project, created_at: 2.days.ago) }
- let_it_be(:milestone) { create(:milestone, project: project) }
- let(:mr) { create_merge_request_closing_issue(user, project, issue, commit_message: "References #{issue.to_reference}") }
- let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha, head_pipeline_of: mr) }
-
- subject { described_class.new(project, options: { from: from_date }) }
-
- describe '#all_medians_by_stage' do
- before do
- allow_next_instance_of(Gitlab::ReferenceExtractor) do |instance|
- allow(instance).to receive(:issues).and_return([issue])
- end
-
- create_cycle(user, project, issue, mr, milestone, pipeline)
- deploy_master(user, project)
- end
-
- it 'returns every median for each stage for a specific project' do
- values = described_class::STAGES.each_with_object({}) do |stage_name, hsh|
- hsh[stage_name] = subject[stage_name].project_median.presence
- end
-
- expect(subject.all_medians_by_stage).to eq(values)
- end
- end
-end
diff --git a/spec/models/cycle_analytics/project_level_stage_adapter_spec.rb b/spec/models/cycle_analytics/project_level_stage_adapter_spec.rb
new file mode 100644
index 00000000000..9bdee292938
--- /dev/null
+++ b/spec/models/cycle_analytics/project_level_stage_adapter_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe CycleAnalytics::ProjectLevelStageAdapter, type: :model do
+ let_it_be(:stage_name) { :review } # pre-defined, default stage
+ let_it_be(:merge_request) do
+ create(:merge_request, created_at: 5.hours.ago).tap do |mr|
+ mr.metrics.update!(merged_at: mr.created_at + 1.hour)
+ end
+ end
+
+ let_it_be(:project) { merge_request.target_project }
+
+ let(:stage) do
+ params = Gitlab::Analytics::CycleAnalytics::DefaultStages.find_by_name!(stage_name).merge(project: project)
+ Analytics::CycleAnalytics::ProjectStage.new(params)
+ end
+
+ around do |example|
+ freeze_time { example.run }
+ end
+
+ subject { described_class.new(stage, from: 1.month.ago, to: Time.zone.now, current_user: merge_request.author) }
+
+ it 'calculates median' do
+ expect(subject.median).to be_within(1.hour).of(0.5)
+ end
+
+ it 'lists events' do
+ expect(subject.events.size).to eq(1)
+ expect(subject.events.first[:title]).to eq(merge_request.title)
+ end
+
+ it 'presents the data as json' do
+ expect(subject.as_json).to include({ title: 'Review', value: 'about 1 hour' })
+ end
+end
diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb
deleted file mode 100644
index 06d9cfbf8c0..00000000000
--- a/spec/models/cycle_analytics/review_spec.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'CycleAnalytics#review' do
- extend CycleAnalyticsHelpers::TestGeneration
-
- let_it_be(:project) { create(:project, :repository) }
- let_it_be(:from_date) { 10.days.ago }
- let_it_be(:user) { project.owner }
-
- subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date, current_user: user }) }
-
- generate_cycle_analytics_spec(
- phase: :review,
- data_fn: -> (context) { { issue: context.create(:issue, project: context.project) } },
- start_time_conditions: [["merge request that closes issue is created",
- -> (context, data) do
- context.create_merge_request_closing_issue(context.user, context.project, data[:issue])
- end]],
- end_time_conditions: [["merge request that closes issue is merged",
- -> (context, data) do
- context.merge_merge_requests_closing_issue(context.user, context.project, data[:issue])
- end]],
- post_fn: nil)
-
- context "when a regular merge request (that doesn't close the issue) is created and merged" do
- it "returns nil" do
- MergeRequests::MergeService.new(project, user).execute(create(:merge_request))
-
- expect(subject[:review].project_median).to be_nil
- end
- end
-end
diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb
deleted file mode 100644
index 50cb49d6309..00000000000
--- a/spec/models/cycle_analytics/staging_spec.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'CycleAnalytics#staging' do
- extend CycleAnalyticsHelpers::TestGeneration
-
- let_it_be(:project) { create(:project, :repository) }
- let_it_be(:from_date) { 10.days.ago }
- let_it_be(:user) { project.owner }
- let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date, current_user: user }) }
-
- subject { project_level }
-
- generate_cycle_analytics_spec(
- phase: :staging,
- data_fn: lambda do |context|
- issue = context.create(:issue, project: context.project)
- { issue: issue, merge_request: context.create_merge_request_closing_issue(context.user, context.project, issue) }
- end,
- start_time_conditions: [["merge request that closes issue is merged",
- -> (context, data) do
- context.merge_merge_requests_closing_issue(context.user, context.project, data[:issue])
- end]],
- end_time_conditions: [["merge request that closes issue is deployed to production",
- -> (context, data) do
- context.deploy_master(context.user, context.project)
- end],
- ["production deploy happens after merge request is merged (along with other changes)",
- lambda do |context, data|
- # Make other changes on master
- context.project.repository.commit("this_sha_apparently_does_not_matter")
- context.deploy_master(context.user, context.project)
- end]])
-
- context "when a regular merge request (that doesn't close the issue) is merged and deployed" do
- it "returns nil" do
- merge_request = create(:merge_request)
- MergeRequests::MergeService.new(project, user).execute(merge_request)
- deploy_master(user, project)
-
- expect(subject[:staging].project_median).to be_nil
- end
- end
-
- context "when the deployment happens to a non-production environment" do
- it "returns nil" do
- issue = create(:issue, project: project)
- merge_request = create_merge_request_closing_issue(user, project, issue)
- MergeRequests::MergeService.new(project, user).execute(merge_request)
- deploy_master(user, project, environment: 'staging')
-
- expect(subject[:staging].project_median).to be_nil
- end
- end
-end
diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb
deleted file mode 100644
index 8f65c047b15..00000000000
--- a/spec/models/cycle_analytics/test_spec.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'CycleAnalytics#test' do
- extend CycleAnalyticsHelpers::TestGeneration
-
- let_it_be(:project) { create(:project, :repository) }
- let_it_be(:from_date) { 10.days.ago }
- let_it_be(:user) { project.owner }
- let_it_be(:issue) { create(:issue, project: project) }
- let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date, current_user: user }) }
- let!(:merge_request) { create_merge_request_closing_issue(user, project, issue) }
-
- subject { project_level }
-
- generate_cycle_analytics_spec(
- phase: :test,
- data_fn: lambda do |context|
- issue = context.issue
- merge_request = context.create_merge_request_closing_issue(context.user, context.project, issue)
- pipeline = context.create(:ci_pipeline, ref: merge_request.source_branch, sha: merge_request.diff_head_sha, project: context.project, head_pipeline_of: merge_request)
- { pipeline: pipeline, issue: issue }
- end,
- start_time_conditions: [["pipeline is started", -> (context, data) { data[:pipeline].run! }]],
- end_time_conditions: [["pipeline is finished", -> (context, data) { data[:pipeline].succeed! }]],
- post_fn: -> (context, data) do
- end)
-
- context "when the pipeline is for a regular merge request (that doesn't close an issue)" do
- it "returns nil" do
- pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
-
- pipeline.run!
- pipeline.succeed!
-
- expect(subject[:test].project_median).to be_nil
- end
- end
-
- context "when the pipeline is not for a merge request" do
- it "returns nil" do
- pipeline = create(:ci_pipeline, ref: "refs/heads/master", sha: project.repository.commit('master').sha)
-
- pipeline.run!
- pipeline.succeed!
-
- expect(subject[:test].project_median).to be_nil
- end
- end
-
- context "when the pipeline is dropped (failed)" do
- it "returns nil" do
- pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
-
- pipeline.run!
- pipeline.drop!
-
- expect(subject[:test].project_median).to be_nil
- end
- end
-
- context "when the pipeline is cancelled" do
- it "returns nil" do
- pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
-
- pipeline.run!
- pipeline.cancel!
-
- expect(subject[:test].project_median).to be_nil
- end
- end
-end
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index c962b012a4b..5bc61db6d21 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -227,6 +227,56 @@ RSpec.describe Deployment do
deployment.skip!
end
end
+
+ describe 'synching status to Jira' do
+ let(:deployment) { create(:deployment) }
+
+ let(:worker) { ::JiraConnect::SyncDeploymentsWorker }
+
+ it 'calls the worker on creation' do
+ expect(worker).to receive(:perform_async).with(Integer)
+
+ deployment
+ end
+
+ it 'does not call the worker for skipped deployments' do
+ expect(deployment).to be_present # warm-up, ignore the creation trigger
+
+ expect(worker).not_to receive(:perform_async)
+
+ deployment.skip!
+ end
+
+ %i[run! succeed! drop! cancel!].each do |event|
+ context "when we call pipeline.#{event}" do
+ it 'triggers a Jira synch worker' do
+ expect(worker).to receive(:perform_async).with(deployment.id)
+
+ deployment.send(event)
+ end
+
+ context 'the feature is disabled' do
+ it 'does not trigger a worker' do
+ stub_feature_flags(jira_sync_deployments: false)
+
+ expect(worker).not_to receive(:perform_async)
+
+ deployment.send(event)
+ end
+ end
+
+ context 'the feature is enabled for this project' do
+ it 'does trigger a worker' do
+ stub_feature_flags(jira_sync_deployments: deployment.project)
+
+ expect(worker).to receive(:perform_async)
+
+ deployment.send(event)
+ end
+ end
+ end
+ end
+ end
end
describe '#success?' do
diff --git a/spec/models/experiment_spec.rb b/spec/models/experiment_spec.rb
index 1bf7b8b4850..171bfd116d3 100644
--- a/spec/models/experiment_spec.rb
+++ b/spec/models/experiment_spec.rb
@@ -164,7 +164,7 @@ RSpec.describe Experiment do
context 'when an experiment_user already exists for the given user' do
before do
# Create an existing experiment_user for this experiment and the :control group
- experiment.record_user_and_group(user, :control, context)
+ experiment.record_user_and_group(user, :control)
end
it 'does not create a new experiment_user record' do
@@ -173,15 +173,65 @@ RSpec.describe Experiment do
context 'but the group_type and context has changed' do
let(:group) { :experimental }
- let(:context) { { b: 37 } }
it 'updates the existing experiment_user record with group_type' do
expect { record_user_and_group }.to change { ExperimentUser.last.group_type }
end
+ end
+ end
+
+ context 'when a context already exists' do
+ let_it_be(:context) { { a: 42, 'b' => 34, 'c': { c1: 100, c2: 'c2', e: :e }, d: [1, 3] } }
+ let_it_be(:initial_expected_context) { { 'a' => 42, 'b' => 34, 'c' => { 'c1' => 100, 'c2' => 'c2', 'e' => 'e' }, 'd' => [1, 3] } }
- it 'updates the existing experiment_user record with context' do
+ before do
+ record_user_and_group
+ experiment.record_user_and_group(user, :control, {})
+ end
+
+ it 'has an initial context with stringified keys' do
+ expect(ExperimentUser.last.context).to eq(initial_expected_context)
+ end
+
+ context 'when updated' do
+ before do
record_user_and_group
- expect(ExperimentUser.last.context).to eq({ 'b' => 37 })
+ experiment.record_user_and_group(user, :control, new_context)
+ end
+
+ context 'with an empty context' do
+ let_it_be(:new_context) { {} }
+
+ it 'keeps the initial context' do
+ expect(ExperimentUser.last.context).to eq(initial_expected_context)
+ end
+ end
+
+ context 'with string keys' do
+ let_it_be(:new_context) { { f: :some_symbol } }
+
+ it 'adds new symbols stringified' do
+ expected_context = initial_expected_context.merge('f' => 'some_symbol')
+ expect(ExperimentUser.last.context).to eq(expected_context)
+ end
+ end
+
+ context 'with atomic values or array values' do
+ let_it_be(:new_context) { { b: 97, d: [99] } }
+
+ it 'overrides the values' do
+ expected_context = { 'a' => 42, 'b' => 97, 'c' => { 'c1' => 100, 'c2' => 'c2', 'e' => 'e' }, 'd' => [99] }
+ expect(ExperimentUser.last.context).to eq(expected_context)
+ end
+ end
+
+ context 'with nested hashes' do
+ let_it_be(:new_context) { { c: { g: 107 } } }
+
+ it 'inserts nested additional values in the same keys' do
+ expected_context = initial_expected_context.deep_merge('c' => { 'g' => 107 })
+ expect(ExperimentUser.last.context).to eq(expected_context)
+ end
end
end
end
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index cc8e744a15c..0acf2b96b74 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -31,6 +31,7 @@ RSpec.describe Group do
it { is_expected.to have_one(:dependency_proxy_setting) }
it { is_expected.to have_many(:dependency_proxy_blobs) }
it { is_expected.to have_many(:dependency_proxy_manifests) }
+ it { is_expected.to have_many(:debian_distributions).class_name('Packages::Debian::GroupDistribution').dependent(:destroy) }
describe '#members & #requesters' do
let(:requester) { create(:user) }
@@ -1751,4 +1752,23 @@ RSpec.describe Group do
it { is_expected.to eq(false) }
end
end
+
+ describe 'with Debian Distributions' do
+ subject { create(:group) }
+
+ let!(:distributions) { create_list(:debian_group_distribution, 2, :with_file, container: subject) }
+
+ it 'removes distribution files on removal' do
+ distribution_file_paths = distributions.map do |distribution|
+ distribution.file.path
+ end
+
+ expect { subject.destroy }
+ .to change {
+ distribution_file_paths.select do |path|
+ File.exist? path
+ end.length
+ }.from(distribution_file_paths.length).to(0)
+ end
+ end
end
diff --git a/spec/models/list_spec.rb b/spec/models/list_spec.rb
index 37158584062..f0b1bc33e84 100644
--- a/spec/models/list_spec.rb
+++ b/spec/models/list_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe List do
it_behaves_like 'having unique enum values'
+ it_behaves_like 'boards listable model', :list
describe 'relationships' do
it { is_expected.to belong_to(:board) }
@@ -14,72 +15,6 @@ RSpec.describe List do
it { is_expected.to validate_presence_of(:board) }
it { is_expected.to validate_presence_of(:label) }
it { is_expected.to validate_presence_of(:list_type) }
- it { is_expected.to validate_presence_of(:position) }
- it { is_expected.to validate_numericality_of(:position).only_integer.is_greater_than_or_equal_to(0) }
-
- context 'when list_type is set to closed' do
- subject { described_class.new(list_type: :closed) }
-
- it { is_expected.not_to validate_presence_of(:label) }
- it { is_expected.not_to validate_presence_of(:position) }
- end
- end
-
- describe '#destroy' do
- it 'can be destroyed when list_type is set to label' do
- subject = create(:list)
-
- expect(subject.destroy).to be_truthy
- end
-
- it 'can not be destroyed when list_type is set to closed' do
- subject = create(:closed_list)
-
- expect(subject.destroy).to be_falsey
- end
- end
-
- describe '#destroyable?' do
- it 'returns true when list_type is set to label' do
- subject.list_type = :label
-
- expect(subject).to be_destroyable
- end
-
- it 'returns false when list_type is set to closed' do
- subject.list_type = :closed
-
- expect(subject).not_to be_destroyable
- end
- end
-
- describe '#movable?' do
- it 'returns true when list_type is set to label' do
- subject.list_type = :label
-
- expect(subject).to be_movable
- end
-
- it 'returns false when list_type is set to closed' do
- subject.list_type = :closed
-
- expect(subject).not_to be_movable
- end
- end
-
- describe '#title' do
- it 'returns label name when list_type is set to label' do
- subject.list_type = :label
- subject.label = Label.new(name: 'Development')
-
- expect(subject.title).to eq 'Development'
- end
-
- it 'returns Closed when list_type is set to closed' do
- subject.list_type = :closed
-
- expect(subject.title).to eq 'Closed'
- end
end
describe '#update_preferences_for' do
diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb
index 2b24e2d6455..3d3ed6fc54a 100644
--- a/spec/models/members/group_member_spec.rb
+++ b/spec/models/members/group_member_spec.rb
@@ -123,4 +123,16 @@ RSpec.describe GroupMember do
end
end
end
+
+ context 'when group member expiration date is updated' do
+ let_it_be(:group_member) { create(:group_member) }
+
+ it 'emails the user that their group membership expiry has changed' do
+ expect_next_instance_of(NotificationService) do |notification|
+ allow(notification).to receive(:updated_group_member_expiration).with(group_member)
+ end
+
+ group_member.update!(expires_at: 5.days.from_now)
+ end
+ end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 431a60a11a5..1cf197322f5 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -411,6 +411,48 @@ RSpec.describe MergeRequest, factory_default: :keep do
end
end
+ describe '.by_squash_commit_sha' do
+ subject { described_class.by_squash_commit_sha(sha) }
+
+ let(:sha) { '123abc' }
+ let(:merge_request) { create(:merge_request, :merged, squash_commit_sha: sha) }
+
+ it 'returns merge requests that match the given squash commit' do
+ is_expected.to eq([merge_request])
+ end
+ end
+
+ describe '.by_related_commit_sha' do
+ subject { described_class.by_related_commit_sha(sha) }
+
+ context 'when commit is a squash commit' do
+ let!(:merge_request) { create(:merge_request, :merged, squash_commit_sha: sha) }
+ let(:sha) { '123abc' }
+
+ it { is_expected.to eq([merge_request]) }
+ end
+
+ context 'when commit is a part of the merge request' do
+ let!(:merge_request) { create(:merge_request, :with_diffs) }
+ let(:sha) { 'b83d6e391c22777fca1ed3012fce84f633d7fed0' }
+
+ it { is_expected.to eq([merge_request]) }
+ end
+
+ context 'when commit is a merge commit' do
+ let!(:merge_request) { create(:merge_request, :merged, merge_commit_sha: sha) }
+ let(:sha) { '123abc' }
+
+ it { is_expected.to eq([merge_request]) }
+ end
+
+ context 'when commit is not found' do
+ let(:sha) { '0000' }
+
+ it { is_expected.to be_empty }
+ end
+ end
+
describe '.by_cherry_pick_sha' do
it 'returns merge requests that match the given merge commit' do
note = create(:track_mr_picking_note, commit_id: '456abc')
@@ -3555,112 +3597,6 @@ RSpec.describe MergeRequest, factory_default: :keep do
end
end
- describe '#mergeable_with_quick_action?' do
- def create_pipeline(status)
- pipeline = create(:ci_pipeline,
- project: project,
- ref: merge_request.source_branch,
- sha: merge_request.diff_head_sha,
- status: status,
- head_pipeline_of: merge_request)
-
- pipeline
- end
-
- let_it_be(:project) { create(:project, :public, :repository, only_allow_merge_if_pipeline_succeeds: true) }
-
- let(:developer) { create(:user) }
- let(:user) { create(:user) }
- let(:merge_request) { create(:merge_request, source_project: project) }
- let(:mr_sha) { merge_request.diff_head_sha }
-
- before do
- project.add_developer(developer)
- end
-
- context 'when autocomplete_precheck is set to true' do
- it 'is mergeable by developer' do
- expect(merge_request.mergeable_with_quick_action?(developer, autocomplete_precheck: true)).to be_truthy
- end
-
- it 'is not mergeable by normal user' do
- expect(merge_request.mergeable_with_quick_action?(user, autocomplete_precheck: true)).to be_falsey
- end
- end
-
- context 'when autocomplete_precheck is set to false' do
- it 'is mergeable by developer' do
- expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_truthy
- end
-
- it 'is not mergeable by normal user' do
- expect(merge_request.mergeable_with_quick_action?(user, last_diff_sha: mr_sha)).to be_falsey
- end
-
- context 'closed MR' do
- before do
- merge_request.update_attribute(:state_id, described_class.available_states[:closed])
- end
-
- it 'is not mergeable' do
- expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_falsey
- end
- end
-
- context 'MR with WIP' do
- before do
- merge_request.update_attribute(:title, 'WIP: some MR')
- end
-
- it 'is not mergeable' do
- expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_falsey
- end
- end
-
- context 'sha differs from the MR diff_head_sha' do
- it 'is not mergeable' do
- expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: 'some other sha')).to be_falsey
- end
- end
-
- context 'sha is not provided' do
- it 'is not mergeable' do
- expect(merge_request.mergeable_with_quick_action?(developer)).to be_falsey
- end
- end
-
- context 'with pipeline ok' do
- before do
- create_pipeline(:success)
- end
-
- it 'is mergeable' do
- expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_truthy
- end
- end
-
- context 'with failing pipeline' do
- before do
- create_pipeline(:failed)
- end
-
- it 'is not mergeable' do
- expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_falsey
- end
- end
-
- context 'with running pipeline' do
- before do
- create_pipeline(:running)
- end
-
- it 'is mergeable' do
- expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_truthy
- end
- end
- end
- end
-
describe '#pipeline_coverage_delta' do
let!(:merge_request) { create(:merge_request) }
diff --git a/spec/models/namespace/package_setting_spec.rb b/spec/models/namespace/package_setting_spec.rb
new file mode 100644
index 00000000000..097cef8ef3b
--- /dev/null
+++ b/spec/models/namespace/package_setting_spec.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Namespace::PackageSetting do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:namespace) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:namespace) }
+
+ describe '#maven_duplicates_allowed' do
+ it { is_expected.to allow_value(true).for(:maven_duplicates_allowed) }
+ it { is_expected.to allow_value(false).for(:maven_duplicates_allowed) }
+ it { is_expected.not_to allow_value(nil).for(:maven_duplicates_allowed) }
+ end
+
+ describe '#maven_duplicate_exception_regex' do
+ let_it_be(:package_settings) { create(:namespace_package_setting) }
+
+ subject { package_settings }
+
+ valid_regexps = %w[SNAPSHOT .* v.+ v10.1.* (?:v.+|SNAPSHOT|TEMP)]
+ invalid_regexps = ['[', '(?:v.+|SNAPSHOT|TEMP']
+
+ valid_regexps.each do |valid_regexp|
+ it { is_expected.to allow_value(valid_regexp).for(:maven_duplicate_exception_regex) }
+ end
+
+ invalid_regexps.each do |invalid_regexp|
+ it { is_expected.not_to allow_value(invalid_regexp).for(:maven_duplicate_exception_regex) }
+ end
+ end
+ end
+
+ describe '#duplicates_allowed?' do
+ using RSpec::Parameterized::TableSyntax
+
+ subject { described_class.duplicates_allowed?(package) }
+
+ context 'package types with package_settings' do
+ # As more package types gain settings they will be added to this list
+ [:maven_package].each do |format|
+ let_it_be(:package) { create(format) } # rubocop:disable Rails/SaveBang
+ let_it_be(:package_type) { package.package_type }
+ let_it_be(:package_setting) { package.project.namespace.package_settings }
+
+ where(:duplicates_allowed, :duplicate_exception_regex, :result) do
+ true | '' | true
+ false | '' | false
+ false | '.*' | true
+ end
+
+ with_them do
+ context "for #{format}" do
+ before do
+ package_setting.update!(
+ "#{package_type}_duplicates_allowed" => duplicates_allowed,
+ "#{package_type}_duplicate_exception_regex" => duplicate_exception_regex
+ )
+ end
+
+ it { is_expected.to be(result) }
+ end
+ end
+ end
+ end
+
+ context 'package types without package_settings' do
+ [:npm_package, :conan_package, :nuget_package, :pypi_package, :composer_package, :generic_package, :golang_package, :debian_package].each do |format|
+ let_it_be(:package) { create(format) } # rubocop:disable Rails/SaveBang
+ let_it_be(:package_setting) { package.project.namespace.package_settings }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(Namespace::PackageSetting::PackageSettingNotImplemented)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/namespace_onboarding_action_spec.rb b/spec/models/namespace_onboarding_action_spec.rb
deleted file mode 100644
index 70dcb989b32..00000000000
--- a/spec/models/namespace_onboarding_action_spec.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe NamespaceOnboardingAction do
- let(:namespace) { build(:namespace) }
-
- describe 'associations' do
- it { is_expected.to belong_to(:namespace).required }
- end
-
- describe 'validations' do
- it { is_expected.to validate_presence_of(:action) }
- end
-
- describe '.completed?' do
- let(:action) { :subscription_created }
-
- subject { described_class.completed?(namespace, action) }
-
- context 'action created for the namespace' do
- before do
- create(:namespace_onboarding_action, namespace: namespace, action: action)
- end
-
- it { is_expected.to eq(true) }
- end
-
- context 'action created for another namespace' do
- before do
- create(:namespace_onboarding_action, namespace: build(:namespace), action: action)
- end
-
- it { is_expected.to eq(false) }
- end
- end
-
- describe '.create_action' do
- let(:action) { :subscription_created }
-
- subject(:create_action) { described_class.create_action(namespace, action) }
-
- it 'creates the action for the namespace just once' do
- expect { create_action }.to change { count_namespace_actions }.by(1)
-
- expect { create_action }.to change { count_namespace_actions }.by(0)
- end
-
- def count_namespace_actions
- described_class.where(namespace: namespace, action: action).count
- end
- end
-end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 0130618d004..a3c0a43115e 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -19,7 +19,8 @@ RSpec.describe Namespace do
it { is_expected.to have_one :aggregation_schedule }
it { is_expected.to have_one :namespace_settings }
it { is_expected.to have_many :custom_emoji }
- it { is_expected.to have_many :namespace_onboarding_actions }
+ it { is_expected.to have_one :package_setting_relation }
+ it { is_expected.to have_one :onboarding_progress }
end
describe 'validations' do
@@ -1500,4 +1501,24 @@ RSpec.describe Namespace do
end
end
end
+
+ describe '#root?' do
+ subject { namespace.root? }
+
+ context 'when is subgroup' do
+ before do
+ namespace.parent = build(:group)
+ end
+
+ it 'returns false' do
+ is_expected.to eq(false)
+ end
+ end
+
+ context 'when is root' do
+ it 'returns true' do
+ is_expected.to eq(true)
+ end
+ end
+ end
end
diff --git a/spec/models/onboarding_progress_spec.rb b/spec/models/onboarding_progress_spec.rb
new file mode 100644
index 00000000000..bd951846bb8
--- /dev/null
+++ b/spec/models/onboarding_progress_spec.rb
@@ -0,0 +1,107 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe OnboardingProgress do
+ let(:namespace) { create(:namespace) }
+ let(:action) { :subscription_created }
+
+ describe 'associations' do
+ it { is_expected.to belong_to(:namespace).required }
+ end
+
+ describe 'validations' do
+ describe 'namespace_is_root_namespace' do
+ subject(:onboarding_progress) { build(:onboarding_progress, namespace: namespace)}
+
+ context 'when associated namespace is root' do
+ it { is_expected.to be_valid }
+ end
+
+ context 'when associated namespace is not root' do
+ let(:namespace) { build(:group, :nested) }
+
+ it 'is invalid' do
+ expect(onboarding_progress).to be_invalid
+ expect(onboarding_progress.errors[:namespace]).to include('must be a root namespace')
+ end
+ end
+ end
+ end
+
+ describe '.onboard' do
+ subject(:onboard) { described_class.onboard(namespace) }
+
+ it 'adds a record for the namespace' do
+ expect { onboard }.to change(described_class, :count).from(0).to(1)
+ end
+
+ context 'when not given a namespace' do
+ let(:namespace) { nil }
+
+ it 'does not add a record for the namespace' do
+ expect { onboard }.not_to change(described_class, :count).from(0)
+ end
+ end
+
+ context 'when not given a root namespace' do
+ let(:namespace) { create(:namespace, parent: build(:namespace)) }
+
+ it 'does not add a record for the namespace' do
+ expect { onboard }.not_to change(described_class, :count).from(0)
+ end
+ end
+ end
+
+ describe '.register' do
+ subject(:register_action) { described_class.register(namespace, action) }
+
+ context 'when the namespace was onboarded' do
+ before do
+ described_class.onboard(namespace)
+ end
+
+ it 'registers the action for the namespace' do
+ expect { register_action }.to change { described_class.completed?(namespace, action) }.from(false).to(true)
+ end
+
+ context 'when the action does not exist' do
+ let(:action) { :foo }
+
+ it 'does not register the action for the namespace' do
+ expect { register_action }.not_to change { described_class.completed?(namespace, action) }.from(nil)
+ end
+ end
+ end
+
+ context 'when the namespace was not onboarded' do
+ it 'does not register the action for the namespace' do
+ expect { register_action }.not_to change { described_class.completed?(namespace, action) }.from(false)
+ end
+ end
+ end
+
+ describe '.completed?' do
+ subject { described_class.completed?(namespace, action) }
+
+ context 'when the namespace has not yet been onboarded' do
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when the namespace has been onboarded but not registered the action yet' do
+ before do
+ described_class.onboard(namespace)
+ end
+
+ it { is_expected.to eq(false) }
+
+ context 'when the action has been registered' do
+ before do
+ described_class.register(namespace, action)
+ end
+
+ it { is_expected.to eq(true) }
+ end
+ end
+ end
+end
diff --git a/spec/models/packages/debian/file_metadatum_spec.rb b/spec/models/packages/debian/file_metadatum_spec.rb
new file mode 100644
index 00000000000..1215adfa6a1
--- /dev/null
+++ b/spec/models/packages/debian/file_metadatum_spec.rb
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Debian::FileMetadatum, type: :model do
+ RSpec.shared_context 'Debian file metadatum' do |factory, trait|
+ let_it_be_with_reload(:debian_package_file) { create(factory, trait) }
+ let(:debian_file_metadatum) { debian_package_file.debian_file_metadatum }
+
+ subject { debian_file_metadatum }
+ end
+
+ RSpec.shared_examples 'Test Debian file metadatum' do |has_component, has_architecture, has_fields, has_outdated|
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package_file) }
+ end
+
+ describe 'validations' do
+ describe '#package_file' do
+ it { is_expected.to validate_presence_of(:package_file) }
+ end
+
+ describe '#file_type' do
+ it { is_expected.to validate_presence_of(:file_type) }
+ end
+
+ describe '#component' do
+ it "has_component=#{has_component}" do
+ if has_component
+ is_expected.to validate_presence_of(:component)
+ is_expected.to allow_value('main').for(:component)
+ is_expected.not_to allow_value('hé').for(:component)
+ else
+ is_expected.to validate_absence_of(:component)
+ end
+ end
+ end
+
+ describe '#architecture' do
+ it "has_architecture=#{has_architecture}" do
+ if has_architecture
+ is_expected.to validate_presence_of(:architecture)
+ is_expected.to allow_value('amd64').for(:architecture)
+ is_expected.not_to allow_value('-a').for(:architecture)
+ else
+ is_expected.to validate_absence_of(:architecture)
+ end
+ end
+ end
+
+ describe '#fields' do
+ if has_fields
+ it { is_expected.to validate_presence_of(:fields) }
+ it { is_expected.to allow_value({ 'a': 'b' }).for(:fields) }
+ it { is_expected.not_to allow_value({ 'a': { 'b': 'c' } }).for(:fields) }
+ else
+ it { is_expected.to validate_absence_of(:fields) }
+ end
+ end
+
+ describe '#debian_package_type' do
+ before do
+ debian_package_file.package.package_type = :pypi
+ end
+
+ it 'validates package of type debian' do
+ expect(debian_file_metadatum).not_to be_valid
+ expect(debian_file_metadatum.errors.to_a).to contain_exactly('Package file Package type must be Debian')
+ end
+ end
+ end
+ end
+
+ using RSpec::Parameterized::TableSyntax
+
+ where(:factory, :trait, :has_component, :has_architecture, :has_fields) do
+ :debian_package_file | :unknown | false | false | false
+ :debian_package_file | :source | true | false | false
+ :debian_package_file | :dsc | true | false | true
+ :debian_package_file | :deb | true | true | true
+ :debian_package_file | :udeb | true | true | true
+ :debian_package_file | :buildinfo | true | false | true
+ :debian_package_file | :changes | false | false | true
+ end
+
+ with_them do
+ include_context 'Debian file metadatum', params[:factory], params[:trait] do
+ it_behaves_like 'Test Debian file metadatum', params[:has_component], params[:has_architecture], params[:has_fields], params[:has_outdated]
+ end
+ end
+end
diff --git a/spec/models/packages/debian/group_architecture_spec.rb b/spec/models/packages/debian/group_architecture_spec.rb
new file mode 100644
index 00000000000..a6def6e56cd
--- /dev/null
+++ b/spec/models/packages/debian/group_architecture_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Debian::GroupArchitecture do
+ it_behaves_like 'Debian Distribution Architecture', :debian_group_architecture, :group, false
+end
diff --git a/spec/models/packages/debian/group_distribution_spec.rb b/spec/models/packages/debian/group_distribution_spec.rb
new file mode 100644
index 00000000000..90fb0d0e7d8
--- /dev/null
+++ b/spec/models/packages/debian/group_distribution_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Debian::GroupDistribution do
+ it_behaves_like 'Debian Distribution', :debian_group_distribution, :group, false
+end
diff --git a/spec/models/packages/debian/project_architecture_spec.rb b/spec/models/packages/debian/project_architecture_spec.rb
new file mode 100644
index 00000000000..b82ecabc4d4
--- /dev/null
+++ b/spec/models/packages/debian/project_architecture_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Debian::ProjectArchitecture do
+ it_behaves_like 'Debian Distribution Architecture', :debian_project_architecture, :project, true
+end
diff --git a/spec/models/packages/debian/project_distribution_spec.rb b/spec/models/packages/debian/project_distribution_spec.rb
new file mode 100644
index 00000000000..5f4041ad9fe
--- /dev/null
+++ b/spec/models/packages/debian/project_distribution_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Debian::ProjectDistribution do
+ it_behaves_like 'Debian Distribution', :debian_project_distribution, :project, true
+end
diff --git a/spec/models/packages/package_file_spec.rb b/spec/models/packages/package_file_spec.rb
index 82ac159b9cc..ebb10e991ad 100644
--- a/spec/models/packages/package_file_spec.rb
+++ b/spec/models/packages/package_file_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe Packages::PackageFile, type: :model do
it { is_expected.to have_one(:conan_file_metadatum) }
it { is_expected.to have_many(:package_file_build_infos).inverse_of(:package_file) }
it { is_expected.to have_many(:pipelines).through(:package_file_build_infos) }
+ it { is_expected.to have_one(:debian_file_metadatum).inverse_of(:package_file).class_name('Packages::Debian::FileMetadatum') }
end
describe 'validations' do
diff --git a/spec/models/packages/package_spec.rb b/spec/models/packages/package_spec.rb
index 16764673474..6645db33503 100644
--- a/spec/models/packages/package_spec.rb
+++ b/spec/models/packages/package_spec.rb
@@ -599,6 +599,20 @@ RSpec.describe Packages::Package, type: :model do
end
end
+ describe '.order_by_package_file' do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:package1) { create(:maven_package, project: project) }
+ let_it_be(:package2) { create(:maven_package, project: project) }
+
+ it 'orders packages their associated package_file\'s created_at date', :aggregate_failures do
+ expect(project.packages.order_by_package_file).to match_array([package1, package1, package1, package2, package2, package2])
+
+ create(:package_file, :xml, package: package1)
+
+ expect(project.packages.order_by_package_file).to match_array([package1, package1, package1, package2, package2, package2, package1])
+ end
+ end
+
describe '#versions' do
let_it_be(:project) { create(:project) }
let_it_be(:package) { create(:maven_package, project: project) }
@@ -731,4 +745,14 @@ RSpec.describe Packages::Package, type: :model do
end
end
end
+
+ describe '#package_settings' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, group: group) }
+ let_it_be(:package) { create(:maven_package, project: project) }
+
+ it 'returns the namespace package_settings' do
+ expect(package.package_settings).to eq(group.package_settings)
+ end
+ end
end
diff --git a/spec/models/project_import_state_spec.rb b/spec/models/project_import_state_spec.rb
index 6a0402d43a8..843beb4ce23 100644
--- a/spec/models/project_import_state_spec.rb
+++ b/spec/models/project_import_state_spec.rb
@@ -103,7 +103,7 @@ RSpec.describe ProjectImportState, type: :model do
allow(after_import_service)
.to receive(:execute) { housekeeping_service.execute }
- allow(Projects::HousekeepingService)
+ allow(Repositories::HousekeepingService)
.to receive(:new) { housekeeping_service }
end
diff --git a/spec/models/project_pages_metadatum_spec.rb b/spec/models/project_pages_metadatum_spec.rb
new file mode 100644
index 00000000000..31a533e0363
--- /dev/null
+++ b/spec/models/project_pages_metadatum_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ProjectPagesMetadatum do
+ describe '.only_on_legacy_storage' do
+ it 'returns only deployed records without deployment' do
+ create(:project) # without pages deployed
+
+ legacy_storage_project = create(:project)
+ legacy_storage_project.mark_pages_as_deployed
+
+ project_with_deployment = create(:project)
+ deployment = create(:pages_deployment, project: project_with_deployment)
+ project_with_deployment.mark_pages_as_deployed
+ project_with_deployment.update_pages_deployment!(deployment)
+
+ expect(described_class.only_on_legacy_storage).to eq([legacy_storage_project.pages_metadatum])
+ end
+ end
+end
diff --git a/spec/models/project_services/alerts_service_spec.rb b/spec/models/project_services/alerts_service_spec.rb
index db25885c76a..75b91c29914 100644
--- a/spec/models/project_services/alerts_service_spec.rb
+++ b/spec/models/project_services/alerts_service_spec.rb
@@ -2,108 +2,38 @@
require 'spec_helper'
+# AlertsService is stripped down to only required methods
+# to avoid errors loading integration-related pages if
+# records are present.
RSpec.describe AlertsService do
let_it_be(:project) { create(:project) }
- let(:service_params) { { project: project, active: active } }
- let(:active) { true }
- let(:service) { described_class.new(service_params) }
+ subject(:service) { described_class.new(project: project) }
- shared_context 'when active' do
- let(:active) { true }
- end
-
- shared_context 'when inactive' do
- let(:active) { false }
- end
-
- shared_context 'when persisted' do
- before do
- service.save!
- service.reload
- end
- end
-
- describe '#url' do
- include Gitlab::Routing
-
- subject { service.url }
+ it { is_expected.to be_valid }
- it { is_expected.to eq(project_alerts_notify_url(project, format: :json)) }
- end
-
- describe '#json_fields' do
- subject { service.json_fields }
+ describe '#to_param' do
+ subject { service.to_param }
- it { is_expected.to eq(%w(active token)) }
+ it { is_expected.to eq('alerts') }
end
- describe '#as_json' do
- subject { service.as_json(only: service.json_fields) }
+ describe '#supported_events' do
+ subject { service.supported_events }
- it { is_expected.to eq('active' => true, 'token' => nil) }
+ it { is_expected.to be_empty }
end
- describe '#token' do
- shared_context 'reset token' do
- before do
- service.token = ''
- service.valid?
- end
- end
-
- shared_context 'assign token' do |token|
- before do
- service.token = token
- service.valid?
- end
- end
-
- shared_examples 'valid token' do
- it { is_expected.to match(/\A\h{32}\z/) }
- end
-
- shared_examples 'no token' do
- it { is_expected.to be_blank }
- end
-
- subject { service.token }
-
- context 'when active' do
- include_context 'when active'
-
- context 'when resetting' do
- let!(:previous_token) { service.token }
-
- include_context 'reset token'
-
- it_behaves_like 'valid token'
-
- it { is_expected.not_to eq(previous_token) }
- end
-
- context 'when assigning' do
- include_context 'assign token', 'random token'
-
- it_behaves_like 'valid token'
- end
- end
-
- context 'when inactive' do
- include_context 'when inactive'
-
- context 'when resetting' do
- let!(:previous_token) { service.token }
-
- include_context 'reset token'
-
- it_behaves_like 'no token'
- end
- end
+ describe '#save' do
+ it 'prevents records from being created or updated' do
+ expect(Gitlab::ProjectServiceLogger).to receive(:error).with(
+ hash_including(message: 'Prevented attempt to save or update deprecated AlertsService')
+ )
- context 'when persisted' do
- include_context 'when persisted'
+ expect(service.save).to be_falsey
- it_behaves_like 'valid token'
+ expect(service.errors.full_messages).to include(
+ 'Alerts endpoint is deprecated and should not be created or modified. Use HTTP Integrations instead.'
+ )
end
end
end
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index e7cd3d7f537..cd0873bddd2 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -5,12 +5,21 @@ require 'spec_helper'
RSpec.describe JiraService do
include AssetsHelpers
+ let_it_be(:project) { create(:project, :repository) }
let(:url) { 'http://jira.example.com' }
let(:api_url) { 'http://api-jira.example.com' }
let(:username) { 'jira-username' }
let(:password) { 'jira-password' }
let(:transition_id) { 'test27' }
let(:server_info_results) { { 'deploymentType' => 'Cloud' } }
+ let(:jira_service) do
+ described_class.new(
+ project: project,
+ url: url,
+ username: username,
+ password: password
+ )
+ end
before do
WebMock.stub_request(:get, /serverInfo/).to_return(body: server_info_results.to_json )
@@ -19,7 +28,7 @@ RSpec.describe JiraService do
describe '#options' do
let(:options) do
{
- project: create(:project),
+ project: project,
active: true,
username: 'username',
password: 'test',
@@ -108,7 +117,7 @@ RSpec.describe JiraService do
describe '#create' do
let(:params) do
{
- project: create(:project),
+ project: project,
url: url, api_url: api_url,
username: username, password: password,
jira_issue_transition_id: transition_id
@@ -434,10 +443,23 @@ RSpec.describe JiraService do
end
end
+ describe '#find_issue' do
+ let(:issue_key) { 'JIRA-123' }
+ let(:issue_url) { "#{url}/rest/api/2/issue/#{issue_key}" }
+
+ before do
+ stub_request(:get, issue_url).with(basic_auth: [username, password])
+ end
+
+ it 'call the Jira API to get the issue' do
+ jira_service.find_issue(issue_key)
+
+ expect(WebMock).to have_requested(:get, issue_url)
+ end
+ end
+
describe '#close_issue' do
let(:custom_base_url) { 'http://custom_url' }
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository) }
shared_examples 'close_issue' do
before do
@@ -445,7 +467,6 @@ RSpec.describe JiraService do
allow(@jira_service).to receive_messages(
project_id: project.id,
project: project,
- service_hook: true,
url: 'http://jira.example.com',
username: 'gitlab_jira_username',
password: 'gitlab_jira_password',
@@ -657,17 +678,7 @@ RSpec.describe JiraService do
end
describe '#create_cross_reference_note' do
- let_it_be(:user) { build_stubbed(:user) }
- let_it_be(:project) { create(:project, :repository) }
- let(:jira_service) do
- described_class.new(
- project: project,
- url: url,
- username: username,
- password: password
- )
- end
-
+ let_it_be(:user) { build_stubbed(:user) }
let(:jira_issue) { ExternalIssue.new('JIRA-123', project) }
subject { jira_service.create_cross_reference_note(jira_issue, resource, user) }
@@ -732,15 +743,6 @@ RSpec.describe JiraService do
describe '#test' do
let(:server_info_results) { { 'url' => 'http://url', 'deploymentType' => 'Cloud' } }
- let_it_be(:project) { create(:project, :repository) }
- let(:jira_service) do
- described_class.new(
- url: url,
- project: project,
- username: username,
- password: password
- )
- end
def server_info
jira_service.test(nil)
@@ -790,7 +792,6 @@ RSpec.describe JiraService do
}
allow(Gitlab.config).to receive(:issues_tracker).and_return(settings)
- project = create(:project)
service = project.create_jira_service(active: true)
expect(service.url).to eq('http://jira.sample/projects/project_a')
diff --git a/spec/models/project_services/prometheus_service_spec.rb b/spec/models/project_services/prometheus_service_spec.rb
index 76fc5a826c9..8215fb5c336 100644
--- a/spec/models/project_services/prometheus_service_spec.rb
+++ b/spec/models/project_services/prometheus_service_spec.rb
@@ -95,7 +95,7 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl
service.api_url = 'http://localhost:9090'
stub_application_setting(self_monitoring_project_id: project.id)
- stub_config(prometheus: { enable: true, listen_address: 'localhost:9090' })
+ stub_config(prometheus: { enable: true, server_address: 'localhost:9090' })
end
it 'allows self-monitoring project to connect to internal Prometheus' do
@@ -242,7 +242,7 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl
stub_config(prometheus: {
enable: true,
- listen_address: api_url
+ server_address: api_url
})
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index a71b0eb842a..a2b51684d4d 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -127,6 +127,7 @@ RSpec.describe Project, factory_default: :keep do
it { is_expected.to have_many(:reviews).inverse_of(:project) }
it { is_expected.to have_many(:packages).class_name('Packages::Package') }
it { is_expected.to have_many(:package_files).class_name('Packages::PackageFile') }
+ it { is_expected.to have_many(:debian_distributions).class_name('Packages::Debian::ProjectDistribution').dependent(:destroy) }
it { is_expected.to have_many(:pipeline_artifacts) }
it { is_expected.to have_many(:terraform_states).class_name('Terraform::State').inverse_of(:project) }
@@ -1066,36 +1067,6 @@ RSpec.describe Project, factory_default: :keep do
end
end
- describe '#cache_has_external_wiki' do
- let_it_be(:project) { create(:project, has_external_wiki: nil) }
-
- it 'stores true if there is any external_wikis' do
- services = double(:service, external_wikis: [ExternalWikiService.new])
- expect(project).to receive(:services).and_return(services)
-
- expect do
- project.cache_has_external_wiki
- end.to change { project.has_external_wiki}.to(true)
- end
-
- it 'stores false if there is no external_wikis' do
- services = double(:service, external_wikis: [])
- expect(project).to receive(:services).and_return(services)
-
- expect do
- project.cache_has_external_wiki
- end.to change { project.has_external_wiki}.to(false)
- end
-
- it 'does not cache data when in a read-only GitLab instance' do
- allow(Gitlab::Database).to receive(:read_only?) { true }
-
- expect do
- project.cache_has_external_wiki
- end.not_to change { project.has_external_wiki }
- end
- end
-
describe '#has_wiki?' do
let(:no_wiki_project) { create(:project, :wiki_disabled, has_external_wiki: false) }
let(:wiki_enabled_project) { create(:project) }
@@ -1135,52 +1106,64 @@ RSpec.describe Project, factory_default: :keep do
describe '#external_wiki' do
let_it_be(:project) { create(:project) }
- context 'with an active external wiki' do
- before do
- create(:service, project: project, type: 'ExternalWikiService', active: true)
- project.external_wiki
- end
+ def subject
+ project.reload.external_wiki
+ end
- it 'sets :has_external_wiki as true' do
- expect(project.has_external_wiki).to be(true)
- end
+ it 'returns an active external wiki' do
+ create(:service, project: project, type: 'ExternalWikiService', active: true)
- it 'sets :has_external_wiki as false if an external wiki service is destroyed later' do
- expect(project.has_external_wiki).to be(true)
+ is_expected.to be_kind_of(ExternalWikiService)
+ end
- project.services.external_wikis.first.destroy
+ it 'does not return an inactive external wiki' do
+ create(:service, project: project, type: 'ExternalWikiService', active: false)
- expect(project.has_external_wiki).to be(false)
- end
+ is_expected.to eq(nil)
end
- context 'with an inactive external wiki' do
- before do
- create(:service, project: project, type: 'ExternalWikiService', active: false)
- end
+ it 'sets Project#has_external_wiki when it is nil' do
+ create(:service, project: project, type: 'ExternalWikiService', active: true)
+ project.update_column(:has_external_wiki, nil)
- it 'sets :has_external_wiki as false' do
- expect(project.has_external_wiki).to be(false)
- end
+ expect { subject }.to change { project.has_external_wiki }.from(nil).to(true)
end
+ end
- context 'with no external wiki' do
- before do
- project.external_wiki
- end
+ describe '#has_external_wiki' do
+ let_it_be(:project) { create(:project) }
- it 'sets :has_external_wiki as false' do
- expect(project.has_external_wiki).to be(false)
- end
+ def subject
+ project.reload.has_external_wiki
+ end
- it 'sets :has_external_wiki as true if an external wiki service is created later' do
- expect(project.has_external_wiki).to be(false)
+ specify { is_expected.to eq(false) }
+ context 'when there is an active external wiki service' do
+ let!(:service) do
create(:service, project: project, type: 'ExternalWikiService', active: true)
+ end
+
+ specify { is_expected.to eq(true) }
+
+ it 'becomes false if the external wiki service is destroyed' do
+ expect do
+ Service.find(service.id).delete
+ end.to change { subject }.to(false)
+ end
- expect(project.has_external_wiki).to be(true)
+ it 'becomes false if the external wiki service becomes inactive' do
+ expect do
+ service.update_column(:active, false)
+ end.to change { subject }.to(false)
end
end
+
+ it 'is false when external wiki service is not active' do
+ create(:service, project: project, type: 'ExternalWikiService', active: false)
+
+ is_expected.to eq(false)
+ end
end
describe '#star_count' do
@@ -1516,63 +1499,13 @@ RSpec.describe Project, factory_default: :keep do
allow(::Gitlab::ServiceDeskEmail).to receive(:config).and_return(config)
end
- context 'when service_desk_custom_address flag is enabled' do
- before do
- stub_feature_flags(service_desk_custom_address: true)
- end
-
- it 'returns custom address when project_key is set' do
- create(:service_desk_setting, project: project, project_key: 'key1')
-
- expect(subject).to eq("foo+#{project.full_path_slug}-key1@bar.com")
- end
+ it 'returns custom address when project_key is set' do
+ create(:service_desk_setting, project: project, project_key: 'key1')
- it_behaves_like 'with incoming email address'
+ expect(subject).to eq("foo+#{project.full_path_slug}-key1@bar.com")
end
- context 'when service_desk_custom_address flag is disabled' do
- before do
- stub_feature_flags(service_desk_custom_address: false)
- end
-
- it_behaves_like 'with incoming email address'
- end
- end
- end
-
- describe '.service_desk_custom_address_enabled?' do
- let_it_be(:project) { create(:project, service_desk_enabled: true) }
-
- subject(:address_enabled) { project.service_desk_custom_address_enabled? }
-
- context 'when service_desk_email is enabled' do
- before do
- allow(::Gitlab::ServiceDeskEmail).to receive(:enabled?).and_return(true)
- end
-
- it 'returns true' do
- expect(address_enabled).to be_truthy
- end
-
- context 'when service_desk_custom_address flag is disabled' do
- before do
- stub_feature_flags(service_desk_custom_address: false)
- end
-
- it 'returns false' do
- expect(address_enabled).to be_falsey
- end
- end
- end
-
- context 'when service_desk_email is disabled' do
- before do
- allow(::Gitlab::ServiceDeskEmail).to receive(:enabled?).and_return(false)
- end
-
- it 'returns false when service_desk_email is disabled' do
- expect(address_enabled).to be_falsey
- end
+ it_behaves_like 'with incoming email address'
end
end
@@ -3044,56 +2977,9 @@ RSpec.describe Project, factory_default: :keep do
end
end
- describe '#pushes_since_gc' do
- let(:project) { build_stubbed(:project) }
-
- after do
- project.reset_pushes_since_gc
- end
-
- context 'without any pushes' do
- it 'returns 0' do
- expect(project.pushes_since_gc).to eq(0)
- end
- end
-
- context 'with a number of pushes' do
- it 'returns the number of pushes' do
- 3.times { project.increment_pushes_since_gc }
-
- expect(project.pushes_since_gc).to eq(3)
- end
- end
- end
-
- describe '#increment_pushes_since_gc' do
- let(:project) { build_stubbed(:project) }
-
- after do
- project.reset_pushes_since_gc
- end
-
- it 'increments the number of pushes since the last GC' do
- 3.times { project.increment_pushes_since_gc }
-
- expect(project.pushes_since_gc).to eq(3)
- end
- end
-
- describe '#reset_pushes_since_gc' do
- let(:project) { build_stubbed(:project) }
-
- after do
- project.reset_pushes_since_gc
- end
-
- it 'resets the number of pushes since the last GC' do
- 3.times { project.increment_pushes_since_gc }
-
- project.reset_pushes_since_gc
-
- expect(project.pushes_since_gc).to eq(0)
- end
+ it_behaves_like 'can housekeep repository' do
+ let(:resource) { build_stubbed(:project) }
+ let(:resource_key) { 'projects' }
end
describe '#deployment_variables' do
@@ -4548,6 +4434,24 @@ RSpec.describe Project, factory_default: :keep do
end
end
+ describe '#predefined_project_variables' do
+ let_it_be(:project) { create(:project, :repository) }
+
+ subject { project.predefined_project_variables.to_runner_variables }
+
+ specify do
+ expect(subject).to include({ key: 'CI_PROJECT_CONFIG_PATH', value: Ci::Pipeline::DEFAULT_CONFIG_PATH, public: true, masked: false })
+ end
+
+ context 'when ci config path is overridden' do
+ before do
+ project.update!(ci_config_path: 'random.yml')
+ end
+
+ it { expect(subject).to include({ key: 'CI_PROJECT_CONFIG_PATH', value: 'random.yml', public: true, masked: false }) }
+ end
+ end
+
describe '#auto_devops_enabled?' do
before do
Feature.enable_percentage_of_actors(:force_autodevops_on_by_default, 0)
@@ -6002,6 +5906,43 @@ RSpec.describe Project, factory_default: :keep do
end
end
+ describe '#set_first_pages_deployment!' do
+ let(:project) { create(:project) }
+ let(:deployment) { create(:pages_deployment, project: project) }
+
+ it "creates new metadata record if none exists yet and sets deployment" do
+ project.pages_metadatum.destroy!
+ project.reload
+
+ project.set_first_pages_deployment!(deployment)
+
+ expect(project.pages_metadatum.reload.pages_deployment).to eq(deployment)
+ end
+
+ it "updates the existing metadara record with deployment" do
+ expect do
+ project.set_first_pages_deployment!(deployment)
+ end.to change { project.pages_metadatum.reload.pages_deployment }.from(nil).to(deployment)
+ end
+
+ it 'only updates metadata for this project' do
+ other_project = create(:project)
+
+ expect do
+ project.set_first_pages_deployment!(deployment)
+ end.not_to change { other_project.pages_metadatum.reload.pages_deployment }.from(nil)
+ end
+
+ it 'does nothing if metadata already references some deployment' do
+ existing_deployment = create(:pages_deployment, project: project)
+ project.set_first_pages_deployment!(existing_deployment)
+
+ expect do
+ project.set_first_pages_deployment!(deployment)
+ end.not_to change { project.pages_metadatum.reload.pages_deployment }.from(existing_deployment)
+ end
+ end
+
describe '#has_pool_repsitory?' do
it 'returns false when it does not have a pool repository' do
subject = create(:project, :repository)
@@ -6275,28 +6216,6 @@ RSpec.describe Project, factory_default: :keep do
end
end
- describe '#alerts_service_activated?' do
- let!(:project) { create(:project) }
-
- subject { project.alerts_service_activated? }
-
- context 'when project has an activated alerts service' do
- before do
- create(:alerts_service, project: project)
- end
-
- it { is_expected.to be_truthy }
- end
-
- context 'when project has an inactive alerts service' do
- before do
- create(:alerts_service, :inactive, project: project)
- end
-
- it { is_expected.to be_falsey }
- end
- end
-
describe '#prometheus_service_active?' do
let(:project) { create(:project) }
@@ -6456,6 +6375,25 @@ RSpec.describe Project, factory_default: :keep do
end
end
+ describe 'with Debian Distributions' do
+ subject { create(:project) }
+
+ let!(:distributions) { create_list(:debian_project_distribution, 2, :with_file, container: subject) }
+
+ it 'removes distribution files on removal' do
+ distribution_file_paths = distributions.map do |distribution|
+ distribution.file.path
+ end
+
+ expect { subject.destroy }
+ .to change {
+ distribution_file_paths.select do |path|
+ File.exist? path
+ end.length
+ }.from(distribution_file_paths.length).to(0)
+ end
+ end
+
describe '#environments_for_scope' do
let_it_be(:project, reload: true) { create(:project) }
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index 2e82fcf5511..8001d009901 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -42,4 +42,10 @@ RSpec.describe ProjectWiki do
end
end
end
+
+ it_behaves_like 'can housekeep repository' do
+ let_it_be(:resource) { create(:project_wiki) }
+
+ let(:resource_key) { 'project_wikis' }
+ end
end
diff --git a/spec/models/protectable_dropdown_spec.rb b/spec/models/protectable_dropdown_spec.rb
index c51197234ca..918c3078405 100644
--- a/spec/models/protectable_dropdown_spec.rb
+++ b/spec/models/protectable_dropdown_spec.rb
@@ -14,21 +14,33 @@ RSpec.describe ProtectableDropdown do
end
describe '#protectable_ref_names' do
- before do
- project.protected_branches.create(name: 'master')
- end
+ context 'when project repository is not empty' do
+ before do
+ project.protected_branches.create(name: 'master')
+ end
+
+ it { expect(subject.protectable_ref_names).to include('feature') }
+ it { expect(subject.protectable_ref_names).not_to include('master') }
+
+ it "includes branches matching a protected branch wildcard" do
+ expect(subject.protectable_ref_names).to include('feature')
- it { expect(subject.protectable_ref_names).to include('feature') }
- it { expect(subject.protectable_ref_names).not_to include('master') }
+ create(:protected_branch, name: 'feat*', project: project)
- it "includes branches matching a protected branch wildcard" do
- expect(subject.protectable_ref_names).to include('feature')
+ subject = described_class.new(project.reload, :branches)
+
+ expect(subject.protectable_ref_names).to include('feature')
+ end
+ end
- create(:protected_branch, name: 'feat*', project: project)
+ context 'when project repository is empty' do
+ let(:project) { create(:project) }
- subject = described_class.new(project.reload, :branches)
+ it "returns empty list" do
+ subject = described_class.new(project, :branches)
- expect(subject.protectable_ref_names).to include('feature')
+ expect(subject.protectable_ref_names).to be_empty
+ end
end
end
end
diff --git a/spec/models/release_highlight_spec.rb b/spec/models/release_highlight_spec.rb
index ac252e6d6cf..749b9b8e1ab 100644
--- a/spec/models/release_highlight_spec.rb
+++ b/spec/models/release_highlight_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe ReleaseHighlight do
- let(:fixture_dir_glob) { Dir.glob(File.join('spec', 'fixtures', 'whats_new', '*.yml')) }
+ let(:fixture_dir_glob) { Dir.glob(File.join('spec', 'fixtures', 'whats_new', '*.yml')).grep(/\d*\_(\d*\_\d*)\.yml$/) }
before do
allow(Dir).to receive(:glob).with(Rails.root.join('data', 'whats_new', '*.yml')).and_return(fixture_dir_glob)
diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb
index fea15ea00c8..b436c2e1088 100644
--- a/spec/models/release_spec.rb
+++ b/spec/models/release_spec.rb
@@ -3,9 +3,9 @@
require 'spec_helper'
RSpec.describe Release do
- let(:user) { create(:user) }
- let(:project) { create(:project, :public, :repository) }
- let(:release) { create(:release, project: project, author: user) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :public, :repository) }
+ let_it_be(:release) { create(:release, project: project, author: user) }
it { expect(release).to be_valid }
@@ -132,8 +132,10 @@ RSpec.describe Release do
end
describe '#milestone_titles' do
- let(:release) { create(:release, :with_milestones) }
+ let_it_be(:milestone_1) { create(:milestone, project: project, title: 'Milestone 1') }
+ let_it_be(:milestone_2) { create(:milestone, project: project, title: 'Milestone 2') }
+ let_it_be(:release) { create(:release, project: project, milestones: [milestone_1, milestone_2]) }
- it { expect(release.milestone_titles).to eq(release.milestones.map {|m| m.title }.sort.join(", "))}
+ it { expect(release.milestone_titles).to eq("#{milestone_1.title}, #{milestone_2.title}")}
end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index c1f073e26d1..dd54a701282 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -123,7 +123,7 @@ RSpec.describe Repository do
options = { message: 'test tag message\n',
tagger: { name: 'John Smith', email: 'john@gmail.com' } }
- rugged_repo(repository).tags.create(annotated_tag_name, 'a48e4fc218069f68ef2e769dd8dfea3991362175', options)
+ rugged_repo(repository).tags.create(annotated_tag_name, 'a48e4fc218069f68ef2e769dd8dfea3991362175', **options)
double_first = double(committed_date: Time.current - 1.second)
double_last = double(committed_date: Time.current)
@@ -2028,6 +2028,22 @@ RSpec.describe Repository do
end
end
+ describe '#lookup' do
+ before do
+ allow(repository.raw_repository).to receive(:lookup).and_return('interesting_blob')
+ end
+
+ it 'uses the lookup cache' do
+ 2.times.each { repository.lookup('sha1') }
+
+ expect(repository.raw_repository).to have_received(:lookup).once
+ end
+
+ it 'returns the correct value' do
+ expect(repository.lookup('sha1')).to eq('interesting_blob')
+ end
+ end
+
describe '#after_create' do
it 'calls expire_status_cache' do
expect(repository).to receive(:expire_status_cache)
diff --git a/spec/models/snippet_repository_storage_move_spec.rb b/spec/models/snippet_repository_storage_move_spec.rb
index c9feff0c22f..357951f8859 100644
--- a/spec/models/snippet_repository_storage_move_spec.rb
+++ b/spec/models/snippet_repository_storage_move_spec.rb
@@ -8,6 +8,6 @@ RSpec.describe SnippetRepositoryStorageMove, type: :model do
let(:repository_storage_factory_key) { :snippet_repository_storage_move }
let(:error_key) { :snippet }
- let(:repository_storage_worker) { nil } # TODO set to SnippetUpdateRepositoryStorageWorker after https://gitlab.com/gitlab-org/gitlab/-/issues/218991 is implemented
+ let(:repository_storage_worker) { SnippetUpdateRepositoryStorageWorker }
end
end
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index f87259ea048..68d183d5d55 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -796,4 +796,90 @@ RSpec.describe Snippet do
it_behaves_like 'can move repository storage' do
let_it_be(:container) { create(:snippet, :repository) }
end
+
+ describe '#change_head_to_default_branch' do
+ let(:head_path) { Rails.root.join(TestEnv.repos_path, "#{snippet.disk_path}.git", 'HEAD') }
+
+ subject { snippet.change_head_to_default_branch }
+
+ context 'when repository does not exist' do
+ let(:snippet) { create(:snippet) }
+
+ it 'does nothing' do
+ expect(snippet.repository_exists?).to eq false
+ expect(snippet.repository.raw_repository).not_to receive(:write_ref)
+
+ subject
+ end
+ end
+
+ context 'when repository is empty' do
+ let(:snippet) { create(:snippet, :empty_repo) }
+
+ before do
+ allow(Gitlab::CurrentSettings).to receive(:default_branch_name).and_return(default_branch)
+ end
+
+ context 'when default branch in settings is "master"' do
+ let(:default_branch) { 'master' }
+
+ it 'does nothing' do
+ expect(File.read(head_path).squish).to eq 'ref: refs/heads/master'
+
+ expect(snippet.repository.raw_repository).not_to receive(:write_ref)
+
+ subject
+ end
+ end
+
+ context 'when default branch in settings is different from "master"' do
+ let(:default_branch) { 'main' }
+
+ 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}"
+ end
+ end
+ end
+
+ context 'when repository is not empty' do
+ let(:snippet) { create(:snippet, :empty_repo) }
+
+ before do
+ populate_snippet_repo
+ end
+
+ context 'when HEAD branch is empty' do
+ it 'changes HEAD to default branch' do
+ File.write(head_path, 'ref: refs/heads/non_existen_branch')
+ expect(File.read(head_path).squish).to eq 'ref: refs/heads/non_existen_branch'
+
+ subject
+
+ expect(File.read(head_path).squish).to eq 'ref: refs/heads/main'
+ expect(snippet.list_files('HEAD')).not_to be_empty
+ end
+ end
+
+ context 'when HEAD branch is not empty' do
+ it 'does nothing' do
+ File.write(head_path, 'ref: refs/heads/main')
+
+ expect(snippet.repository.raw_repository).not_to receive(:write_ref)
+
+ subject
+ end
+ end
+
+ def populate_snippet_repo
+ allow(Gitlab::CurrentSettings).to receive(:default_branch_name).and_return('main')
+
+ data = [{ file_path: 'new_file_test', content: 'bar' }]
+ snippet.snippet_repository.multi_files_action(snippet.author, data, branch_name: 'main', message: 'foo')
+ end
+ end
+ end
end
diff --git a/spec/models/terraform/state_spec.rb b/spec/models/terraform/state_spec.rb
index ef91e4a5a71..ed311314086 100644
--- a/spec/models/terraform/state_spec.rb
+++ b/spec/models/terraform/state_spec.rb
@@ -27,6 +27,22 @@ RSpec.describe Terraform::State do
end
end
+ describe '#destroy' do
+ let(:terraform_state) { create(:terraform_state) }
+ let(:user) { terraform_state.project.creator }
+
+ it 'deletes when the state is unlocked' do
+ expect(terraform_state.destroy).to be_truthy
+ end
+
+ it 'fails to delete when the state is locked', :aggregate_failures do
+ terraform_state.update!(lock_xid: SecureRandom.uuid, locked_by_user: user, locked_at: Time.current)
+
+ expect(terraform_state.destroy).to be_falsey
+ expect(terraform_state.errors.full_messages).to eq(["You cannot remove the State file because it's locked. Unlock the State file first before removing it."])
+ end
+ end
+
describe '#latest_file' do
let(:terraform_state) { create(:terraform_state, :with_version) }
let(:latest_version) { terraform_state.latest_version }
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index fb05c9e8052..0935d3576a4 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -4084,6 +4084,7 @@ RSpec.describe User do
cache_mock = double
expect(cache_mock).to receive(:delete).with(['users', user.id, 'assigned_open_merge_requests_count'])
+ expect(cache_mock).to receive(:delete).with(['users', user.id, 'review_requested_open_merge_requests_count'])
allow(Rails).to receive(:cache).and_return(cache_mock)
@@ -4163,6 +4164,20 @@ RSpec.describe User do
end
end
+ describe '#review_requested_open_merge_requests_count' do
+ it 'returns number of open merge requests from non-archived projects' do
+ user = create(:user)
+ project = create(:project, :public)
+ archived_project = create(:project, :public, :archived)
+
+ create(:merge_request, source_project: project, author: user, reviewers: [user])
+ create(:merge_request, :closed, source_project: project, author: user, reviewers: [user])
+ create(:merge_request, source_project: archived_project, author: user, reviewers: [user])
+
+ expect(user.review_requested_open_merge_requests_count(force: true)).to eq 1
+ end
+ end
+
describe '#assigned_open_issues_count' do
it 'returns number of open issues from non-archived projects' do
user = create(:user)
@@ -4265,7 +4280,7 @@ RSpec.describe User do
it 'adds the namespace errors to the user' do
user.update(username: new_username)
- expect(user.errors.full_messages.first).to eq('Username has already been taken')
+ expect(user.errors.full_messages.first).to eq('A user, alias, or group already exists with that username.')
end
end
end
@@ -5074,9 +5089,10 @@ RSpec.describe User do
end
describe '#hook_attrs' do
- it 'includes name, username, avatar_url, and email' do
+ it 'includes id, name, username, avatar_url, and email' do
user = create(:user)
user_attributes = {
+ id: user.id,
name: user.name,
username: user.username,
avatar_url: user.avatar_url(only_path: false),