diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-18 13:34:06 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-18 13:34:06 +0300 |
commit | 859a6fb938bb9ee2a317c46dfa4fcc1af49608f0 (patch) | |
tree | d7f2700abe6b4ffcb2dcfc80631b2d87d0609239 /spec/finders | |
parent | 446d496a6d000c73a304be52587cd9bbc7493136 (diff) |
Add latest changes from gitlab-org/gitlab@13-9-stable-eev13.9.0-rc42
Diffstat (limited to 'spec/finders')
-rw-r--r-- | spec/finders/autocomplete/users_finder_spec.rb | 5 | ||||
-rw-r--r-- | spec/finders/ci/jobs_finder_spec.rb | 33 | ||||
-rw-r--r-- | spec/finders/ci/testing/daily_build_group_report_results_finder_spec.rb | 99 | ||||
-rw-r--r-- | spec/finders/container_repositories_finder_spec.rb | 30 | ||||
-rw-r--r-- | spec/finders/deployments_finder_spec.rb | 207 | ||||
-rw-r--r-- | spec/finders/license_template_finder_spec.rb | 43 | ||||
-rw-r--r-- | spec/finders/merge_request/metrics_finder_spec.rb | 76 | ||||
-rw-r--r-- | spec/finders/merge_requests/oldest_per_commit_finder_spec.rb | 46 | ||||
-rw-r--r-- | spec/finders/packages/group_packages_finder_spec.rb | 1 | ||||
-rw-r--r-- | spec/finders/packages/packages_finder_spec.rb | 1 | ||||
-rw-r--r-- | spec/finders/repositories/commits_with_trailer_finder_spec.rb | 38 | ||||
-rw-r--r-- | spec/finders/repositories/previous_tag_finder_spec.rb | 41 | ||||
-rw-r--r-- | spec/finders/template_finder_spec.rb | 192 | ||||
-rw-r--r-- | spec/finders/terraform/states_finder_spec.rb | 45 | ||||
-rw-r--r-- | spec/finders/user_recent_events_finder_spec.rb | 120 |
15 files changed, 866 insertions, 111 deletions
diff --git a/spec/finders/autocomplete/users_finder_spec.rb b/spec/finders/autocomplete/users_finder_spec.rb index 357b6dfcea2..28bd7e12916 100644 --- a/spec/finders/autocomplete/users_finder_spec.rb +++ b/spec/finders/autocomplete/users_finder_spec.rb @@ -118,5 +118,10 @@ RSpec.describe Autocomplete::UsersFinder do it { is_expected.to match_array([user1, external_user, omniauth_user, current_user]) } end + + it 'preloads the status association' do + associations = subject.map { |user| user.association(:status) } + expect(associations).to all(be_loaded) + end end end diff --git a/spec/finders/ci/jobs_finder_spec.rb b/spec/finders/ci/jobs_finder_spec.rb index 4a6585e3f2b..ab056dd26e8 100644 --- a/spec/finders/ci/jobs_finder_spec.rb +++ b/spec/finders/ci/jobs_finder_spec.rb @@ -9,7 +9,7 @@ RSpec.describe Ci::JobsFinder, '#execute' do let_it_be(:pipeline) { create(:ci_pipeline, project: project) } let_it_be(:job_1) { create(:ci_build) } let_it_be(:job_2) { create(:ci_build, :running) } - let_it_be(:job_3) { create(:ci_build, :success, pipeline: pipeline) } + let_it_be(:job_3) { create(:ci_build, :success, pipeline: pipeline, name: 'build') } let(:params) { {} } @@ -95,4 +95,35 @@ RSpec.describe Ci::JobsFinder, '#execute' do end end end + + context 'when pipeline is present' do + before_all do + project.add_maintainer(user) + job_3.update!(retried: true) + end + + let_it_be(:job_4) { create(:ci_build, :success, pipeline: pipeline, name: 'build') } + + subject { described_class.new(current_user: user, pipeline: pipeline, params: params).execute } + + it 'does not return retried jobs by default' do + expect(subject).to match_array([job_4]) + end + + context 'when include_retried is false' do + let(:params) { { include_retried: false } } + + it 'does not return retried jobs' do + expect(subject).to match_array([job_4]) + end + end + + context 'when include_retried is true' do + let(:params) { { include_retried: true } } + + it 'returns retried jobs' do + expect(subject).to match_array([job_3, job_4]) + end + end + end end diff --git a/spec/finders/ci/testing/daily_build_group_report_results_finder_spec.rb b/spec/finders/ci/testing/daily_build_group_report_results_finder_spec.rb new file mode 100644 index 00000000000..a703f3b800c --- /dev/null +++ b/spec/finders/ci/testing/daily_build_group_report_results_finder_spec.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ci::Testing::DailyBuildGroupReportResultsFinder do + describe '#execute' do + let_it_be(:project) { create(:project, :private) } + let(:user_without_permission) { create(:user) } + let_it_be(:user_with_permission) { project.owner } + let_it_be(:ref_path) { 'refs/heads/master' } + let(:limit) { nil } + let_it_be(:default_branch) { false } + let(:start_date) { '2020-03-09' } + let(:end_date) { '2020-03-10' } + let(:sort) { true } + + let_it_be(:rspec_coverage_1) { create_daily_coverage('rspec', 79.0, '2020-03-09') } + let_it_be(:karma_coverage_1) { create_daily_coverage('karma', 89.0, '2020-03-09') } + let_it_be(:rspec_coverage_2) { create_daily_coverage('rspec', 95.0, '2020-03-10') } + let_it_be(:karma_coverage_2) { create_daily_coverage('karma', 92.0, '2020-03-10') } + let_it_be(:rspec_coverage_3) { create_daily_coverage('rspec', 97.0, '2020-03-11') } + let_it_be(:karma_coverage_3) { create_daily_coverage('karma', 99.0, '2020-03-11') } + + let(:finder) { described_class.new(params: params, current_user: current_user) } + + let(:params) do + { + project: project, + coverage: true, + ref_path: ref_path, + start_date: start_date, + end_date: end_date, + limit: limit, + sort: sort + } + end + + subject(:coverages) { finder.execute } + + context 'when params are provided' do + context 'when current user is not allowed to read data' do + let(:current_user) { user_without_permission } + + it 'returns an empty collection' do + expect(coverages).to be_empty + end + end + + context 'when current user is allowed to read data' do + let(:current_user) { user_with_permission } + + it 'returns matching coverages within the given date range' do + expect(coverages).to match_array([ + karma_coverage_2, + rspec_coverage_2, + karma_coverage_1, + rspec_coverage_1 + ]) + end + + context 'when ref_path is nil' do + let(:default_branch) { true } + let(:ref_path) { nil } + + it 'returns coverages for the default branch' do + rspec_coverage_4 = create_daily_coverage('rspec', 66.0, '2020-03-10') + + expect(coverages).to contain_exactly(rspec_coverage_4) + end + end + + context 'when limit is specified' do + let(:limit) { 2 } + + it 'returns limited number of matching coverages within the given date range' do + expect(coverages).to match_array([ + karma_coverage_2, + rspec_coverage_2 + ]) + end + end + end + end + end + + private + + def create_daily_coverage(group_name, coverage, date) + create( + :ci_daily_build_group_report_result, + project: project, + ref_path: ref_path || 'feature-branch', + group_name: group_name, + data: { 'coverage' => coverage }, + date: date, + default_branch: default_branch + ) + end +end diff --git a/spec/finders/container_repositories_finder_spec.rb b/spec/finders/container_repositories_finder_spec.rb index b6305e3f5b7..983f6dba28b 100644 --- a/spec/finders/container_repositories_finder_spec.rb +++ b/spec/finders/container_repositories_finder_spec.rb @@ -32,6 +32,34 @@ RSpec.describe ContainerRepositoriesFinder do end end + shared_examples 'with sorting' do + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, group: group) } + let_it_be(:sort_repository) do + create(:container_repository, name: 'bar', project: project, created_at: 1.day.ago) + end + + let_it_be(:sort_repository2) do + create(:container_repository, name: 'foo', project: project, created_at: 1.hour.ago, updated_at: 1.hour.ago) + end + + [:created_desc, :updated_asc, :name_desc].each do |order| + context "with sort set to #{order}" do + let(:params) { { sort: order } } + + it { is_expected.to eq([sort_repository2, sort_repository])} + end + end + + [:created_asc, :updated_desc, :name_asc].each do |order| + context "with sort set to #{order}" do + let(:params) { { sort: order } } + + it { is_expected.to eq([sort_repository, sort_repository2])} + end + end + end + describe '#execute' do context 'with authorized user' do subject { described_class.new(user: reporter, subject: subject_object, params: params).execute } @@ -47,6 +75,7 @@ RSpec.describe ContainerRepositoriesFinder do it { is_expected.to match_array([project_repository, other_repository]) } it_behaves_like 'with name search' + it_behaves_like 'with sorting' end context 'when subject_type is project' do @@ -55,6 +84,7 @@ RSpec.describe ContainerRepositoriesFinder do it { is_expected.to match_array([project_repository]) } it_behaves_like 'with name search' + it_behaves_like 'with sorting' end context 'with invalid subject_type' do diff --git a/spec/finders/deployments_finder_spec.rb b/spec/finders/deployments_finder_spec.rb index e4e0f366eeb..0f659fa1dab 100644 --- a/spec/finders/deployments_finder_spec.rb +++ b/spec/finders/deployments_finder_spec.rb @@ -3,130 +3,161 @@ require 'spec_helper' RSpec.describe DeploymentsFinder do - subject { described_class.new(project, params).execute } - - let(:project) { create(:project, :public, :test_repo) } - let(:params) { {} } + subject { described_class.new(params).execute } describe "#execute" do - it 'returns all deployments by default' do - deployments = create_list(:deployment, 2, :success, project: project) - is_expected.to match_array(deployments) + context 'when project or group is missing' do + let(:params) { {} } + + it 'returns nothing' do + is_expected.to eq([]) + end end - describe 'filtering' do - context 'when updated_at filters are specified' do - let(:params) { { updated_before: 1.day.ago, updated_after: 3.days.ago } } - let!(:deployment_1) { create(:deployment, :success, project: project, updated_at: 2.days.ago) } - let!(:deployment_2) { create(:deployment, :success, project: project, updated_at: 4.days.ago) } - let!(:deployment_3) { create(:deployment, :success, project: project, updated_at: 1.hour.ago) } + context 'at project scope' do + let_it_be(:project) { create(:project, :public, :test_repo) } + let(:base_params) { { project: project } } + + describe 'filtering' do + context 'when updated_at filters are specified' do + let(:params) { { **base_params, updated_before: 1.day.ago, updated_after: 3.days.ago } } + let!(:deployment_1) { create(:deployment, :success, project: project, updated_at: 2.days.ago) } + let!(:deployment_2) { create(:deployment, :success, project: project, updated_at: 4.days.ago) } + let!(:deployment_3) { create(:deployment, :success, project: project, updated_at: 1.hour.ago) } - it 'returns deployments with matched updated_at' do - is_expected.to match_array([deployment_1]) + it 'returns deployments with matched updated_at' do + is_expected.to match_array([deployment_1]) + end end - end - context 'when the environment name is specified' do - let!(:environment1) { create(:environment, project: project) } - let!(:environment2) { create(:environment, project: project) } - let!(:deployment1) do - create(:deployment, project: project, environment: environment1) + context 'when the environment name is specified' do + let!(:environment1) { create(:environment, project: project) } + let!(:environment2) { create(:environment, project: project) } + let!(:deployment1) do + create(:deployment, project: project, environment: environment1) + end + + let!(:deployment2) do + create(:deployment, project: project, environment: environment2) + end + + let(:params) { { **base_params, environment: environment1.name } } + + it 'returns deployments for the given environment' do + is_expected.to match_array([deployment1]) + end end - let!(:deployment2) do - create(:deployment, project: project, environment: environment2) + context 'when the deployment status is specified' do + let!(:deployment1) { create(:deployment, :success, project: project) } + let!(:deployment2) { create(:deployment, :failed, project: project) } + let(:params) { { **base_params, status: 'success' } } + + it 'returns deployments for the given environment' do + is_expected.to match_array([deployment1]) + end end - let(:params) { { environment: environment1.name } } + context 'when using an invalid deployment status' do + let(:params) { { **base_params, status: 'kittens' } } - it 'returns deployments for the given environment' do - is_expected.to match_array([deployment1]) + it 'raises ArgumentError' do + expect { subject }.to raise_error(ArgumentError) + end end end - context 'when the deployment status is specified' do - let!(:deployment1) { create(:deployment, :success, project: project) } - let!(:deployment2) { create(:deployment, :failed, project: project) } - let(:params) { { status: 'success' } } + describe 'ordering' do + using RSpec::Parameterized::TableSyntax + + let(:params) { { **base_params, order_by: order_by, sort: sort } } + + let!(:deployment_1) { create(:deployment, :success, project: project, iid: 11, ref: 'master', created_at: 2.days.ago, updated_at: Time.now, finished_at: Time.now) } + let!(:deployment_2) { create(:deployment, :success, project: project, iid: 12, ref: 'feature', created_at: 1.day.ago, updated_at: 2.hours.ago, finished_at: 2.hours.ago) } + let!(:deployment_3) { create(:deployment, :success, project: project, iid: 8, ref: 'video', created_at: Time.now, updated_at: 1.hour.ago, finished_at: 1.hour.ago) } + + where(:order_by, :sort, :ordered_deployments) do + 'created_at' | 'asc' | [:deployment_1, :deployment_2, :deployment_3] + 'created_at' | 'desc' | [:deployment_3, :deployment_2, :deployment_1] + 'id' | 'asc' | [:deployment_1, :deployment_2, :deployment_3] + 'id' | 'desc' | [:deployment_3, :deployment_2, :deployment_1] + 'iid' | 'asc' | [:deployment_3, :deployment_1, :deployment_2] + 'iid' | 'desc' | [:deployment_2, :deployment_1, :deployment_3] + 'ref' | 'asc' | [:deployment_2, :deployment_1, :deployment_3] + 'ref' | 'desc' | [:deployment_3, :deployment_1, :deployment_2] + 'updated_at' | 'asc' | [:deployment_2, :deployment_3, :deployment_1] + 'updated_at' | 'desc' | [:deployment_1, :deployment_3, :deployment_2] + 'finished_at' | 'asc' | [:deployment_2, :deployment_3, :deployment_1] + 'finished_at' | 'desc' | [:deployment_1, :deployment_3, :deployment_2] + 'invalid' | 'asc' | [:deployment_1, :deployment_2, :deployment_3] + 'iid' | 'err' | [:deployment_3, :deployment_1, :deployment_2] + end - it 'returns deployments for the given environment' do - is_expected.to match_array([deployment1]) + with_them do + it 'returns the deployments ordered' do + expect(subject).to eq(ordered_deployments.map { |name| public_send(name) }) + end end end - context 'when using an invalid deployment status' do - let(:params) { { status: 'kittens' } } + describe 'transform `created_at` sorting to `id` sorting' do + let(:params) { { **base_params, order_by: 'created_at', sort: 'asc' } } - it 'raises ArgumentError' do - expect { subject }.to raise_error(ArgumentError) + it 'sorts by only one column' do + expect(subject.order_values.size).to eq(1) end - end - end - describe 'ordering' do - using RSpec::Parameterized::TableSyntax - - let(:params) { { order_by: order_by, sort: sort } } - - let!(:deployment_1) { create(:deployment, :success, project: project, iid: 11, ref: 'master', created_at: 2.days.ago, updated_at: Time.now) } - let!(:deployment_2) { create(:deployment, :success, project: project, iid: 12, ref: 'feature', created_at: 1.day.ago, updated_at: 2.hours.ago) } - let!(:deployment_3) { create(:deployment, :success, project: project, iid: 8, ref: 'video', created_at: Time.now, updated_at: 1.hour.ago) } - - where(:order_by, :sort, :ordered_deployments) do - 'created_at' | 'asc' | [:deployment_1, :deployment_2, :deployment_3] - 'created_at' | 'desc' | [:deployment_3, :deployment_2, :deployment_1] - 'id' | 'asc' | [:deployment_1, :deployment_2, :deployment_3] - 'id' | 'desc' | [:deployment_3, :deployment_2, :deployment_1] - 'iid' | 'asc' | [:deployment_3, :deployment_1, :deployment_2] - 'iid' | 'desc' | [:deployment_2, :deployment_1, :deployment_3] - 'ref' | 'asc' | [:deployment_2, :deployment_1, :deployment_3] - 'ref' | 'desc' | [:deployment_3, :deployment_1, :deployment_2] - 'updated_at' | 'asc' | [:deployment_2, :deployment_3, :deployment_1] - 'updated_at' | 'desc' | [:deployment_1, :deployment_3, :deployment_2] - 'invalid' | 'asc' | [:deployment_1, :deployment_2, :deployment_3] - 'iid' | 'err' | [:deployment_3, :deployment_1, :deployment_2] + it 'sorts by `id`' do + expect(subject.order_values.first.to_sql).to eq(Deployment.arel_table[:id].asc.to_sql) + end end - with_them do - it 'returns the deployments ordered' do - expect(subject).to eq(ordered_deployments.map { |name| public_send(name) }) + describe 'tie-breaker for `finished_at` sorting' do + let(:params) { { **base_params, order_by: 'updated_at', sort: 'asc' } } + + it 'sorts by two columns' do + expect(subject.order_values.size).to eq(2) end - end - end - describe 'transform `created_at` sorting to `id` sorting' do - let(:params) { { order_by: 'created_at', sort: 'asc' } } + it 'adds `id` sorting as the second order column' do + order_value = subject.order_values[1] - it 'sorts by only one column' do - expect(subject.order_values.size).to eq(1) - end + expect(order_value.to_sql).to eq(Deployment.arel_table[:id].desc.to_sql) + end - it 'sorts by `id`' do - expect(subject.order_values.first.to_sql).to eq(Deployment.arel_table[:id].asc.to_sql) - end - end + it 'uses the `id DESC` as tie-breaker when ordering' do + updated_at = Time.now - describe 'tie-breaker for `updated_at` sorting' do - let(:params) { { order_by: 'updated_at', sort: 'asc' } } + deployment_1 = create(:deployment, :success, project: project, updated_at: updated_at) + deployment_2 = create(:deployment, :success, project: project, updated_at: updated_at) + deployment_3 = create(:deployment, :success, project: project, updated_at: updated_at) - it 'sorts by two columns' do - expect(subject.order_values.size).to eq(2) + expect(subject).to eq([deployment_3, deployment_2, deployment_1]) + end end - it 'adds `id` sorting as the second order column' do - order_value = subject.order_values[1] + context 'when filtering by finished time' do + let!(:deployment_1) { create(:deployment, :success, project: project, finished_at: 2.days.ago) } + let!(:deployment_2) { create(:deployment, :success, project: project, finished_at: 4.days.ago) } + let!(:deployment_3) { create(:deployment, :success, project: project, finished_at: 5.hours.ago) } - expect(order_value.to_sql).to eq(Deployment.arel_table[:id].desc.to_sql) - end + context 'when filtering by finished_after and finished_before' do + let(:params) { { **base_params, finished_after: 3.days.ago, finished_before: 1.day.ago } } + + it { is_expected.to match_array([deployment_1]) } + end - it 'uses the `id DESC` as tie-breaker when ordering' do - updated_at = Time.now + context 'when the finished_before parameter is missing' do + let(:params) { { **base_params, finished_after: 3.days.ago } } - deployment_1 = create(:deployment, :success, project: project, updated_at: updated_at) - deployment_2 = create(:deployment, :success, project: project, updated_at: updated_at) - deployment_3 = create(:deployment, :success, project: project, updated_at: updated_at) + it { is_expected.to match_array([deployment_1, deployment_3]) } + end + + context 'when finished_after is missing' do + let(:params) { { **base_params, finished_before: 3.days.ago } } - expect(subject).to eq([deployment_3, deployment_2, deployment_1]) + it { is_expected.to match_array([deployment_2]) } + end end end end diff --git a/spec/finders/license_template_finder_spec.rb b/spec/finders/license_template_finder_spec.rb index 93f13632b6f..754b92faccc 100644 --- a/spec/finders/license_template_finder_spec.rb +++ b/spec/finders/license_template_finder_spec.rb @@ -3,12 +3,7 @@ require 'spec_helper' RSpec.describe LicenseTemplateFinder do - describe '#execute' do - subject(:result) { described_class.new(nil, params).execute } - - let(:categories) { categorised_licenses.keys } - let(:categorised_licenses) { result.group_by(&:category) } - + RSpec.shared_examples 'filters by popular category' do context 'popular: true' do let(:params) { { popular: true } } @@ -26,6 +21,15 @@ RSpec.describe LicenseTemplateFinder do expect(categorised_licenses[:Other]).to be_present end end + end + + describe '#execute' do + subject(:result) { described_class.new(nil, params).execute } + + let(:categories) { categorised_licenses.keys } + let(:categorised_licenses) { result.group_by(&:category) } + + it_behaves_like 'filters by popular category' context 'popular: nil' do let(:params) { { popular: nil } } @@ -48,4 +52,31 @@ RSpec.describe LicenseTemplateFinder do end end end + + describe '#template_names' do + let(:params) { {} } + + subject(:template_names) { described_class.new(nil, params).template_names } + + let(:categories) { categorised_licenses.keys } + let(:categorised_licenses) { template_names } + + it_behaves_like 'filters by popular category' + + context 'popular: nil' do + let(:params) { { popular: nil } } + + it 'returns all licenses known by the Licensee gem' do + from_licensee = Licensee::License.all.map { |l| l.key } + + expect(template_names.values.flatten.map { |x| x[:key] }).to match_array(from_licensee) + end + end + + context 'template names hash keys' do + it 'has all the expected keys' do + expect(template_names.values.flatten.first.keys).to match_array(%i(id key name project_id)) + end + end + end end diff --git a/spec/finders/merge_request/metrics_finder_spec.rb b/spec/finders/merge_request/metrics_finder_spec.rb new file mode 100644 index 00000000000..ea039462e66 --- /dev/null +++ b/spec/finders/merge_request/metrics_finder_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe MergeRequest::MetricsFinder do + let_it_be(:current_user) { create(:user) } + let_it_be(:project) { create(:project, :repository) } + let_it_be(:merge_request_not_merged) { create(:merge_request, :unique_branches, source_project: project) } + let_it_be(:merged_at) { Time.new(2020, 5, 1) } + let_it_be(:merge_request_merged) do + create(:merge_request, :unique_branches, :merged, source_project: project).tap do |mr| + mr.metrics.update!(merged_at: merged_at) + end + end + + let(:params) do + { + target_project: project, + merged_after: merged_at - 10.days, + merged_before: merged_at + 10.days + } + end + + subject { described_class.new(current_user, params).execute.to_a } + + context 'when target project is missing' do + before do + params.delete(:target_project) + end + + it { is_expected.to be_empty } + end + + context 'when the user is not part of the project' do + it { is_expected.to be_empty } + end + + context 'when user is part of the project' do + before do + project.add_developer(current_user) + end + + it 'returns merge request records' do + is_expected.to eq([merge_request_merged.metrics]) + end + + it 'excludes not merged records' do + is_expected.not_to eq([merge_request_not_merged.metrics]) + end + + context 'when only merged_before is given' do + before do + params.delete(:merged_after) + end + + it { is_expected.to eq([merge_request_merged.metrics]) } + end + + context 'when only merged_after is given' do + before do + params.delete(:merged_before) + end + + it { is_expected.to eq([merge_request_merged.metrics]) } + end + + context 'when no records matching the date range' do + before do + params[:merged_before] = merged_at - 1.year + params[:merged_after] = merged_at - 2.years + end + + it { is_expected.to be_empty } + end + end +end diff --git a/spec/finders/merge_requests/oldest_per_commit_finder_spec.rb b/spec/finders/merge_requests/oldest_per_commit_finder_spec.rb new file mode 100644 index 00000000000..4e9d021fa5d --- /dev/null +++ b/spec/finders/merge_requests/oldest_per_commit_finder_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe MergeRequests::OldestPerCommitFinder do + describe '#execute' do + it 'returns a Hash mapping commit SHAs to their oldest merge requests' do + project = create(:project) + mr1 = create(:merge_request, :merged, target_project: project) + mr2 = create(:merge_request, :merged, target_project: project) + mr1_diff = create(:merge_request_diff, merge_request: mr1) + mr2_diff = create(:merge_request_diff, merge_request: mr2) + sha1 = Digest::SHA1.hexdigest('foo') + sha2 = Digest::SHA1.hexdigest('bar') + + create(:merge_request_diff_commit, merge_request_diff: mr1_diff, sha: sha1) + create(:merge_request_diff_commit, merge_request_diff: mr2_diff, sha: sha1) + create( + :merge_request_diff_commit, + merge_request_diff: mr2_diff, + sha: sha2, + relative_order: 1 + ) + + commits = [double(:commit, id: sha1), double(:commit, id: sha2)] + + expect(described_class.new(project).execute(commits)).to eq( + sha1 => mr1, + sha2 => mr2 + ) + end + + it 'skips merge requests that are not merged' do + mr = create(:merge_request) + mr_diff = create(:merge_request_diff, merge_request: mr) + sha = Digest::SHA1.hexdigest('foo') + + create(:merge_request_diff_commit, merge_request_diff: mr_diff, sha: sha) + + commits = [double(:commit, id: sha)] + + expect(described_class.new(mr.target_project).execute(commits)) + .to be_empty + end + end +end diff --git a/spec/finders/packages/group_packages_finder_spec.rb b/spec/finders/packages/group_packages_finder_spec.rb index 8dd53b9c3f9..445482a5a96 100644 --- a/spec/finders/packages/group_packages_finder_spec.rb +++ b/spec/finders/packages/group_packages_finder_spec.rb @@ -147,6 +147,7 @@ RSpec.describe Packages::GroupPackagesFinder do end it_behaves_like 'concerning versionless param' + it_behaves_like 'concerning package statuses' end context 'group has package of all types' do diff --git a/spec/finders/packages/packages_finder_spec.rb b/spec/finders/packages/packages_finder_spec.rb index 77a171db144..6e92616bafa 100644 --- a/spec/finders/packages/packages_finder_spec.rb +++ b/spec/finders/packages/packages_finder_spec.rb @@ -82,5 +82,6 @@ RSpec.describe ::Packages::PackagesFinder do end it_behaves_like 'concerning versionless param' + it_behaves_like 'concerning package statuses' end end diff --git a/spec/finders/repositories/commits_with_trailer_finder_spec.rb b/spec/finders/repositories/commits_with_trailer_finder_spec.rb new file mode 100644 index 00000000000..0c457aae340 --- /dev/null +++ b/spec/finders/repositories/commits_with_trailer_finder_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Repositories::CommitsWithTrailerFinder do + let(:project) { create(:project, :repository) } + + describe '#each_page' do + it 'only yields commits with the given trailer' do + finder = described_class.new( + project: project, + from: '570e7b2abdd848b95f2f578043fc23bd6f6fd24d', + to: 'c7fbe50c7c7419d9701eebe64b1fdacc3df5b9dd' + ) + + commits = finder.each_page('Signed-off-by').to_a.flatten + + expect(commits.length).to eq(1) + expect(commits.first.id).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') + expect(commits.first.trailers).to eq( + 'Signed-off-by' => 'Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>' + ) + end + + it 'supports paginating of commits' do + finder = described_class.new( + project: project, + from: 'c1acaa58bbcbc3eafe538cb8274ba387047b69f8', + to: '5937ac0a7beb003549fc5fd26fc247adbce4a52e', + per_page: 1 + ) + + commits = finder.each_page('Signed-off-by') + + expect(commits.count).to eq(4) + end + end +end diff --git a/spec/finders/repositories/previous_tag_finder_spec.rb b/spec/finders/repositories/previous_tag_finder_spec.rb new file mode 100644 index 00000000000..7cc33d11baf --- /dev/null +++ b/spec/finders/repositories/previous_tag_finder_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Repositories::PreviousTagFinder do + let(:project) { build_stubbed(:project) } + let(:finder) { described_class.new(project) } + + describe '#execute' do + context 'when there is a previous tag' do + it 'returns the previous tag' do + tag1 = double(:tag1, name: 'v1.0.0') + tag2 = double(:tag2, name: 'v1.1.0') + tag3 = double(:tag3, name: 'v2.0.0') + tag4 = double(:tag4, name: '1.0.0') + + allow(project.repository) + .to receive(:tags) + .and_return([tag1, tag3, tag2, tag4]) + + expect(finder.execute('2.1.0')).to eq(tag3) + expect(finder.execute('2.0.0')).to eq(tag2) + expect(finder.execute('1.5.0')).to eq(tag2) + expect(finder.execute('1.0.1')).to eq(tag1) + end + end + + context 'when there is no previous tag' do + it 'returns nil' do + tag1 = double(:tag1, name: 'v1.0.0') + tag2 = double(:tag2, name: 'v1.1.0') + + allow(project.repository) + .to receive(:tags) + .and_return([tag1, tag2]) + + expect(finder.execute('1.0.0')).to be_nil + end + end + end +end diff --git a/spec/finders/template_finder_spec.rb b/spec/finders/template_finder_spec.rb index 2da864b9a46..164975fdfb6 100644 --- a/spec/finders/template_finder_spec.rb +++ b/spec/finders/template_finder_spec.rb @@ -5,6 +5,102 @@ require 'spec_helper' RSpec.describe TemplateFinder do using RSpec::Parameterized::TableSyntax + let_it_be(:template_files) do + { + "Dockerfile/project_dockerfiles_template.dockerfile" => "project_dockerfiles_template content", + "gitignore/project_gitignores_template.gitignore" => "project_gitignores_template content", + "gitlab-ci/project_gitlab_ci_ymls_template.yml" => "project_gitlab_ci_ymls_template content", + ".gitlab/issue_templates/project_issues_template.md" => "project_issues_template content", + ".gitlab/merge_request_templates/project_merge_requests_template.md" => "project_merge_requests_template content" + } + end + + RSpec.shared_examples 'fetches predefined vendor templates' do + where(:type, :vendored_name) do + :dockerfiles | 'Binary' + :gitignores | 'Actionscript' + :gitlab_ci_ymls | 'Android' + :metrics_dashboard_ymls | 'Default' + :gitlab_ci_syntax_ymls | 'Artifacts example' + end + + with_them do + it 'returns all vendored templates when no name is specified' do + expect(result).to include(have_attributes(name: vendored_name)) + end + + context 'with name param' do + let(:params) { { name: vendored_name } } + + it 'returns only the specified vendored template when a name is specified' do + expect(result).to have_attributes(name: vendored_name) + end + + context 'with mistaken name param' do + let(:params) { { name: 'unknown' } } + + it 'returns nil when an unknown name is specified' do + expect(result).to be_nil + end + end + end + end + end + + RSpec.shared_examples 'no issues and merge requests templates available' do + context 'with issue and merge request templates' do + where(:type, :vendored_name) do + :issues | nil + :merge_requests | nil + end + + with_them do + context 'when fetching all templates' do + it 'returns empty array' do + expect(result).to eq([]) + end + end + + context 'when looking for specific template by name' do + let(:params) { { name: 'anything' } } + + it 'raises an error' do + expect { result }.to raise_exception(Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError) + end + end + end + end + end + + RSpec.shared_examples 'fetches issues and merge requests templates' do + where(:type, :template_name) do + :issues | 'project_issues_template' + :merge_requests | 'project_merge_requests_template' + end + + with_them do + it 'returns all repository template files for issues and merge requests' do + expect(result).to include(have_attributes(name: template_name)) + end + + context 'with name param' do + let(:params) { { name: template_name } } + + it 'returns only the specified vendored template when a name is specified' do + expect(result).to have_attributes(name: template_name) + end + + context 'with mistaken name param' do + let(:params) { { name: 'unknown' } } + + it 'raises an error when an unknown name is specified' do + expect { result }.to raise_exception(Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError) + end + end + end + end + end + describe '#build' do let(:project) { build_stubbed(:project) } @@ -15,6 +111,8 @@ RSpec.describe TemplateFinder do :licenses | ::LicenseTemplateFinder :metrics_dashboard_ymls | described_class :gitlab_ci_syntax_ymls | described_class + :issues | described_class + :merge_requests | described_class end with_them do @@ -26,6 +124,37 @@ RSpec.describe TemplateFinder do end describe '#execute' do + let_it_be(:project) { nil } + let(:params) { {} } + + subject(:result) { described_class.new(type, project, params).execute } + + context 'when no project is passed in' do + it_behaves_like 'fetches predefined vendor templates' + it_behaves_like 'no issues and merge requests templates available' + end + + context 'when project has no repository' do + let_it_be(:project) { create(:project) } + + it_behaves_like 'fetches predefined vendor templates' + it_behaves_like 'no issues and merge requests templates available' + end + + context 'when project has a repository' do + let_it_be(:project) { create(:project, :custom_repo, files: template_files) } + + it_behaves_like 'fetches predefined vendor templates' + it_behaves_like 'fetches issues and merge requests templates' + end + end + + describe '#template_names' do + let_it_be(:project) { nil } + let(:params) { {} } + + subject(:result) { described_class.new(type, project, params).template_names.values.flatten.map { |el| OpenStruct.new(el) } } + where(:type, :vendored_name) do :dockerfiles | 'Binary' :gitignores | 'Actionscript' @@ -35,22 +164,67 @@ RSpec.describe TemplateFinder do end with_them do - it 'returns all vendored templates when no name is specified' do - result = described_class.new(type, nil).execute + context 'when no project is passed in' do + it 'returns all vendored templates when no name is specified' do + expect(result).to include(have_attributes(name: vendored_name)) + end + end - expect(result).to include(have_attributes(name: vendored_name)) + context 'when project has no repository' do + let_it_be(:project) { create(:project) } + + it 'returns all vendored templates when no name is specified' do + expect(result).to include(have_attributes(name: vendored_name)) + end end - it 'returns only the specified vendored template when a name is specified' do - result = described_class.new(type, nil, name: vendored_name).execute + context 'when project has a repository' do + let_it_be(:project) { create(:project, :custom_repo, files: template_files) } - expect(result).to have_attributes(name: vendored_name) + it 'returns all vendored templates when no name is specified' do + expect(result).to include(have_attributes(name: vendored_name)) + end end - it 'returns nil when an unknown name is specified' do - result = described_class.new(type, nil, name: 'unknown').execute + context 'template names hash keys' do + it 'has all the expected keys' do + expect(result.first.to_h.keys).to match_array(%i(id key name project_id)) + end + end + end + + where(:type, :template_name) do + :issues | 'project_issues_template' + :merge_requests | 'project_merge_requests_template' + end + + with_them do + context 'when no project is passed in' do + it 'returns all vendored templates when no name is specified' do + expect(result).to eq([]) + end + end + + context 'when project has no repository' do + let_it_be(:project) { create(:project) } + + it 'returns all vendored templates when no name is specified' do + expect(result).to eq([]) + end + end + + context 'when project has a repository' do + let_it_be(:project) { create(:project, :custom_repo, files: template_files) } + + it 'returns all vendored templates when no name is specified' do + expect(result).to include(have_attributes(name: template_name)) + end - expect(result).to be_nil + context 'template names hash keys' do + it 'has all the expected keys' do + expect(result.first.to_h.keys).to match_array(%i(id key name project_id)) + end + end end end end diff --git a/spec/finders/terraform/states_finder_spec.rb b/spec/finders/terraform/states_finder_spec.rb new file mode 100644 index 00000000000..260e5f4818f --- /dev/null +++ b/spec/finders/terraform/states_finder_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Terraform::StatesFinder do + describe '#execute' do + let_it_be(:project) { create(:project) } + let_it_be(:state_1) { create(:terraform_state, project: project) } + let_it_be(:state_2) { create(:terraform_state, project: project) } + + let(:user) { project.creator } + + subject { described_class.new(project, user).execute } + + it { is_expected.to contain_exactly(state_1, state_2) } + + context 'user does not have permission' do + let(:user) { create(:user) } + + before do + project.add_guest(user) + end + + it { is_expected.to be_empty } + end + + context 'filtering by name' do + let(:params) { { name: name_param } } + + subject { described_class.new(project, user, params: params).execute } + + context 'name does not match' do + let(:name_param) { 'other-name' } + + it { is_expected.to be_empty } + end + + context 'name does match' do + let(:name_param) { state_1.name } + + it { is_expected.to contain_exactly(state_1) } + end + end + end +end diff --git a/spec/finders/user_recent_events_finder_spec.rb b/spec/finders/user_recent_events_finder_spec.rb index ddba9b595a4..5a9243d150d 100644 --- a/spec/finders/user_recent_events_finder_spec.rb +++ b/spec/finders/user_recent_events_finder_spec.rb @@ -5,16 +5,17 @@ require 'spec_helper' RSpec.describe UserRecentEventsFinder do let_it_be(:project_owner, reload: true) { create(:user) } let_it_be(:current_user, reload: true) { create(:user) } - let(:private_project) { create(:project, :private, creator: project_owner) } - let(:internal_project) { create(:project, :internal, creator: project_owner) } - let(:public_project) { create(:project, :public, creator: project_owner) } + let_it_be(:private_project) { create(:project, :private, creator: project_owner) } + let_it_be(:internal_project) { create(:project, :internal, creator: project_owner) } + let_it_be(:public_project) { create(:project, :public, creator: project_owner) } let!(:private_event) { create(:event, project: private_project, author: project_owner) } let!(:internal_event) { create(:event, project: internal_project, author: project_owner) } let!(:public_event) { create(:event, project: public_project, author: project_owner) } + let_it_be(:issue) { create(:issue, project: public_project) } let(:limit) { nil } let(:params) { { limit: limit } } - subject(:finder) { described_class.new(current_user, project_owner, params) } + subject(:finder) { described_class.new(current_user, project_owner, nil, params) } describe '#execute' do context 'when profile is public' do @@ -39,11 +40,116 @@ RSpec.describe UserRecentEventsFinder do expect(finder.execute).to be_empty end - describe 'design activity events' do - let_it_be(:event_a) { create(:design_event, author: project_owner) } - let_it_be(:event_b) { create(:design_event, author: project_owner) } + context 'events from multiple users' do + let_it_be(:second_user, reload: true) { create(:user) } + let_it_be(:private_project_second_user) { create(:project, :private, creator: second_user) } + let(:internal_project_second_user) { create(:project, :internal, creator: second_user) } + let(:public_project_second_user) { create(:project, :public, creator: second_user) } + let!(:private_event_second_user) { create(:event, project: private_project_second_user, author: second_user) } + let!(:internal_event_second_user) { create(:event, project: internal_project_second_user, author: second_user) } + let!(:public_event_second_user) { create(:event, project: public_project_second_user, author: second_user) } + + it 'includes events from all users', :aggregate_failures do + events = described_class.new(current_user, [project_owner, second_user], nil, params).execute + + expect(events).to include(private_event, internal_event, public_event) + expect(events).to include(private_event_second_user, internal_event_second_user, public_event_second_user) + expect(events.size).to eq(6) + end + + it 'does not include events from users with private profile', :aggregate_failures do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).with(current_user, :read_user_profile, second_user).and_return(false) + + events = described_class.new(current_user, [project_owner, second_user], nil, params).execute + + expect(events).to include(private_event, internal_event, public_event) + expect(events.size).to eq(3) + end + end + + context 'filter activity events' do + let!(:push_event) { create(:push_event, project: public_project, author: project_owner) } + let!(:merge_event) { create(:event, :merged, project: public_project, author: project_owner) } + let!(:issue_event) { create(:event, :closed, project: public_project, target: issue, author: project_owner) } + let!(:comment_event) { create(:event, :commented, project: public_project, author: project_owner) } + let!(:wiki_event) { create(:wiki_page_event, project: public_project, author: project_owner) } + let!(:design_event) { create(:design_event, project: public_project, author: project_owner) } + let!(:team_event) { create(:event, :joined, project: public_project, author: project_owner) } + + it 'includes all events', :aggregate_failures do + event_filter = EventFilter.new(EventFilter::ALL) + events = described_class.new(current_user, project_owner, event_filter, params).execute + + expect(events).to include(private_event, internal_event, public_event) + expect(events).to include(push_event, merge_event, issue_event, comment_event, wiki_event, design_event, team_event) + expect(events.size).to eq(10) + end + + it 'only includes push events', :aggregate_failures do + event_filter = EventFilter.new(EventFilter::PUSH) + events = described_class.new(current_user, project_owner, event_filter, params).execute + + expect(events).to include(push_event) + expect(events.size).to eq(1) + end + + it 'only includes merge events', :aggregate_failures do + event_filter = EventFilter.new(EventFilter::MERGED) + events = described_class.new(current_user, project_owner, event_filter, params).execute + + expect(events).to include(merge_event) + expect(events.size).to eq(1) + end + + it 'only includes issue events', :aggregate_failures do + event_filter = EventFilter.new(EventFilter::ISSUE) + events = described_class.new(current_user, project_owner, event_filter, params).execute + + expect(events).to include(issue_event) + expect(events.size).to eq(1) + end + + it 'only includes comments events', :aggregate_failures do + event_filter = EventFilter.new(EventFilter::COMMENTS) + events = described_class.new(current_user, project_owner, event_filter, params).execute + + expect(events).to include(comment_event) + expect(events.size).to eq(1) + end + + it 'only includes wiki events', :aggregate_failures do + event_filter = EventFilter.new(EventFilter::WIKI) + events = described_class.new(current_user, project_owner, event_filter, params).execute + + expect(events).to include(wiki_event) + expect(events.size).to eq(1) + end it 'only includes design events', :aggregate_failures do + event_filter = EventFilter.new(EventFilter::DESIGNS) + events = described_class.new(current_user, project_owner, event_filter, params).execute + + expect(events).to include(design_event) + expect(events.size).to eq(1) + end + + it 'only includes team events', :aggregate_failures do + event_filter = EventFilter.new(EventFilter::TEAM) + events = described_class.new(current_user, project_owner, event_filter, params).execute + + expect(events).to include(private_event, internal_event, public_event, team_event) + expect(events.size).to eq(4) + end + end + + describe 'issue activity events' do + let(:issue) { create(:issue, project: public_project) } + let(:note) { create(:note_on_issue, noteable: issue, project: public_project) } + let!(:event_a) { create(:event, :commented, target: note, author: project_owner) } + let!(:event_b) { create(:event, :closed, target: issue, author: project_owner) } + + it 'includes all issue related events', :aggregate_failures do events = finder.execute expect(events).to include(event_a) |