diff options
Diffstat (limited to 'spec')
25 files changed, 499 insertions, 160 deletions
diff --git a/spec/controllers/every_controller_spec.rb b/spec/controllers/every_controller_spec.rb index d333c98ccf7..7a7b17ae1f0 100644 --- a/spec/controllers/every_controller_spec.rb +++ b/spec/controllers/every_controller_spec.rb @@ -24,19 +24,19 @@ RSpec.describe "Every controller" do let_it_be(:routes_without_category) do controller_actions.map do |controller, action| next if controller.feature_category_for_action(action) - next unless controller.to_s.start_with?('B', 'C', 'D', 'E', 'F', 'Projects::MergeRequestsController') + + next unless controller.to_s.start_with?('B', 'C', 'D', 'E', 'F', + 'H', 'I', 'J', 'K', 'L', + 'M', 'N', 'O', 'Q', 'R', + 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', + 'Projects::MergeRequestsController') "#{controller}##{action}" end.compact end it "has feature categories" do - routes_without_category.map { |x| x.split('#') }.group_by(&:first).each do |controller, actions| - puts controller - puts actions.map { |x| ":#{x.last}" }.sort.join(', ') - puts '' - end - expect(routes_without_category).to be_empty, "#{routes_without_category} did not have a category" end diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index d7c22d46e90..eff98ab65a6 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -197,16 +197,40 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do context 'with not expiry date' do let(:job) { create(:ci_build, :success, :artifacts, pipeline: pipeline) } - it 'exposes needed information' do - get_show_json + context 'when artifacts are unlocked' do + before do + job.pipeline.unlocked! + end - expect(response).to have_gitlab_http_status(:ok) - expect(response).to match_response_schema('job/job_details') - expect(json_response['artifact']['download_path']).to match(%r{artifacts/download}) - expect(json_response['artifact']['browse_path']).to match(%r{artifacts/browse}) - expect(json_response['artifact']).not_to have_key('keep_path') - expect(json_response['artifact']).not_to have_key('expired') - expect(json_response['artifact']).not_to have_key('expired_at') + it 'exposes needed information' do + get_show_json + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('job/job_details') + expect(json_response['artifact']['download_path']).to match(%r{artifacts/download}) + expect(json_response['artifact']['browse_path']).to match(%r{artifacts/browse}) + expect(json_response['artifact']).not_to have_key('keep_path') + expect(json_response['artifact']).not_to have_key('expired') + expect(json_response['artifact']).not_to have_key('expired_at') + end + end + + context 'when artifacts are locked' do + before do + job.pipeline.artifacts_locked! + end + + it 'exposes needed information' do + get_show_json + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('job/job_details') + expect(json_response['artifact']['download_path']).to match(%r{artifacts/download}) + expect(json_response['artifact']['browse_path']).to match(%r{artifacts/browse}) + expect(json_response['artifact']).not_to have_key('keep_path') + expect(json_response['artifact']).not_to have_key('expired') + expect(json_response['artifact']).not_to have_key('expired_at') + end end end diff --git a/spec/factories/packages.rb b/spec/factories/packages.rb index 3562f57654e..e2c5b000988 100644 --- a/spec/factories/packages.rb +++ b/spec/factories/packages.rb @@ -21,6 +21,10 @@ FactoryBot.define do end end + factory :debian_package do + package_type { :debian } + end + factory :npm_package do sequence(:name) { |n| "@#{project.root_namespace.path}/package-#{n}"} version { '1.0.0' } diff --git a/spec/frontend/design_management/components/__snapshots__/design_note_pin_spec.js.snap b/spec/frontend/design_management/components/__snapshots__/design_note_pin_spec.js.snap index 62a0f675cff..ed8ed3254ba 100644 --- a/spec/frontend/design_management/components/__snapshots__/design_note_pin_spec.js.snap +++ b/spec/frontend/design_management/components/__snapshots__/design_note_pin_spec.js.snap @@ -1,23 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Design note pin component should match the snapshot of note when repositioning 1`] = ` -<button - aria-label="Comment form position" - class="design-pin gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-0 btn-transparent comment-indicator" - style="left: 10px; top: 10px; cursor: move;" - type="button" -> - <gl-icon-stub - name="image-comment-dark" - size="24" - /> -</button> -`; - exports[`Design note pin component should match the snapshot of note with index 1`] = ` <button aria-label="Comment '1' position" - class="design-pin gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-0 js-image-badge badge badge-pill" + class="gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-font-lg gl-outline-0! js-image-badge badge badge-pill" style="left: 10px; top: 10px;" type="button" > @@ -30,7 +16,7 @@ exports[`Design note pin component should match the snapshot of note with index exports[`Design note pin component should match the snapshot of note without index 1`] = ` <button aria-label="Comment form position" - class="design-pin gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-0 btn-transparent comment-indicator" + class="gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-font-lg gl-outline-0! btn-transparent comment-indicator gl-p-0" style="left: 10px; top: 10px;" type="button" > diff --git a/spec/frontend/design_management/components/design_note_pin_spec.js b/spec/frontend/design_management/components/design_note_pin_spec.js index 4e045b58a35..a6219923aca 100644 --- a/spec/frontend/design_management/components/design_note_pin_spec.js +++ b/spec/frontend/design_management/components/design_note_pin_spec.js @@ -29,21 +29,4 @@ describe('Design note pin component', () => { createComponent({ label: 1 }); expect(wrapper.element).toMatchSnapshot(); }); - - it('should match the snapshot of note when repositioning', () => { - createComponent({ repositioning: true }); - expect(wrapper.element).toMatchSnapshot(); - }); - - describe('pinStyle', () => { - it('sets cursor to `move` when repositioning = true', () => { - createComponent({ repositioning: true }); - expect(wrapper.vm.pinStyle.cursor).toBe('move'); - }); - - it('does not set cursor when repositioning = false', () => { - createComponent(); - expect(wrapper.vm.pinStyle.cursor).toBe(undefined); - }); - }); }); diff --git a/spec/frontend/design_management/components/design_overlay_spec.js b/spec/frontend/design_management/components/design_overlay_spec.js index 673a09320e5..4ef067e3f5e 100644 --- a/spec/frontend/design_management/components/design_overlay_spec.js +++ b/spec/frontend/design_management/components/design_overlay_spec.js @@ -202,7 +202,7 @@ describe('Design overlay component', () => { { x: position.x, y: position.y }, { x: 20, y: 20 }, ).then(() => { - expect(findFirstBadge().attributes().style).toBe('left: 20px; top: 20px; cursor: move;'); + expect(findFirstBadge().attributes().style).toBe('left: 20px; top: 20px;'); }); }); @@ -300,9 +300,7 @@ describe('Design overlay component', () => { { x: position.x, y: position.y }, { x: 20, y: 20 }, ).then(() => { - expect(findCommentBadge().attributes().style).toBe( - 'left: 20px; top: 20px; cursor: move;', - ); + expect(findCommentBadge().attributes().style).toBe('left: 20px; top: 20px;'); }); }); diff --git a/spec/frontend/packages/details/store/getters_spec.js b/spec/frontend/packages/details/store/getters_spec.js index 06e5950eb5d..cb164a426c8 100644 --- a/spec/frontend/packages/details/store/getters_spec.js +++ b/spec/frontend/packages/details/store/getters_spec.js @@ -101,7 +101,7 @@ describe('Getters PackageDetails Store', () => { ${packageWithoutBuildInfo} | ${'Maven'} ${npmPackage} | ${'NPM'} ${nugetPackage} | ${'NuGet'} - ${pypiPackage} | ${'PyPi'} + ${pypiPackage} | ${'PyPI'} `(`package type`, ({ packageEntity, expectedResult }) => { beforeEach(() => setupState({ packageEntity })); diff --git a/spec/frontend/packages/list/components/__snapshots__/packages_list_app_spec.js.snap b/spec/frontend/packages/list/components/__snapshots__/packages_list_app_spec.js.snap index 794e583a487..9a52531ae8d 100644 --- a/spec/frontend/packages/list/components/__snapshots__/packages_list_app_spec.js.snap +++ b/spec/frontend/packages/list/components/__snapshots__/packages_list_app_spec.js.snap @@ -385,7 +385,7 @@ exports[`packages_list_app renders 1`] = ` </b-tab-stub> <b-tab-stub tag="div" - title="PyPi" + title="PyPI" titlelinkclass="gl-tab-nav-item" > <template> @@ -400,7 +400,7 @@ exports[`packages_list_app renders 1`] = ` class="svg-250 svg-content" > <img - alt="There are no PyPi packages yet" + alt="There are no PyPI packages yet" class="gl-max-w-full" src="helpSvg" /> @@ -416,7 +416,7 @@ exports[`packages_list_app renders 1`] = ` <h1 class="h4" > - There are no PyPi packages yet + There are no PyPI packages yet </h1> <p> diff --git a/spec/frontend/packages/shared/utils_spec.js b/spec/frontend/packages/shared/utils_spec.js index 1fe90a4827f..3e4ce8eb323 100644 --- a/spec/frontend/packages/shared/utils_spec.js +++ b/spec/frontend/packages/shared/utils_spec.js @@ -37,7 +37,7 @@ describe('Packages shared utils', () => { ${'maven'} | ${'Maven'} ${'npm'} | ${'NPM'} ${'nuget'} | ${'NuGet'} - ${'pypi'} | ${'PyPi'} + ${'pypi'} | ${'PyPI'} ${'composer'} | ${'Composer'} ${'foo'} | ${null} `(`package type`, ({ packageType, expectedResult }) => { diff --git a/spec/graphql/types/merge_request_type_spec.rb b/spec/graphql/types/merge_request_type_spec.rb index 46aebbdabeb..1bad9f8c627 100644 --- a/spec/graphql/types/merge_request_type_spec.rb +++ b/spec/graphql/types/merge_request_type_spec.rb @@ -27,14 +27,13 @@ RSpec.describe GitlabSchema.types['MergeRequest'] do upvotes downvotes head_pipeline pipelines task_completion_status milestone assignees participants subscribed labels discussion_locked time_estimate total_time_spent reference author merged_at commit_count current_user_todos - conflicts auto_merge_enabled + conflicts auto_merge_enabled approved_by ] if Gitlab.ee? expected_fields << 'approved' expected_fields << 'approvals_left' expected_fields << 'approvals_required' - expected_fields << 'approved_by' end expect(described_class).to have_graphql_fields(*expected_fields) diff --git a/spec/graphql/types/package_type_enum_spec.rb b/spec/graphql/types/package_type_enum_spec.rb index 638f8ccbaee..407d5786f65 100644 --- a/spec/graphql/types/package_type_enum_spec.rb +++ b/spec/graphql/types/package_type_enum_spec.rb @@ -4,6 +4,6 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['PackageTypeEnum'] do it 'exposes all package types' do - expect(described_class.values.keys).to contain_exactly(*%w[MAVEN NPM CONAN NUGET PYPI COMPOSER GENERIC GOLANG]) + expect(described_class.values.keys).to contain_exactly(*%w[MAVEN NPM CONAN NUGET PYPI COMPOSER GENERIC GOLANG DEBIAN]) end end diff --git a/spec/lib/gitlab/ci/lint_spec.rb b/spec/lib/gitlab/ci/lint_spec.rb index 077c0fd3162..c67f8464123 100644 --- a/spec/lib/gitlab/ci/lint_spec.rb +++ b/spec/lib/gitlab/ci/lint_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Lint do - let_it_be(:project) { create(:project, :repository) } + let(:project) { create(:project, :repository) } let_it_be(:user) { create(:user) } let(:lint) { described_class.new(project: project, current_user: user) } @@ -61,6 +61,43 @@ RSpec.describe Gitlab::Ci::Lint do end end + shared_examples 'sets merged yaml' do + let(:content) do + <<~YAML + :include: + :local: another-gitlab-ci.yml + :test_job: + :stage: test + :script: echo + YAML + end + + let(:included_content) do + <<~YAML + :another_job: + :script: echo + YAML + end + + before do + project.repository.create_file( + project.creator, + 'another-gitlab-ci.yml', + included_content, + message: 'Automatically created another-gitlab-ci.yml', + branch_name: 'master' + ) + end + + it 'sets merged_config' do + root_config = YAML.safe_load(content, [Symbol]) + included_config = YAML.safe_load(included_content, [Symbol]) + expected_config = included_config.merge(root_config).except(:include) + + expect(subject.merged_yaml).to eq(expected_config.to_yaml) + end + end + shared_examples 'content with errors and warnings' do context 'when content has errors' do let(:content) do @@ -173,6 +210,8 @@ RSpec.describe Gitlab::Ci::Lint do end end + it_behaves_like 'sets merged yaml' + include_context 'advanced validations' do it 'does not catch advanced logical errors' do expect(subject).to be_valid @@ -203,6 +242,8 @@ RSpec.describe Gitlab::Ci::Lint do end end + it_behaves_like 'sets merged yaml' + include_context 'advanced validations' do it 'runs advanced logical validations' do expect(subject).not_to be_valid diff --git a/spec/lib/gitlab/ci/reports/test_suite_spec.rb b/spec/lib/gitlab/ci/reports/test_suite_spec.rb index 15fa78444e5..50d1595da73 100644 --- a/spec/lib/gitlab/ci/reports/test_suite_spec.rb +++ b/spec/lib/gitlab/ci/reports/test_suite_spec.rb @@ -229,6 +229,20 @@ RSpec.describe Gitlab::Ci::Reports::TestSuite do end end + describe '#each_test_case' do + before do + test_suite.add_test_case(test_case_success) + test_suite.add_test_case(test_case_failed) + test_suite.add_test_case(test_case_skipped) + test_suite.add_test_case(test_case_error) + end + + it 'yields each test case to given block' do + expect { |b| test_suite.each_test_case(&b) } + .to yield_successive_args(test_case_success, test_case_failed, test_case_skipped, test_case_error) + end + end + Gitlab::Ci::Reports::TestCase::STATUS_TYPES.each do |status_type| describe "##{status_type}_count" do subject { test_suite.public_send("#{status_type}_count") } diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb index f2bc6390032..37349c30224 100644 --- a/spec/lib/gitlab/closing_issue_extractor_spec.rb +++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb @@ -3,18 +3,16 @@ require 'spec_helper' RSpec.describe Gitlab::ClosingIssueExtractor do - let(:project) { create(:project) } - let(:project2) { create(:project) } - let(:forked_project) { Projects::ForkService.new(project, project2.creator).execute } - let(:issue) { create(:issue, project: project) } - let(:issue2) { create(:issue, project: project2) } + let_it_be_with_reload(:project) { create(:project) } + let_it_be(:project2) { create(:project) } + let_it_be(:issue) { create(:issue, project: project) } + let_it_be(:issue2) { create(:issue, project: project2) } let(:reference) { issue.to_reference } let(:cross_reference) { issue2.to_reference(project) } - let(:fork_cross_reference) { issue.to_reference(forked_project) } subject { described_class.new(project, project.creator) } - before do + before_all do project.add_developer(project.creator) project.add_developer(project2.creator) project2.add_maintainer(project.creator) @@ -325,6 +323,9 @@ RSpec.describe Gitlab::ClosingIssueExtractor do end context "with a cross-project fork reference" do + let(:forked_project) { Projects::ForkService.new(project, project2.creator).execute } + let(:fork_cross_reference) { issue.to_reference(forked_project) } + subject { described_class.new(forked_project, forked_project.creator) } it do @@ -348,8 +349,8 @@ RSpec.describe Gitlab::ClosingIssueExtractor do end context 'with multiple references' do - let(:other_issue) { create(:issue, project: project) } - let(:third_issue) { create(:issue, project: project) } + let_it_be(:other_issue) { create(:issue, project: project) } + let_it_be(:third_issue) { create(:issue, project: project) } let(:reference2) { other_issue.to_reference } let(:reference3) { third_issue.to_reference } diff --git a/spec/lib/gitlab/redis/hll_spec.rb b/spec/lib/gitlab/redis/hll_spec.rb index cbf78f23036..e452e5b2f52 100644 --- a/spec/lib/gitlab/redis/hll_spec.rb +++ b/spec/lib/gitlab/redis/hll_spec.rb @@ -39,6 +39,24 @@ RSpec.describe Gitlab::Redis::HLL, :clean_gitlab_redis_shared_state do end end end + + context 'when adding entries' do + let(:metric) { 'test-{metric}' } + + it 'supports single value' do + track_event(metric, 1) + + expect(count_unique_events([metric])).to eq(1) + end + + it 'supports multiple values' do + stub_const("#{described_class.name}::HLL_BATCH_SIZE", 2) + + track_event(metric, [1, 2, 3, 4, 5]) + + expect(count_unique_events([metric])).to eq(5) + end + end end describe '.count' do @@ -94,13 +112,13 @@ RSpec.describe Gitlab::Redis::HLL, :clean_gitlab_redis_shared_state do expect(unique_counts).to eq(4) end + end - def track_event(key, value, expiry = 1.day) - described_class.add(key: key, value: value, expiry: expiry) - end + def track_event(key, value, expiry = 1.day) + described_class.add(key: key, value: value, expiry: expiry) + end - def count_unique_events(keys) - described_class.count(keys: keys) - end + def count_unique_events(keys) + described_class.count(keys: keys) end end diff --git a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb index 3255e3616b2..e84c3c17274 100644 --- a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb +++ b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb @@ -20,7 +20,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s describe '.categories' do it 'gets all unique category names' do - expect(described_class.categories).to contain_exactly('analytics', 'compliance', 'ide_edit', 'search', 'source_code', 'incident_management', 'issues_edit') + expect(described_class.categories).to contain_exactly('analytics', 'compliance', 'ide_edit', 'search', 'source_code', 'incident_management', 'issues_edit', 'testing') end end diff --git a/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb index 4cc85c86de1..bd3c8024215 100644 --- a/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb +++ b/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb @@ -132,6 +132,76 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git end end + context 'for Issue cross-referenced actions' do + it_behaves_like 'tracks and counts action' do + let(:action) { described_class::ISSUE_CROSS_REFERENCED } + + def track_action(params) + described_class.track_issue_cross_referenced_action(**params) + end + end + end + + context 'for Issue moved actions' do + it_behaves_like 'tracks and counts action' do + let(:action) { described_class::ISSUE_MOVED } + + def track_action(params) + described_class.track_issue_moved_action(**params) + end + end + end + + context 'for Issue relate actions' do + it_behaves_like 'tracks and counts action' do + let(:action) { described_class::ISSUE_RELATED } + + def track_action(params) + described_class.track_issue_related_action(**params) + end + end + end + + context 'for Issue unrelate actions' do + it_behaves_like 'tracks and counts action' do + let(:action) { described_class::ISSUE_UNRELATED } + + def track_action(params) + described_class.track_issue_unrelated_action(**params) + end + end + end + + context 'for Issue marked as duplicate actions' do + it_behaves_like 'tracks and counts action' do + let(:action) { described_class::ISSUE_MARKED_AS_DUPLICATE } + + def track_action(params) + described_class.track_issue_marked_as_duplicate_action(**params) + end + end + end + + context 'for Issue locked actions' do + it_behaves_like 'tracks and counts action' do + let(:action) { described_class::ISSUE_LOCKED } + + def track_action(params) + described_class.track_issue_locked_action(**params) + end + end + end + + context 'for Issue unlocked actions' do + it_behaves_like 'tracks and counts action' do + let(:action) { described_class::ISSUE_UNLOCKED } + + def track_action(params) + described_class.track_issue_unlocked_action(**params) + end + end + end + it 'can return the count of actions per user deduplicated', :aggregate_failures do described_class.track_issue_title_changed_action(author: user1) described_class.track_issue_description_changed_action(author: user1) diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 984fd4c08e6..09728539499 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -1182,9 +1182,9 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do subject { described_class.redis_hll_counters } let(:categories) { ::Gitlab::UsageDataCounters::HLLRedisCounter.categories } - let(:ineligible_total_categories) { ['source_code'] } + let(:ineligible_total_categories) { %w[source_code testing] } - it 'has all know_events' do + it 'has all known_events' do expect(subject).to have_key(:redis_hll_counters) expect(subject[:redis_hll_counters].keys).to match_array(categories) diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 49731757593..ab22a203d06 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -2307,6 +2307,54 @@ RSpec.describe Ci::Build do end end + describe '#has_expired_locked_archive_artifacts?' do + subject { build.has_expired_locked_archive_artifacts? } + + context 'when build does not have artifacts' do + it { is_expected.to eq(nil) } + end + + context 'when build has artifacts' do + before do + create(:ci_job_artifact, :archive, job: build) + end + + context 'when artifacts are unlocked' do + before do + build.pipeline.unlocked! + end + + it { is_expected.to eq(false) } + end + + context 'when artifacts are locked' do + before do + build.pipeline.artifacts_locked! + end + + context 'when artifacts do not expire' do + it { is_expected.to eq(false) } + end + + context 'when artifacts expire in the future' do + before do + build.update!(artifacts_expire_at: 1.day.from_now) + end + + it { is_expected.to eq(false) } + end + + context 'when artifacts expired in the past' do + before do + build.update!(artifacts_expire_at: 1.day.ago) + end + + it { is_expected.to eq(true) } + end + end + end + end + describe '#has_expiring_archive_artifacts?' do context 'when artifacts have expiration date set' do before do diff --git a/spec/requests/api/graphql/project/merge_requests_spec.rb b/spec/requests/api/graphql/project/merge_requests_spec.rb index dfce1e12dd5..40e5e1d5a84 100644 --- a/spec/requests/api/graphql/project/merge_requests_spec.rb +++ b/spec/requests/api/graphql/project/merge_requests_spec.rb @@ -173,6 +173,28 @@ RSpec.describe 'getting merge request listings nested in a project' do it_behaves_like 'searching with parameters' end + context 'when requesting `approved_by`' do + let(:search_params) { { iids: [merge_request_a.iid.to_s, merge_request_b.iid.to_s] } } + let(:extra_iid_for_second_query) { merge_request_c.iid.to_s } + let(:requested_fields) { query_graphql_field(:approved_by, nil, query_graphql_field(:nodes, nil, [:username])) } + + def execute_query + query = query_merge_requests(requested_fields) + post_graphql(query, current_user: current_user) + end + + it 'exposes approver username' do + merge_request_a.approved_by_users << current_user + + execute_query + + user_data = { 'username' => current_user.username } + expect(results).to include(a_hash_including('approvedBy' => { 'nodes' => array_including(user_data) })) + end + + include_examples 'N+1 query check' + end + describe 'fields' do let(:requested_fields) { nil } let(:extra_iid_for_second_query) { merge_request_c.iid.to_s } diff --git a/spec/requests/api/lint_spec.rb b/spec/requests/api/lint_spec.rb index 6b5a4b6436a..3892df273bd 100644 --- a/spec/requests/api/lint_spec.rb +++ b/spec/requests/api/lint_spec.rb @@ -75,4 +75,115 @@ RSpec.describe API::Lint do end end end + + describe 'GET /projects/:id/ci/lint' do + subject(:ci_lint) { get api("/projects/#{project.id}/ci/lint", api_user), params: { dry_run: dry_run } } + + let_it_be(:api_user) { create(:user) } + let(:project) { create(:project, :repository) } + let(:dry_run) { nil } + + RSpec.shared_examples 'valid config' do + it 'passes validation' do + ci_lint + + included_config = YAML.safe_load(included_content, [Symbol]) + root_config = YAML.safe_load(yaml_content, [Symbol]) + expected_yaml = included_config.merge(root_config).except(:include).to_yaml + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to be_an Hash + expect(json_response['merged_yaml']).to eq(expected_yaml) + expect(json_response['valid']).to eq(true) + expect(json_response['errors']).to eq([]) + end + end + + RSpec.shared_examples 'invalid config' do + it 'responds with errors about invalid configuration' do + ci_lint + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['merged_yaml']).to eq(yaml_content) + expect(json_response['valid']).to eq(false) + expect(json_response['errors']).to eq(['jobs config should contain at least one visible job']) + end + end + + context 'when unauthenticated' do + it 'returns authentication error' do + ci_lint + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when authenticated as project member' do + before do + project.add_developer(api_user) + end + + context 'with valid .gitlab-ci.yml content' do + let(:yaml_content) do + { include: { local: 'another-gitlab-ci.yml' }, test: { stage: 'test', script: 'echo 1' } }.to_yaml + end + + let(:included_content) do + { another_test: { stage: 'test', script: 'echo 1' } }.to_yaml + end + + before do + project.repository.create_file( + project.creator, + '.gitlab-ci.yml', + yaml_content, + message: 'Automatically created .gitlab-ci.yml', + branch_name: 'master' + ) + + project.repository.create_file( + project.creator, + 'another-gitlab-ci.yml', + included_content, + message: 'Automatically created another-gitlab-ci.yml', + branch_name: 'master' + ) + end + + context 'when running as dry run' do + let(:dry_run) { true } + + it_behaves_like 'valid config' + end + + context 'when running static validation' do + let(:dry_run) { false } + + it_behaves_like 'valid config' + end + end + + context 'with invalid .gitlab-ci.yml content' do + let(:yaml_content) do + { image: 'ruby:2.7', services: ['postgres'] }.to_yaml + end + + before do + stub_ci_pipeline_yaml_file(yaml_content) + end + + context 'when running as dry run' do + let(:dry_run) { true } + + it_behaves_like 'invalid config' + end + + context 'when running static validation' do + let(:dry_run) { false } + + it_behaves_like 'invalid config' + end + end + end + end end diff --git a/spec/requests/api/pypi_packages_spec.rb b/spec/requests/api/pypi_packages_spec.rb index e72ac002f6b..de5dea449f8 100644 --- a/spec/requests/api/pypi_packages_spec.rb +++ b/spec/requests/api/pypi_packages_spec.rb @@ -23,24 +23,24 @@ RSpec.describe API::PypiPackages do using RSpec::Parameterized::TableSyntax where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do - 'PUBLIC' | :developer | true | true | 'PyPi package versions' | :success - 'PUBLIC' | :guest | true | true | 'PyPi package versions' | :success - 'PUBLIC' | :developer | true | false | 'PyPi package versions' | :success - 'PUBLIC' | :guest | true | false | 'PyPi package versions' | :success - 'PUBLIC' | :developer | false | true | 'PyPi package versions' | :success - 'PUBLIC' | :guest | false | true | 'PyPi package versions' | :success - 'PUBLIC' | :developer | false | false | 'PyPi package versions' | :success - 'PUBLIC' | :guest | false | false | 'PyPi package versions' | :success - 'PUBLIC' | :anonymous | false | true | 'PyPi package versions' | :success - 'PRIVATE' | :developer | true | true | 'PyPi package versions' | :success - 'PRIVATE' | :guest | true | true | 'process PyPi api request' | :forbidden - 'PRIVATE' | :developer | true | false | 'process PyPi api request' | :unauthorized - 'PRIVATE' | :guest | true | false | 'process PyPi api request' | :unauthorized - 'PRIVATE' | :developer | false | true | 'process PyPi api request' | :not_found - 'PRIVATE' | :guest | false | true | 'process PyPi api request' | :not_found - 'PRIVATE' | :developer | false | false | 'process PyPi api request' | :unauthorized - 'PRIVATE' | :guest | false | false | 'process PyPi api request' | :unauthorized - 'PRIVATE' | :anonymous | false | true | 'process PyPi api request' | :unauthorized + 'PUBLIC' | :developer | true | true | 'PyPI package versions' | :success + 'PUBLIC' | :guest | true | true | 'PyPI package versions' | :success + 'PUBLIC' | :developer | true | false | 'PyPI package versions' | :success + 'PUBLIC' | :guest | true | false | 'PyPI package versions' | :success + 'PUBLIC' | :developer | false | true | 'PyPI package versions' | :success + 'PUBLIC' | :guest | false | true | 'PyPI package versions' | :success + 'PUBLIC' | :developer | false | false | 'PyPI package versions' | :success + 'PUBLIC' | :guest | false | false | 'PyPI package versions' | :success + 'PUBLIC' | :anonymous | false | true | 'PyPI package versions' | :success + 'PRIVATE' | :developer | true | true | 'PyPI package versions' | :success + 'PRIVATE' | :guest | true | true | 'process PyPI api request' | :forbidden + 'PRIVATE' | :developer | true | false | 'process PyPI api request' | :unauthorized + 'PRIVATE' | :guest | true | false | 'process PyPI api request' | :unauthorized + 'PRIVATE' | :developer | false | true | 'process PyPI api request' | :not_found + 'PRIVATE' | :guest | false | true | 'process PyPI api request' | :not_found + 'PRIVATE' | :developer | false | false | 'process PyPI api request' | :unauthorized + 'PRIVATE' | :guest | false | false | 'process PyPI api request' | :unauthorized + 'PRIVATE' | :anonymous | false | true | 'process PyPI api request' | :unauthorized end with_them do @@ -76,24 +76,24 @@ RSpec.describe API::PypiPackages do using RSpec::Parameterized::TableSyntax where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do - 'PUBLIC' | :developer | true | true | 'process PyPi api request' | :success - 'PUBLIC' | :guest | true | true | 'process PyPi api request' | :forbidden - 'PUBLIC' | :developer | true | false | 'process PyPi api request' | :unauthorized - 'PUBLIC' | :guest | true | false | 'process PyPi api request' | :unauthorized - 'PUBLIC' | :developer | false | true | 'process PyPi api request' | :forbidden - 'PUBLIC' | :guest | false | true | 'process PyPi api request' | :forbidden - 'PUBLIC' | :developer | false | false | 'process PyPi api request' | :unauthorized - 'PUBLIC' | :guest | false | false | 'process PyPi api request' | :unauthorized - 'PUBLIC' | :anonymous | false | true | 'process PyPi api request' | :unauthorized - 'PRIVATE' | :developer | true | true | 'process PyPi api request' | :success - 'PRIVATE' | :guest | true | true | 'process PyPi api request' | :forbidden - 'PRIVATE' | :developer | true | false | 'process PyPi api request' | :unauthorized - 'PRIVATE' | :guest | true | false | 'process PyPi api request' | :unauthorized - 'PRIVATE' | :developer | false | true | 'process PyPi api request' | :not_found - 'PRIVATE' | :guest | false | true | 'process PyPi api request' | :not_found - 'PRIVATE' | :developer | false | false | 'process PyPi api request' | :unauthorized - 'PRIVATE' | :guest | false | false | 'process PyPi api request' | :unauthorized - 'PRIVATE' | :anonymous | false | true | 'process PyPi api request' | :unauthorized + 'PUBLIC' | :developer | true | true | 'process PyPI api request' | :success + 'PUBLIC' | :guest | true | true | 'process PyPI api request' | :forbidden + 'PUBLIC' | :developer | true | false | 'process PyPI api request' | :unauthorized + 'PUBLIC' | :guest | true | false | 'process PyPI api request' | :unauthorized + 'PUBLIC' | :developer | false | true | 'process PyPI api request' | :forbidden + 'PUBLIC' | :guest | false | true | 'process PyPI api request' | :forbidden + 'PUBLIC' | :developer | false | false | 'process PyPI api request' | :unauthorized + 'PUBLIC' | :guest | false | false | 'process PyPI api request' | :unauthorized + 'PUBLIC' | :anonymous | false | true | 'process PyPI api request' | :unauthorized + 'PRIVATE' | :developer | true | true | 'process PyPI api request' | :success + 'PRIVATE' | :guest | true | true | 'process PyPI api request' | :forbidden + 'PRIVATE' | :developer | true | false | 'process PyPI api request' | :unauthorized + 'PRIVATE' | :guest | true | false | 'process PyPI api request' | :unauthorized + 'PRIVATE' | :developer | false | true | 'process PyPI api request' | :not_found + 'PRIVATE' | :guest | false | true | 'process PyPI api request' | :not_found + 'PRIVATE' | :developer | false | false | 'process PyPI api request' | :unauthorized + 'PRIVATE' | :guest | false | false | 'process PyPI api request' | :unauthorized + 'PRIVATE' | :anonymous | false | true | 'process PyPI api request' | :unauthorized end with_them do @@ -142,24 +142,24 @@ RSpec.describe API::PypiPackages do using RSpec::Parameterized::TableSyntax where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do - 'PUBLIC' | :developer | true | true | 'PyPi package creation' | :created - 'PUBLIC' | :guest | true | true | 'process PyPi api request' | :forbidden - 'PUBLIC' | :developer | true | false | 'process PyPi api request' | :unauthorized - 'PUBLIC' | :guest | true | false | 'process PyPi api request' | :unauthorized - 'PUBLIC' | :developer | false | true | 'process PyPi api request' | :forbidden - 'PUBLIC' | :guest | false | true | 'process PyPi api request' | :forbidden - 'PUBLIC' | :developer | false | false | 'process PyPi api request' | :unauthorized - 'PUBLIC' | :guest | false | false | 'process PyPi api request' | :unauthorized - 'PUBLIC' | :anonymous | false | true | 'process PyPi api request' | :unauthorized - 'PRIVATE' | :developer | true | true | 'process PyPi api request' | :created - 'PRIVATE' | :guest | true | true | 'process PyPi api request' | :forbidden - 'PRIVATE' | :developer | true | false | 'process PyPi api request' | :unauthorized - 'PRIVATE' | :guest | true | false | 'process PyPi api request' | :unauthorized - 'PRIVATE' | :developer | false | true | 'process PyPi api request' | :not_found - 'PRIVATE' | :guest | false | true | 'process PyPi api request' | :not_found - 'PRIVATE' | :developer | false | false | 'process PyPi api request' | :unauthorized - 'PRIVATE' | :guest | false | false | 'process PyPi api request' | :unauthorized - 'PRIVATE' | :anonymous | false | true | 'process PyPi api request' | :unauthorized + 'PUBLIC' | :developer | true | true | 'PyPI package creation' | :created + 'PUBLIC' | :guest | true | true | 'process PyPI api request' | :forbidden + 'PUBLIC' | :developer | true | false | 'process PyPI api request' | :unauthorized + 'PUBLIC' | :guest | true | false | 'process PyPI api request' | :unauthorized + 'PUBLIC' | :developer | false | true | 'process PyPI api request' | :forbidden + 'PUBLIC' | :guest | false | true | 'process PyPI api request' | :forbidden + 'PUBLIC' | :developer | false | false | 'process PyPI api request' | :unauthorized + 'PUBLIC' | :guest | false | false | 'process PyPI api request' | :unauthorized + 'PUBLIC' | :anonymous | false | true | 'process PyPI api request' | :unauthorized + 'PRIVATE' | :developer | true | true | 'process PyPI api request' | :created + 'PRIVATE' | :guest | true | true | 'process PyPI api request' | :forbidden + 'PRIVATE' | :developer | true | false | 'process PyPI api request' | :unauthorized + 'PRIVATE' | :guest | true | false | 'process PyPI api request' | :unauthorized + 'PRIVATE' | :developer | false | true | 'process PyPI api request' | :not_found + 'PRIVATE' | :guest | false | true | 'process PyPI api request' | :not_found + 'PRIVATE' | :developer | false | false | 'process PyPI api request' | :unauthorized + 'PRIVATE' | :guest | false | false | 'process PyPI api request' | :unauthorized + 'PRIVATE' | :anonymous | false | true | 'process PyPI api request' | :unauthorized end with_them do @@ -185,7 +185,7 @@ RSpec.describe API::PypiPackages do project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) end - it_behaves_like 'process PyPi api request', :developer, :bad_request, true + it_behaves_like 'process PyPI api request', :developer, :bad_request, true end context 'with an invalid package' do @@ -232,24 +232,24 @@ RSpec.describe API::PypiPackages do using RSpec::Parameterized::TableSyntax where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do - 'PUBLIC' | :developer | true | true | 'PyPi package download' | :success - 'PUBLIC' | :guest | true | true | 'PyPi package download' | :success - 'PUBLIC' | :developer | true | false | 'PyPi package download' | :success - 'PUBLIC' | :guest | true | false | 'PyPi package download' | :success - 'PUBLIC' | :developer | false | true | 'PyPi package download' | :success - 'PUBLIC' | :guest | false | true | 'PyPi package download' | :success - 'PUBLIC' | :developer | false | false | 'PyPi package download' | :success - 'PUBLIC' | :guest | false | false | 'PyPi package download' | :success - 'PUBLIC' | :anonymous | false | true | 'PyPi package download' | :success - 'PRIVATE' | :developer | true | true | 'PyPi package download' | :success - 'PRIVATE' | :guest | true | true | 'PyPi package download' | :success - 'PRIVATE' | :developer | true | false | 'PyPi package download' | :success - 'PRIVATE' | :guest | true | false | 'PyPi package download' | :success - 'PRIVATE' | :developer | false | true | 'PyPi package download' | :success - 'PRIVATE' | :guest | false | true | 'PyPi package download' | :success - 'PRIVATE' | :developer | false | false | 'PyPi package download' | :success - 'PRIVATE' | :guest | false | false | 'PyPi package download' | :success - 'PRIVATE' | :anonymous | false | true | 'PyPi package download' | :success + 'PUBLIC' | :developer | true | true | 'PyPI package download' | :success + 'PUBLIC' | :guest | true | true | 'PyPI package download' | :success + 'PUBLIC' | :developer | true | false | 'PyPI package download' | :success + 'PUBLIC' | :guest | true | false | 'PyPI package download' | :success + 'PUBLIC' | :developer | false | true | 'PyPI package download' | :success + 'PUBLIC' | :guest | false | true | 'PyPI package download' | :success + 'PUBLIC' | :developer | false | false | 'PyPI package download' | :success + 'PUBLIC' | :guest | false | false | 'PyPI package download' | :success + 'PUBLIC' | :anonymous | false | true | 'PyPI package download' | :success + 'PRIVATE' | :developer | true | true | 'PyPI package download' | :success + 'PRIVATE' | :guest | true | true | 'PyPI package download' | :success + 'PRIVATE' | :developer | true | false | 'PyPI package download' | :success + 'PRIVATE' | :guest | true | false | 'PyPI package download' | :success + 'PRIVATE' | :developer | false | true | 'PyPI package download' | :success + 'PRIVATE' | :guest | false | true | 'PyPI package download' | :success + 'PRIVATE' | :developer | false | false | 'PyPI package download' | :success + 'PRIVATE' | :guest | false | false | 'PyPI package download' | :success + 'PRIVATE' | :anonymous | false | true | 'PyPI package download' | :success end with_them do diff --git a/spec/services/ci/build_report_result_service_spec.rb b/spec/services/ci/build_report_result_service_spec.rb index 70bcf74ba43..134b662a72a 100644 --- a/spec/services/ci/build_report_result_service_spec.rb +++ b/spec/services/ci/build_report_result_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Ci::BuildReportResultService do - describe "#execute" do + describe '#execute', :clean_gitlab_redis_shared_state do subject(:build_report_result) { described_class.new.execute(build) } context 'when build is finished' do @@ -17,6 +17,25 @@ RSpec.describe Ci::BuildReportResultService do expect(build_report_result.tests_skipped).to eq(0) expect(build_report_result.tests_duration).to eq(0.010284) expect(Ci::BuildReportResult.count).to eq(1) + + unique_test_cases_parsed = Gitlab::UsageDataCounters::HLLRedisCounter.unique_events( + event_names: described_class::EVENT_NAME, + start_date: 2.weeks.ago, + end_date: 2.weeks.from_now + ) + expect(unique_test_cases_parsed).to eq(4) + end + + context 'when feature flag for tracking is disabled' do + before do + stub_feature_flags(track_unique_test_cases_parsed: false) + end + + it 'creates the report but does not track the event' do + expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event) + expect(build_report_result.tests_name).to eq("test") + expect(Ci::BuildReportResult.count).to eq(1) + end end context 'when data has already been persisted' do diff --git a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb index 40bedc84366..bbcf856350d 100644 --- a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.shared_examples 'PyPi package creation' do |user_type, status, add_member = true| +RSpec.shared_examples 'PyPI package creation' do |user_type, status, add_member = true| RSpec.shared_examples 'creating pypi package files' do it 'creates package files' do expect { subject } @@ -106,7 +106,7 @@ RSpec.shared_examples 'PyPi package creation' do |user_type, status, add_member end end -RSpec.shared_examples 'PyPi package versions' do |user_type, status, add_member = true| +RSpec.shared_examples 'PyPI package versions' do |user_type, status, add_member = true| context "for user type #{user_type}" do before do project.send("add_#{user_type}", user) if add_member && user_type != :anonymous @@ -123,7 +123,7 @@ RSpec.shared_examples 'PyPi package versions' do |user_type, status, add_member end end -RSpec.shared_examples 'PyPi package download' do |user_type, status, add_member = true| +RSpec.shared_examples 'PyPI package download' do |user_type, status, add_member = true| context "for user type #{user_type}" do before do project.send("add_#{user_type}", user) if add_member && user_type != :anonymous @@ -140,7 +140,7 @@ RSpec.shared_examples 'PyPi package download' do |user_type, status, add_member end end -RSpec.shared_examples 'process PyPi api request' do |user_type, status, add_member = true| +RSpec.shared_examples 'process PyPI api request' do |user_type, status, add_member = true| context "for user type #{user_type}" do before do project.send("add_#{user_type}", user) if add_member && user_type != :anonymous @@ -155,13 +155,13 @@ RSpec.shared_examples 'rejects PyPI access with unknown project id' do let(:project) { OpenStruct.new(id: 1234567890) } context 'as anonymous' do - it_behaves_like 'process PyPi api request', :anonymous, :not_found + it_behaves_like 'process PyPI api request', :anonymous, :not_found end context 'as authenticated user' do subject { get api(url), headers: basic_auth_header(user.username, personal_access_token.token) } - it_behaves_like 'process PyPi api request', :anonymous, :not_found + it_behaves_like 'process PyPI api request', :anonymous, :not_found end end end diff --git a/spec/support/shared_examples/services/packages_shared_examples.rb b/spec/support/shared_examples/services/packages_shared_examples.rb index c00a087311c..65f4b3b5513 100644 --- a/spec/support/shared_examples/services/packages_shared_examples.rb +++ b/spec/support/shared_examples/services/packages_shared_examples.rb @@ -171,6 +171,7 @@ RSpec.shared_examples 'filters on each package_type' do |is_project: false| let_it_be(:package6) { create(:composer_package, project: project) } let_it_be(:package7) { create(:generic_package, project: project) } let_it_be(:package8) { create(:golang_package, project: project) } + let_it_be(:package9) { create(:debian_package, project: project) } Packages::Package.package_types.keys.each do |package_type| context "for package type #{package_type}" do |