From 983a0bba5d2a042c4a3bbb22432ec192c7501d82 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Mon, 20 Apr 2020 18:38:24 +0000 Subject: Add latest changes from gitlab-org/gitlab@12-10-stable-ee --- spec/models/ci/bridge_spec.rb | 2 - spec/models/ci/build_spec.rb | 108 ++++++++++++--------- spec/models/ci/job_artifact_spec.rb | 21 ++-- spec/models/ci/processable_spec.rb | 78 +++++++++++++++ spec/models/ci/runner_spec.rb | 30 ++++++ spec/models/clusters/applications/fluentd_spec.rb | 50 ++++++++++ spec/models/clusters/applications/ingress_spec.rb | 6 ++ spec/models/clusters/cluster_spec.rb | 3 +- spec/models/concerns/issuable_spec.rb | 34 +++++++ spec/models/cycle_analytics/group_level_spec.rb | 2 +- spec/models/diff_note_position_spec.rb | 7 ++ spec/models/import_failure_spec.rb | 23 ++++- spec/models/jira_import_state_spec.rb | 20 +++- spec/models/merge_request_diff_spec.rb | 39 ++++++++ spec/models/merge_request_spec.rb | 66 +++++++++++-- spec/models/metrics/dashboard/annotation_spec.rb | 26 +++++ spec/models/project_feature_spec.rb | 4 +- spec/models/project_import_state_spec.rb | 21 +++- .../project_services/prometheus_service_spec.rb | 44 +++++++++ spec/models/project_spec.rb | 40 +------- spec/models/resource_milestone_event_spec.rb | 26 +++++ spec/models/terraform/state_spec.rb | 25 +++-- spec/models/user_spec.rb | 44 +++++++-- spec/models/user_type_enums_spec.rb | 13 +++ 24 files changed, 600 insertions(+), 132 deletions(-) create mode 100644 spec/models/clusters/applications/fluentd_spec.rb create mode 100644 spec/models/user_type_enums_spec.rb (limited to 'spec/models') diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb index 31e13122b95..34f89d9cdae 100644 --- a/spec/models/ci/bridge_spec.rb +++ b/spec/models/ci/bridge_spec.rb @@ -17,8 +17,6 @@ describe Ci::Bridge do { trigger: { project: 'my/project', branch: 'master' } } end - it { is_expected.to include_module(Ci::PipelineDelegator) } - it 'has many sourced pipelines' do expect(bridge).to have_many(:sourced_pipelines) end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 673b9e5f076..bdaecea2089 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -37,8 +37,6 @@ describe Ci::Build do it { is_expected.to delegate_method(:merge_request_ref?).to(:pipeline) } it { is_expected.to delegate_method(:legacy_detached_merge_request_pipeline?).to(:pipeline) } - it { is_expected.to include_module(Ci::PipelineDelegator) } - describe 'associations' do it 'has a bidirectional relationship with projects' do expect(described_class.reflect_on_association(:project).has_inverse?).to eq(:builds) @@ -1818,64 +1816,65 @@ describe Ci::Build do end describe '#merge_request' do - def create_mr(build, pipeline, factory: :merge_request, created_at: Time.now) - create(factory, source_project: pipeline.project, - target_project: pipeline.project, - source_branch: build.ref, - created_at: created_at) - end + subject { pipeline.builds.take.merge_request } - context 'when a MR has a reference to the pipeline' do - before do - @merge_request = create_mr(build, pipeline, factory: :merge_request) + context 'on a branch pipeline' do + let!(:pipeline) { create(:ci_pipeline, :with_job, project: project, ref: 'fix') } - commits = [double(id: pipeline.sha)] - allow(@merge_request).to receive(:commits).and_return(commits) - allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request]) + context 'with no merge request' do + it { is_expected.to be_nil } end - it 'returns the single associated MR' do - expect(build.merge_request.id).to eq(@merge_request.id) - end - end + context 'with an open merge request from the same ref name' do + let!(:merge_request) { create(:merge_request, source_project: project, source_branch: 'fix') } - context 'when there is not a MR referencing the pipeline' do - it 'returns nil' do - expect(build.merge_request).to be_nil - end - end + # If no diff exists, the pipeline commit was not part of the merge + # request and may have simply incidentally used the same ref name. + context 'without a merge request diff containing the pipeline commit' do + it { is_expected.to be_nil } + end - context 'when more than one MR have a reference to the pipeline' do - before do - @merge_request = create_mr(build, pipeline, factory: :merge_request) - @merge_request.close! - @merge_request2 = create_mr(build, pipeline, factory: :merge_request) + # If the merge request was truly opened from the branch that the + # pipeline ran on, that head sha will be present in a diff. + context 'with a merge request diff containing the pipeline commit' do + let!(:mr_diff) { create(:merge_request_diff, merge_request: merge_request) } + let!(:mr_diff_commit) { create(:merge_request_diff_commit, sha: build.sha, merge_request_diff: mr_diff) } - commits = [double(id: pipeline.sha)] - allow(@merge_request).to receive(:commits).and_return(commits) - allow(@merge_request2).to receive(:commits).and_return(commits) - allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request, @merge_request2]) + it { is_expected.to eq(merge_request) } + end end - it 'returns the first MR' do - expect(build.merge_request.id).to eq(@merge_request.id) + context 'with multiple open merge requests' do + let!(:merge_request) { create(:merge_request, source_project: project, source_branch: 'fix') } + let!(:mr_diff) { create(:merge_request_diff, merge_request: merge_request) } + let!(:mr_diff_commit) { create(:merge_request_diff_commit, sha: build.sha, merge_request_diff: mr_diff) } + + let!(:new_merge_request) { create(:merge_request, source_project: project, source_branch: 'fix', target_branch: 'staging') } + let!(:new_mr_diff) { create(:merge_request_diff, merge_request: new_merge_request) } + let!(:new_mr_diff_commit) { create(:merge_request_diff_commit, sha: build.sha, merge_request_diff: new_mr_diff) } + + it 'returns the first merge request' do + expect(subject).to eq(merge_request) + end end end - context 'when a Build is created after the MR' do - before do - @merge_request = create_mr(build, pipeline, factory: :merge_request_with_diffs) - pipeline2 = create(:ci_pipeline, project: project) - @build2 = create(:ci_build, pipeline: pipeline2) + context 'on a detached merged request pipeline' do + let(:pipeline) { create(:ci_pipeline, :detached_merge_request_pipeline, :with_job) } - allow(@merge_request).to receive(:commit_shas) - .and_return([pipeline.sha, pipeline2.sha]) - allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request]) - end + it { is_expected.to eq(pipeline.merge_request) } + end - it 'returns the current MR' do - expect(@build2.merge_request.id).to eq(@merge_request.id) - end + context 'on a legacy detached merged request pipeline' do + let(:pipeline) { create(:ci_pipeline, :legacy_detached_merge_request_pipeline, :with_job) } + + it { is_expected.to eq(pipeline.merge_request) } + end + + context 'on a pipeline for merged results' do + let(:pipeline) { create(:ci_pipeline, :merged_result_pipeline, :with_job) } + + it { is_expected.to eq(pipeline.merge_request) } end end @@ -2281,6 +2280,7 @@ describe Ci::Build do { key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true, masked: false }, { key: 'CI_REGISTRY_PASSWORD', value: 'my-token', public: false, masked: true }, { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false, masked: false }, + { key: 'CI_JOB_JWT', value: 'ci.job.jwt', public: false, masked: true }, { key: 'CI_JOB_NAME', value: 'test', public: true, masked: false }, { key: 'CI_JOB_STAGE', value: 'test', public: true, masked: false }, { key: 'CI_NODE_TOTAL', value: '1', public: true, masked: false }, @@ -2333,23 +2333,36 @@ describe Ci::Build do end before do + allow(Gitlab::Ci::Jwt).to receive(:for_build).with(build).and_return('ci.job.jwt') build.set_token('my-token') build.yaml_variables = [] end it { is_expected.to eq(predefined_variables) } + context 'when ci_job_jwt feature flag is disabled' do + before do + stub_feature_flags(ci_job_jwt: false) + end + + it 'CI_JOB_JWT is not included' do + expect(subject.pluck(:key)).not_to include('CI_JOB_JWT') + end + end + describe 'variables ordering' do context 'when variables hierarchy is stubbed' do let(:build_pre_var) { { key: 'build', value: 'value', public: true, masked: false } } let(:project_pre_var) { { key: 'project', value: 'value', public: true, masked: false } } let(:pipeline_pre_var) { { key: 'pipeline', value: 'value', public: true, masked: false } } let(:build_yaml_var) { { key: 'yaml', value: 'value', public: true, masked: false } } + let(:job_jwt_var) { { key: 'CI_JOB_JWT', value: 'ci.job.jwt', public: false, masked: true } } before do allow(build).to receive(:predefined_variables) { [build_pre_var] } allow(build).to receive(:yaml_variables) { [build_yaml_var] } allow(build).to receive(:persisted_variables) { [] } + allow(build).to receive(:job_jwt_variables) { [job_jwt_var] } allow_any_instance_of(Project) .to receive(:predefined_variables) { [project_pre_var] } @@ -2362,7 +2375,8 @@ describe Ci::Build do it 'returns variables in order depending on resource hierarchy' do is_expected.to eq( - [build_pre_var, + [job_jwt_var, + build_pre_var, project_pre_var, pipeline_pre_var, build_yaml_var, diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb index 80b619ed2b1..6f6ff3704b4 100644 --- a/spec/models/ci/job_artifact_spec.rb +++ b/spec/models/ci/job_artifact_spec.rb @@ -349,13 +349,16 @@ describe Ci::JobArtifact do end describe 'file is being stored' do - context 'when object has nil store' do - it 'is stored locally' do - subject = build(:ci_job_artifact, :archive, file_store: nil) + subject { create(:ci_job_artifact, :archive) } - subject.save + context 'when object has nil store' do + before do + subject.update_column(:file_store, nil) + subject.reload + end - expect(subject.file_store).to be(ObjectStorage::Store::LOCAL) + it 'is stored locally' do + expect(subject.file_store).to be(nil) expect(subject.file).to be_file_storage expect(subject.file.object_store).to eq(ObjectStorage::Store::LOCAL) end @@ -363,10 +366,6 @@ describe Ci::JobArtifact do context 'when existing object has local store' do it 'is stored locally' do - subject = build(:ci_job_artifact, :archive) - - subject.save - expect(subject.file_store).to be(ObjectStorage::Store::LOCAL) expect(subject.file).to be_file_storage expect(subject.file.object_store).to eq(ObjectStorage::Store::LOCAL) @@ -380,10 +379,6 @@ describe Ci::JobArtifact do context 'when file is stored' do it 'is stored remotely' do - subject = build(:ci_job_artifact, :archive) - - subject.save - expect(subject.file_store).to eq(ObjectStorage::Store::REMOTE) expect(subject.file).not_to be_file_storage expect(subject.file.object_store).to eq(ObjectStorage::Store::REMOTE) diff --git a/spec/models/ci/processable_spec.rb b/spec/models/ci/processable_spec.rb index e8ef7b29681..4490371bde5 100644 --- a/spec/models/ci/processable_spec.rb +++ b/spec/models/ci/processable_spec.rb @@ -6,6 +6,18 @@ describe Ci::Processable do let_it_be(:project) { create(:project) } let_it_be(:pipeline) { create(:ci_pipeline, project: project) } + let_it_be(:detached_merge_request_pipeline) do + create(:ci_pipeline, :detached_merge_request_pipeline, :with_job, project: project) + end + + let_it_be(:legacy_detached_merge_request_pipeline) do + create(:ci_pipeline, :legacy_detached_merge_request_pipeline, :with_job, project: project) + end + + let_it_be(:merged_result_pipeline) do + create(:ci_pipeline, :merged_result_pipeline, :with_job, project: project) + end + describe '#aggregated_needs_names' do let(:with_aggregated_needs) { pipeline.processables.select_with_aggregated_needs(project) } @@ -155,4 +167,70 @@ describe Ci::Processable do end end end + + describe '#merge_request?' do + subject { pipeline.processables.first.merge_request? } + + context 'in a detached merge request pipeline' do + let(:pipeline) { detached_merge_request_pipeline } + + it { is_expected.to eq(pipeline.merge_request?) } + end + + context 'in a legacy detached merge_request_pipeline' do + let(:pipeline) { legacy_detached_merge_request_pipeline } + + it { is_expected.to eq(pipeline.merge_request?) } + end + + context 'in a pipeline for merged results' do + let(:pipeline) { merged_result_pipeline } + + it { is_expected.to eq(pipeline.merge_request?) } + end + end + + describe '#merge_request_ref?' do + subject { pipeline.processables.first.merge_request_ref? } + + context 'in a detached merge request pipeline' do + let(:pipeline) { detached_merge_request_pipeline } + + it { is_expected.to eq(pipeline.merge_request_ref?) } + end + + context 'in a legacy detached merge_request_pipeline' do + let(:pipeline) { legacy_detached_merge_request_pipeline } + + it { is_expected.to eq(pipeline.merge_request_ref?) } + end + + context 'in a pipeline for merged results' do + let(:pipeline) { merged_result_pipeline } + + it { is_expected.to eq(pipeline.merge_request_ref?) } + end + end + + describe '#legacy_detached_merge_request_pipeline?' do + subject { pipeline.processables.first.legacy_detached_merge_request_pipeline? } + + context 'in a detached merge request pipeline' do + let(:pipeline) { detached_merge_request_pipeline } + + it { is_expected.to eq(pipeline.legacy_detached_merge_request_pipeline?) } + end + + context 'in a legacy detached merge_request_pipeline' do + let(:pipeline) { legacy_detached_merge_request_pipeline } + + it { is_expected.to eq(pipeline.legacy_detached_merge_request_pipeline?) } + end + + context 'in a pipeline for merged results' do + let(:pipeline) { merged_result_pipeline } + + it { is_expected.to eq(pipeline.legacy_detached_merge_request_pipeline?) } + end + end end diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index b8034ba5bf2..2dedff7f15b 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -78,6 +78,36 @@ describe Ci::Runner do .to raise_error(ActiveRecord::RecordInvalid) end end + + context 'cost factors validations' do + it 'dissalows :private_projects_minutes_cost_factor being nil' do + runner = build(:ci_runner, private_projects_minutes_cost_factor: nil) + + expect(runner).to be_invalid + expect(runner.errors.full_messages).to include('Private projects minutes cost factor needs to be non-negative') + end + + it 'dissalows :public_projects_minutes_cost_factor being nil' do + runner = build(:ci_runner, public_projects_minutes_cost_factor: nil) + + expect(runner).to be_invalid + expect(runner.errors.full_messages).to include('Public projects minutes cost factor needs to be non-negative') + end + + it 'dissalows :private_projects_minutes_cost_factor being negative' do + runner = build(:ci_runner, private_projects_minutes_cost_factor: -1.1) + + expect(runner).to be_invalid + expect(runner.errors.full_messages).to include('Private projects minutes cost factor needs to be non-negative') + end + + it 'dissalows :public_projects_minutes_cost_factor being negative' do + runner = build(:ci_runner, public_projects_minutes_cost_factor: -2.2) + + expect(runner).to be_invalid + expect(runner.errors.full_messages).to include('Public projects minutes cost factor needs to be non-negative') + end + end end describe 'constraints' do diff --git a/spec/models/clusters/applications/fluentd_spec.rb b/spec/models/clusters/applications/fluentd_spec.rb new file mode 100644 index 00000000000..7e9680b0ab4 --- /dev/null +++ b/spec/models/clusters/applications/fluentd_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Clusters::Applications::Fluentd do + let(:fluentd) { create(:clusters_applications_fluentd) } + + include_examples 'cluster application core specs', :clusters_applications_fluentd + include_examples 'cluster application status specs', :clusters_applications_fluentd + include_examples 'cluster application version specs', :clusters_applications_fluentd + include_examples 'cluster application initial status specs' + + describe '#can_uninstall?' do + subject { fluentd.can_uninstall? } + + it { is_expected.to be true } + end + + describe '#install_command' do + subject { fluentd.install_command } + + it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) } + + it 'is initialized with fluentd arguments' do + expect(subject.name).to eq('fluentd') + expect(subject.chart).to eq('stable/fluentd') + expect(subject.version).to eq('2.4.0') + expect(subject).to be_rbac + end + + context 'application failed to install previously' do + let(:fluentd) { create(:clusters_applications_fluentd, :errored, version: '0.0.1') } + + it 'is initialized with the locked version' do + expect(subject.version).to eq('2.4.0') + end + end + end + + describe '#files' do + let(:application) { fluentd } + let(:values) { subject[:'values.yaml'] } + + subject { application.files } + + it 'includes fluentd specific keys in the values.yaml file' do + expect(values).to include('output.conf', 'general.conf') + end + end +end diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb index 64d667f40f6..b070729ccac 100644 --- a/spec/models/clusters/applications/ingress_spec.rb +++ b/spec/models/clusters/applications/ingress_spec.rb @@ -219,6 +219,12 @@ describe Clusters::Applications::Ingress do expect(subject.values).to include('extraContainers') end + + it 'includes livenessProbe for modsecurity sidecar container' do + probe_config = YAML.safe_load(subject.values).dig('controller', 'extraContainers', 0, 'livenessProbe') + + expect(probe_config).to eq('exec' => { 'command' => ['ls', '/var/log/modsec/audit.log'] }) + end end context 'when modsecurity_enabled is disabled' do diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index 29c75186110..db1d8672d1e 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -582,9 +582,10 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do let!(:jupyter) { create(:clusters_applications_jupyter, cluster: cluster) } let!(:knative) { create(:clusters_applications_knative, cluster: cluster) } let!(:elastic_stack) { create(:clusters_applications_elastic_stack, cluster: cluster) } + let!(:fluentd) { create(:clusters_applications_fluentd, cluster: cluster) } it 'returns a list of created applications' do - is_expected.to contain_exactly(helm, ingress, cert_manager, crossplane, prometheus, runner, jupyter, knative, elastic_stack) + is_expected.to contain_exactly(helm, ingress, cert_manager, crossplane, prometheus, runner, jupyter, knative, elastic_stack, fluentd) end end end diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index cc1bb164c16..24908785320 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -496,6 +496,40 @@ describe Issuable do end end + describe '.labels_hash' do + let(:feature_label) { create(:label, title: 'Feature') } + let(:second_label) { create(:label, title: 'Second Label') } + let!(:issues) { create_list(:labeled_issue, 3, labels: [feature_label, second_label]) } + let(:issue_id) { issues.first.id } + + it 'maps issue ids to labels titles' do + expect(Issue.labels_hash[issue_id]).to include('Feature') + end + + it 'works on relations filtered by multiple labels' do + relation = Issue.with_label(['Feature', 'Second Label']) + + expect(relation.labels_hash[issue_id]).to include('Feature', 'Second Label') + end + + # This tests the workaround for the lack of a NOT NULL constraint in + # label_links.label_id: + # https://gitlab.com/gitlab-org/gitlab/issues/197307 + context 'with a NULL label ID in the link' do + let(:issue) { create(:labeled_issue, labels: [feature_label, second_label]) } + + before do + label_link = issue.label_links.find_by(label_id: second_label.id) + label_link.label_id = nil + label_link.save(validate: false) + end + + it 'filters out bad labels' do + expect(Issue.where(id: issue.id).labels_hash[issue.id]).to match_array(['Feature']) + end + end + end + describe '#user_notes_count' do let(:project) { create(:project) } let(:issue1) { create(:issue, project: project) } diff --git a/spec/models/cycle_analytics/group_level_spec.rb b/spec/models/cycle_analytics/group_level_spec.rb index 1f410a7c539..ac169ebc0cf 100644 --- a/spec/models/cycle_analytics/group_level_spec.rb +++ b/spec/models/cycle_analytics/group_level_spec.rb @@ -38,7 +38,7 @@ describe CycleAnalytics::GroupLevel do end it 'returns medians for each stage for a specific group' do - expect(subject.summary.map { |summary| summary[:value] }).to contain_exactly(1, 1) + expect(subject.summary.map { |summary| summary[:value] }).to contain_exactly('0.1', '1', '1') end end end diff --git a/spec/models/diff_note_position_spec.rb b/spec/models/diff_note_position_spec.rb index dedb8a8da4d..d93e0af5526 100644 --- a/spec/models/diff_note_position_spec.rb +++ b/spec/models/diff_note_position_spec.rb @@ -40,4 +40,11 @@ describe DiffNotePosition, type: :model do expect { diff_note_position.save! }.to raise_error(ActiveRecord::RecordNotUnique) end + + it 'accepts a line_range attribute' do + diff_note_position = build(:diff_note_position) + + expect(diff_note_position).to respond_to(:line_range) + expect(diff_note_position).to respond_to(:line_range=) + end end diff --git a/spec/models/import_failure_spec.rb b/spec/models/import_failure_spec.rb index d6574791a65..d286a4ad314 100644 --- a/spec/models/import_failure_spec.rb +++ b/spec/models/import_failure_spec.rb @@ -3,7 +3,28 @@ require 'spec_helper' describe ImportFailure do - describe "Associations" do + describe 'Scopes' do + let_it_be(:project) { create(:project) } + let_it_be(:correlation_id) { 'ABC' } + let_it_be(:hard_failure) { create(:import_failure, :hard_failure, project: project, correlation_id_value: correlation_id) } + let_it_be(:soft_failure) { create(:import_failure, :soft_failure, project: project, correlation_id_value: correlation_id) } + let_it_be(:unrelated_failure) { create(:import_failure, project: project) } + + it 'returns hard failures given a correlation ID' do + expect(ImportFailure.hard_failures_by_correlation_id(correlation_id)).to eq([hard_failure]) + end + + it 'orders hard failures by newest first' do + older_failure = hard_failure.dup + Timecop.freeze(1.day.before(hard_failure.created_at)) do + older_failure.save! + + expect(ImportFailure.hard_failures_by_correlation_id(correlation_id)).to eq([hard_failure, older_failure]) + end + end + end + + describe 'Associations' do it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:group) } end diff --git a/spec/models/jira_import_state_spec.rb b/spec/models/jira_import_state_spec.rb index f75a17f71b2..4d91bf25b5e 100644 --- a/spec/models/jira_import_state_spec.rb +++ b/spec/models/jira_import_state_spec.rb @@ -130,8 +130,10 @@ describe JiraImportState do context 'after transition to finished' do let!(:jira_import) { build(:jira_import_state, :started, jid: 'some-other-jid', project: project)} + subject { jira_import.finish } + it 'triggers the import job' do - jira_import.finish + subject expect(jira_import.jid).to be_nil end @@ -139,11 +141,25 @@ describe JiraImportState do it 'triggers the import job' do jira_import.update!(status: :scheduled) - jira_import.finish + subject expect(jira_import.status).to eq('scheduled') expect(jira_import.jid).to eq('some-other-jid') end + + it 'updates the record with imported issues counts' do + import_label = create(:label, project: project, title: 'jira-import') + create_list(:labeled_issue, 3, project: project, labels: [import_label]) + + expect(Gitlab::JiraImport).to receive(:get_import_label_id).and_return(import_label.id) + expect(Gitlab::JiraImport).to receive(:issue_failures).and_return(2) + + subject + + expect(jira_import.total_issue_count).to eq(5) + expect(jira_import.failed_to_import_count).to eq(2) + expect(jira_import.imported_issues_count).to eq(3) + end end end end diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index 6d2ad3f0475..016af4f269b 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -566,6 +566,45 @@ describe MergeRequestDiff do it 'returns affected file paths' do expect(subject.modified_paths).to eq(%w{foo bar baz}) end + + context "when fallback_on_overflow is true" do + let(:merge_request) { create(:merge_request, source_branch: 'feature', target_branch: 'master') } + let(:diff) { merge_request.merge_request_diff } + + # before do + # # Temporarily unstub diff.modified_paths in favor of original code + # # + # allow(diff).to receive(:modified_paths).and_call_original + # end + + context "when the merge_request_diff is overflowed" do + before do + expect(diff).to receive(:overflow?).and_return(true) + end + + it "returns file paths via project.repository#diff_stats" do + expect(diff.project.repository).to receive(:diff_stats).and_call_original + + expect( + diff.modified_paths(fallback_on_overflow: true) + ).to eq(diff.modified_paths) + end + end + + context "when the merge_request_diff is not overflowed" do + before do + expect(subject).to receive(:overflow?).and_return(false) + end + + it "returns expect file paths withoout called #modified_paths_for_overflowed_mr" do + expect(subject.project.repository).not_to receive(:diff_stats) + + expect( + subject.modified_paths(fallback_on_overflow: true) + ).to eq(subject.modified_paths) + end + end + end end describe '#opening_external_diff' do diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 50bb194ef71..52cd31ee65f 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -2335,6 +2335,21 @@ describe MergeRequest do end end + describe "#public_merge_status" do + using RSpec::Parameterized::TableSyntax + subject { build(:merge_request, merge_status: status) } + + where(:status, :public_status) do + 'cannot_be_merged_rechecking' | 'checking' + 'checking' | 'checking' + 'cannot_be_merged' | 'cannot_be_merged' + end + + with_them do + it { expect(subject.public_merge_status).to eq(public_status) } + end + end + describe "#head_pipeline_active? " do it do is_expected @@ -3226,20 +3241,51 @@ describe MergeRequest do expect(notification_service).to receive(:merge_request_unmergeable).with(subject).once expect(todo_service).to receive(:merge_request_became_unmergeable).with(subject).once - subject.mark_as_unmergeable - subject.mark_as_unchecked - subject.mark_as_unmergeable + subject.mark_as_unmergeable! + + subject.mark_as_unchecked! + subject.mark_as_unmergeable! + end + + it 'notifies conflict, but does not notify again if rechecking still results in cannot_be_merged with async mergeability check' do + expect(notification_service).to receive(:merge_request_unmergeable).with(subject).once + expect(todo_service).to receive(:merge_request_became_unmergeable).with(subject).once + + subject.mark_as_checking! + subject.mark_as_unmergeable! + + subject.mark_as_unchecked! + subject.mark_as_checking! + subject.mark_as_unmergeable! end it 'notifies conflict, whenever newly unmergeable' do expect(notification_service).to receive(:merge_request_unmergeable).with(subject).twice expect(todo_service).to receive(:merge_request_became_unmergeable).with(subject).twice - subject.mark_as_unmergeable - subject.mark_as_unchecked - subject.mark_as_mergeable - subject.mark_as_unchecked - subject.mark_as_unmergeable + subject.mark_as_unmergeable! + + subject.mark_as_unchecked! + subject.mark_as_mergeable! + + subject.mark_as_unchecked! + subject.mark_as_unmergeable! + end + + it 'notifies conflict, whenever newly unmergeable with async mergeability check' do + expect(notification_service).to receive(:merge_request_unmergeable).with(subject).twice + expect(todo_service).to receive(:merge_request_became_unmergeable).with(subject).twice + + subject.mark_as_checking! + subject.mark_as_unmergeable! + + subject.mark_as_unchecked! + subject.mark_as_checking! + subject.mark_as_mergeable! + + subject.mark_as_unchecked! + subject.mark_as_checking! + subject.mark_as_unmergeable! end it 'does not notify whenever merge request is newly unmergeable due to other reasons' do @@ -3248,7 +3294,7 @@ describe MergeRequest do expect(notification_service).not_to receive(:merge_request_unmergeable) expect(todo_service).not_to receive(:merge_request_became_unmergeable) - subject.mark_as_unmergeable + subject.mark_as_unmergeable! end end end @@ -3261,7 +3307,7 @@ describe MergeRequest do expect(notification_service).not_to receive(:merge_request_unmergeable) expect(todo_service).not_to receive(:merge_request_became_unmergeable) - subject.mark_as_unmergeable + subject.mark_as_unmergeable! end end end diff --git a/spec/models/metrics/dashboard/annotation_spec.rb b/spec/models/metrics/dashboard/annotation_spec.rb index ed3bef37a7c..f7fd7ded7e6 100644 --- a/spec/models/metrics/dashboard/annotation_spec.rb +++ b/spec/models/metrics/dashboard/annotation_spec.rb @@ -50,4 +50,30 @@ describe Metrics::Dashboard::Annotation do end end end + + describe 'scopes' do + let_it_be(:nine_minutes_old_annotation) { create(:metrics_dashboard_annotation, starting_at: 9.minutes.ago) } + let_it_be(:fifteen_minutes_old_annotation) { create(:metrics_dashboard_annotation, starting_at: 15.minutes.ago) } + let_it_be(:just_created_annotation) { create(:metrics_dashboard_annotation) } + + describe '#after' do + it 'returns only younger annotations' do + expect(described_class.after(12.minutes.ago)).to match_array [nine_minutes_old_annotation, just_created_annotation] + end + end + + describe '#before' do + it 'returns only older annotations' do + expect(described_class.before(5.minutes.ago)).to match_array [fifteen_minutes_old_annotation, nine_minutes_old_annotation] + end + end + + describe '#for_dashboard' do + let!(:other_dashboard_annotation) { create(:metrics_dashboard_annotation, dashboard_path: 'other_dashboard.yml') } + + it 'returns annotations only for appointed dashboard' do + expect(described_class.for_dashboard('other_dashboard.yml')).to match_array [other_dashboard_annotation] + end + end + end end diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb index 6a333898955..38fba5ea071 100644 --- a/spec/models/project_feature_spec.rb +++ b/spec/models/project_feature_spec.rb @@ -27,7 +27,7 @@ describe ProjectFeature do end describe '#feature_available?' do - let(:features) { %w(issues wiki builds merge_requests snippets repository pages) } + let(:features) { %w(issues wiki builds merge_requests snippets repository pages metrics_dashboard) } context 'when features are disabled' do it "returns false" do @@ -123,7 +123,7 @@ describe ProjectFeature do end context 'public features' do - features = %w(issues wiki builds merge_requests snippets repository) + features = %w(issues wiki builds merge_requests snippets repository metrics_dashboard) features.each do |feature| it "does not allow public access level for #{feature}" do diff --git a/spec/models/project_import_state_spec.rb b/spec/models/project_import_state_spec.rb index 720dc4f435f..cb34d898a6e 100644 --- a/spec/models/project_import_state_spec.rb +++ b/spec/models/project_import_state_spec.rb @@ -3,7 +3,10 @@ require 'spec_helper' describe ProjectImportState, type: :model do - subject { create(:import_state) } + let_it_be(:correlation_id) { 'cid' } + let_it_be(:import_state, refind: true) { create(:import_state, correlation_id_value: correlation_id) } + + subject { import_state } describe 'associations' do it { is_expected.to belong_to(:project) } @@ -33,12 +36,24 @@ describe ProjectImportState, type: :model do end it 'records job and correlation IDs', :sidekiq_might_not_need_inline do - allow(Labkit::Correlation::CorrelationId).to receive(:current_or_new_id).and_return('abc') + allow(Labkit::Correlation::CorrelationId).to receive(:current_or_new_id).and_return(correlation_id) import_state.schedule expect(import_state.jid).to be_an_instance_of(String) - expect(import_state.correlation_id).to eq('abc') + expect(import_state.correlation_id).to eq(correlation_id) + end + end + + describe '#relation_hard_failures' do + let_it_be(:failures) { create_list(:import_failure, 2, :hard_failure, project: import_state.project, correlation_id_value: correlation_id) } + + it 'returns hard relation failures related to this import' do + expect(subject.relation_hard_failures(limit: 100)).to match_array(failures) + end + + it 'limits returned collection to given maximum' do + expect(subject.relation_hard_failures(limit: 1).size).to eq(1) end end diff --git a/spec/models/project_services/prometheus_service_spec.rb b/spec/models/project_services/prometheus_service_spec.rb index 5565d30d8c1..a85dbe3a7df 100644 --- a/spec/models/project_services/prometheus_service_spec.rb +++ b/spec/models/project_services/prometheus_service_spec.rb @@ -418,4 +418,48 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do end end end + + describe '#editable?' do + it 'is editable' do + expect(service.editable?).to be(true) + end + + context 'when cluster exists with prometheus installed' do + let(:cluster) { create(:cluster, projects: [project]) } + + before do + service.update!(manual_configuration: false) + + create(:clusters_applications_prometheus, :installed, cluster: cluster) + end + + it 'remains editable' do + expect(service.editable?).to be(true) + end + end + end + + describe '#fields' do + let(:expected_fields) do + [ + { + type: 'checkbox', + name: 'manual_configuration', + title: s_('PrometheusService|Active'), + required: true + }, + { + type: 'text', + name: 'api_url', + title: 'API URL', + placeholder: s_('PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/'), + required: true + } + ] + end + + it 'returns fields' do + expect(service.fields).to eq(expected_fields) + end + end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 3c8afee4466..4e75ef4fc87 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1740,32 +1740,12 @@ describe Project do let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, group: group) } - context 'when feature is enabled' do - before do - stub_feature_flags(project_search_by_full_path: true) - end - - it 'returns projects that match the group path' do - expect(described_class.search(group.path, include_namespace: true)).to eq([project]) - end - - it 'returns projects that match the full path' do - expect(described_class.search(project.full_path, include_namespace: true)).to eq([project]) - end + it 'returns projects that match the group path' do + expect(described_class.search(group.path, include_namespace: true)).to eq([project]) end - context 'when feature is disabled' do - before do - stub_feature_flags(project_search_by_full_path: false) - end - - it 'returns no results when searching by group path' do - expect(described_class.search(group.path, include_namespace: true)).to be_empty - end - - it 'returns no results when searching by full path' do - expect(described_class.search(project.full_path, include_namespace: true)).to be_empty - end + it 'returns projects that match the full path' do + expect(described_class.search(project.full_path, include_namespace: true)).to eq([project]) end end @@ -2665,18 +2645,6 @@ describe Project do end end - describe '#daily_statistics_enabled?' do - it { is_expected.to be_daily_statistics_enabled } - - context 'when :project_daily_statistics is disabled for the project' do - before do - stub_feature_flags(project_daily_statistics: { thing: subject, enabled: false }) - end - - it { is_expected.not_to be_daily_statistics_enabled } - end - end - describe '#change_head' do let(:project) { create(:project, :repository) } diff --git a/spec/models/resource_milestone_event_spec.rb b/spec/models/resource_milestone_event_spec.rb index 1b0181e3fd2..bf8672f95c9 100644 --- a/spec/models/resource_milestone_event_spec.rb +++ b/spec/models/resource_milestone_event_spec.rb @@ -52,4 +52,30 @@ describe ResourceMilestoneEvent, type: :model do end end end + + shared_examples 'a milestone action queryable resource event' do |expected_results_for_actions| + [Issue, MergeRequest].each do |klass| + expected_results_for_actions.each do |action, expected_result| + it "is #{expected_result} for action #{action} on #{klass.name.underscore}" do + model = create(klass.name.underscore) + key = model.class.name.underscore + event = build(described_class.name.underscore.to_sym, key => model, action: action) + + expect(event.send(query_method)).to eq(expected_result) + end + end + end + end + + describe '#added?' do + it_behaves_like 'a milestone action queryable resource event', { add: true, remove: false } do + let(:query_method) { :add? } + end + end + + describe '#removed?' do + it_behaves_like 'a milestone action queryable resource event', { add: false, remove: true } do + let(:query_method) { :remove? } + end + end end diff --git a/spec/models/terraform/state_spec.rb b/spec/models/terraform/state_spec.rb index 1d677e7ece5..3cd15e23ee2 100644 --- a/spec/models/terraform/state_spec.rb +++ b/spec/models/terraform/state_spec.rb @@ -5,24 +5,35 @@ require 'spec_helper' describe Terraform::State do subject { create(:terraform_state, :with_file) } + let(:terraform_state_file) { fixture_file('terraform/terraform.tfstate') } + it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:locked_by_user).class_name('User') } + it { is_expected.to validate_presence_of(:project_id) } before do stub_terraform_state_object_storage(Terraform::StateUploader) end - describe '#file_store' do - context 'when no value is set' do - it 'returns the default store of the uploader' do - [ObjectStorage::Store::LOCAL, ObjectStorage::Store::REMOTE].each do |store| - expect(Terraform::StateUploader).to receive(:default_store).and_return(store) - expect(described_class.new.file_store).to eq(store) - end + describe '#file' do + context 'when a file exists' do + it 'does not use the default file' do + expect(subject.file.read).to eq(terraform_state_file) end end + context 'when no file exists' do + subject { create(:terraform_state) } + + it 'creates a default file' do + expect(subject.file.read).to eq('{"version":1}') + end + end + end + + describe '#file_store' do context 'when a value is set' do it 'returns the value' do [ObjectStorage::Store::LOCAL, ObjectStorage::Store::REMOTE].each do |store| diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 5a3e16baa87..8597397c3c6 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -4357,18 +4357,19 @@ describe User, :do_not_mock_admin_mode do describe 'internal methods' do let_it_be(:user) { create(:user) } - let!(:ghost) { described_class.ghost } - let!(:alert_bot) { described_class.alert_bot } - let!(:non_internal) { [user] } - let!(:internal) { [ghost, alert_bot] } + let_it_be(:ghost) { described_class.ghost } + let_it_be(:alert_bot) { described_class.alert_bot } + let_it_be(:project_bot) { create(:user, :project_bot) } + let_it_be(:non_internal) { [user, project_bot] } + let_it_be(:internal) { [ghost, alert_bot] } it 'returns internal users' do - expect(described_class.internal).to eq(internal) + expect(described_class.internal).to match_array(internal) expect(internal.all?(&:internal?)).to eq(true) end it 'returns non internal users' do - expect(described_class.non_internal).to eq(non_internal) + expect(described_class.non_internal).to match_array(non_internal) expect(non_internal.all?(&:internal?)).to eq(false) end @@ -4420,9 +4421,12 @@ describe User, :do_not_mock_admin_mode do it 'returns corresponding users' do human = create(:user) bot = create(:user, :bot) + project_bot = create(:user, :project_bot) expect(described_class.humans).to match_array([human]) - expect(described_class.bots).to match_array([bot]) + expect(described_class.bots).to match_array([bot, project_bot]) + expect(described_class.bots_without_project_bot).to match_array([bot]) + expect(described_class.with_project_bots).to match_array([human, project_bot]) end end @@ -4655,4 +4659,30 @@ describe User, :do_not_mock_admin_mode do it { is_expected.to be :locked } end end + + describe '#password_required?' do + let_it_be(:user) { create(:user) } + + shared_examples 'does not require password to be present' do + it { expect(user).not_to validate_presence_of(:password) } + + it { expect(user).not_to validate_presence_of(:password_confirmation) } + end + + context 'when user is an internal user' do + before do + user.update(user_type: 'alert_bot') + end + + it_behaves_like 'does not require password to be present' + end + + context 'when user is a project bot user' do + before do + user.update(user_type: 'project_bot') + end + + it_behaves_like 'does not require password to be present' + end + end end diff --git a/spec/models/user_type_enums_spec.rb b/spec/models/user_type_enums_spec.rb new file mode 100644 index 00000000000..4f56e6ea96e --- /dev/null +++ b/spec/models/user_type_enums_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe UserTypeEnums do + it '.types' do + expect(described_class.types.keys).to include('alert_bot', 'project_bot', 'human', 'ghost') + end + + it '.bots' do + expect(described_class.bots.keys).to include('alert_bot', 'project_bot') + end +end -- cgit v1.2.3