diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-31 00:12:17 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-31 00:12:17 +0000 |
commit | 46f49bc8e69405fce01691d698c06c75aec5296a (patch) | |
tree | 05ade305dd7b87062277cc59cbef194e93cbad88 /spec | |
parent | 6e124379237d8122a8b4454e1e8f63f239c6958d (diff) | |
download | gitlab-ce-46f49bc8e69405fce01691d698c06c75aec5296a.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
37 files changed, 816 insertions, 104 deletions
diff --git a/spec/controllers/admin/hooks_controller_spec.rb b/spec/controllers/admin/hooks_controller_spec.rb index 4101bd7f658..4e68ffdda2a 100644 --- a/spec/controllers/admin/hooks_controller_spec.rb +++ b/spec/controllers/admin/hooks_controller_spec.rb @@ -59,6 +59,7 @@ RSpec.describe Admin::HooksController do enable_ssl_verification: false, url_variables: [ { key: 'token', value: 'some secret value' }, + { key: 'baz', value: 'qux' }, { key: 'foo', value: nil } ] } @@ -71,7 +72,7 @@ RSpec.describe Admin::HooksController do expect(flash[:notice]).to include('was updated') expect(hook).to have_attributes(hook_params.except(:url_variables)) expect(hook).to have_attributes( - url_variables: { 'token' => 'some secret value', 'baz' => 'woo' } + url_variables: { 'token' => 'some secret value', 'baz' => 'qux' } ) end end diff --git a/spec/controllers/concerns/confirm_email_warning_spec.rb b/spec/controllers/concerns/confirm_email_warning_spec.rb index fca99d37000..7cfbd86cdcb 100644 --- a/spec/controllers/concerns/confirm_email_warning_spec.rb +++ b/spec/controllers/concerns/confirm_email_warning_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ConfirmEmailWarning do +RSpec.describe ConfirmEmailWarning, feature_category: :system_access do before do stub_application_setting_enum('email_confirmation_setting', 'soft') end @@ -82,6 +82,38 @@ RSpec.describe ConfirmEmailWarning do it { is_expected.to set_confirm_warning_for(user.email) } end end + + context 'when user is being impersonated' do + let(:impersonator) { create(:admin) } + + before do + allow(controller).to receive(:session).and_return({ impersonator_id: impersonator.id }) + + get :index + end + + it { is_expected.to set_confirm_warning_for(user.email) } + + context 'when impersonated user email has html in their email' do + let(:user) { create(:user, confirmed_at: nil, unconfirmed_email: "malicious@test.com<form><input/title='<script>alert(document.domain)</script>'>") } + + it { is_expected.to set_confirm_warning_for("malicious@test.com<form><input/title='<script>alert(document.domain)</script>'>") } + end + end + + context 'when user is not being impersonated' do + before do + get :index + end + + it { is_expected.to set_confirm_warning_for(user.email) } + + context 'when user email has html in their email' do + let(:user) { create(:user, confirmed_at: nil, unconfirmed_email: "malicious@test.com<form><input/title='<script>alert(document.domain)</script>'>") } + + it { is_expected.to set_confirm_warning_for("malicious@test.com<form><input/title='<script>alert(document.domain)</script>'>") } + end + end end end end diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index 3f14317a84b..b07cb7a228d 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -2,15 +2,16 @@ require 'spec_helper' -RSpec.describe Projects::BlobController do +RSpec.describe Projects::BlobController, feature_category: :source_code_management do include ProjectForksHelper let(:project) { create(:project, :public, :repository, previous_default_branch: previous_default_branch) } let(:previous_default_branch) { nil } describe "GET show" do - def request - get(:show, params: { namespace_id: project.namespace, project_id: project, id: id }) + let(:params) { { namespace_id: project.namespace, project_id: project, id: id } } + let(:request) do + get(:show, params: params) end render_views @@ -18,10 +19,34 @@ RSpec.describe Projects::BlobController do context 'with file path' do before do expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original - + project.repository.add_tag(project.creator, 'ambiguous_ref', RepoHelpers.sample_commit.id) + project.repository.add_branch(project.creator, 'ambiguous_ref', RepoHelpers.another_sample_commit.id) request end + context 'when the ref is ambiguous' do + let(:ref) { 'ambiguous_ref' } + let(:path) { 'README.md' } + let(:id) { "#{ref}/#{path}" } + let(:params) { { namespace_id: project.namespace, project_id: project, id: id, ref_type: ref_type } } + + context 'and explicitly requesting a branch' do + let(:ref_type) { 'heads' } + + it 'redirects to blob#show with sha for the branch' do + expect(response).to redirect_to(project_blob_path(project, "#{RepoHelpers.another_sample_commit.id}/#{path}")) + end + end + + context 'and explicitly requesting a tag' do + let(:ref_type) { 'tags' } + + it 'responds with success' do + expect(response).to be_ok + end + end + end + context "valid branch, valid file" do let(:id) { 'master/README.md' } diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb index 38f72c769f3..d16e5eea2e9 100644 --- a/spec/controllers/projects/clusters_controller_spec.rb +++ b/spec/controllers/projects/clusters_controller_spec.rb @@ -7,7 +7,7 @@ RSpec.describe Projects::ClustersController, feature_category: :kubernetes_manag include GoogleApi::CloudPlatformHelpers include KubernetesHelpers - let_it_be(:project) { create(:project) } + let_it_be_with_reload(:project) { create(:project) } let(:user) { create(:user) } @@ -140,6 +140,27 @@ RSpec.describe Projects::ClustersController, feature_category: :kubernetes_manag expect(response).to redirect_to(new_user_session_path) end end + + context 'with a public project' do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + project.project_feature.update!(metrics_dashboard_access_level: ProjectFeature::ENABLED) + end + + context 'with guest user' do + let(:prometheus_body) { nil } + + before do + project.add_guest(user) + end + + it 'returns 404' do + get :prometheus_proxy, params: prometheus_proxy_params + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end end end diff --git a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb index 68d50cf19f0..6b0c164e432 100644 --- a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb +++ b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe Projects::Environments::PrometheusApiController do let_it_be(:user) { create(:user) } - let_it_be(:project) { create(:project) } + let_it_be_with_reload(:project) { create(:project) } let_it_be(:proxyable) { create(:environment, project: project) } before do @@ -70,6 +70,27 @@ RSpec.describe Projects::Environments::PrometheusApiController do expect(response).to redirect_to(new_user_session_path) end end + + context 'with a public project' do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + project.project_feature.update!(metrics_dashboard_access_level: ProjectFeature::ENABLED) + end + + context 'with guest user' do + let(:prometheus_body) { nil } + + before do + project.add_guest(user) + end + + it 'returns 404' do + get :prometheus_proxy, params: prometheus_proxy_params + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end end end end diff --git a/spec/controllers/projects/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb index 2b3adc719c1..61998d516e8 100644 --- a/spec/controllers/projects/tree_controller_spec.rb +++ b/spec/controllers/projects/tree_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::TreeController do +RSpec.describe Projects::TreeController, feature_category: :source_code_management do let(:project) { create(:project, :repository, previous_default_branch: previous_default_branch) } let(:previous_default_branch) { nil } let(:user) { create(:user) } @@ -15,15 +15,41 @@ RSpec.describe Projects::TreeController do end describe "GET show" do + let(:params) do + { + namespace_id: project.namespace.to_param, project_id: project, id: id + } + end + # Make sure any errors accessing the tree in our views bubble up to this spec render_views before do expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original + project.repository.add_tag(project.creator, 'ambiguous_ref', RepoHelpers.sample_commit.id) + project.repository.add_branch(project.creator, 'ambiguous_ref', RepoHelpers.another_sample_commit.id) + get :show, params: params + end - get :show, params: { - namespace_id: project.namespace.to_param, project_id: project, id: id - } + context 'when the ref is ambiguous' do + let(:id) { 'ambiguous_ref' } + let(:params) { { namespace_id: project.namespace, project_id: project, id: id, ref_type: ref_type } } + + context 'and explicitly requesting a branch' do + let(:ref_type) { 'heads' } + + it 'redirects to blob#show with sha for the branch' do + expect(response).to redirect_to(project_tree_path(project, RepoHelpers.another_sample_commit.id)) + end + end + + context 'and explicitly requesting a tag' do + let(:ref_type) { 'tags' } + + it 'responds with success' do + expect(response).to be_ok + end + end end context "valid branch, no path" do diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index cd6d3990309..5ece9f09e5f 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -163,6 +163,69 @@ RSpec.describe ProjectsController, feature_category: :projects do expect(assigns(:notification_setting).level).to eq("watch") end end + + context 'when there is a tag with the same name as the default branch' do + let_it_be(:tagged_project) { create(:project, :public, :custom_repo, files: ['somefile']) } + let(:tree_with_default_branch) do + branch = tagged_project.repository.find_branch(tagged_project.default_branch) + project_tree_path(tagged_project, branch.target) + end + + before do + tagged_project.repository.create_file( + tagged_project.creator, + 'file_for_tag', + 'content for file', + message: "Automatically created file", + branch_name: 'branch-to-tag' + ) + + tagged_project.repository.add_tag( + tagged_project.creator, + tagged_project.default_branch, # tag name + 'branch-to-tag' # target + ) + end + + it 'redirects to tree view for the default branch' do + get :show, params: { namespace_id: tagged_project.namespace, id: tagged_project } + expect(response).to redirect_to(tree_with_default_branch) + end + end + + context 'when the default branch name can resolve to another ref' do + let!(:project_with_default_branch) do + create(:project, :public, :custom_repo, files: ['somefile']).tap do |p| + p.repository.create_branch("refs/heads/refs/heads/#{other_ref}", 'master') + p.change_head("refs/heads/#{other_ref}") + end.reload + end + + let(:other_ref) { 'branch-name' } + + context 'but there is no other ref' do + it 'responds with ok' do + get :show, params: { namespace_id: project_with_default_branch.namespace, id: project_with_default_branch } + expect(response).to be_ok + end + end + + context 'and that other ref exists' do + let(:tree_with_default_branch) do + branch = project_with_default_branch.repository.find_branch(project_with_default_branch.default_branch) + project_tree_path(project_with_default_branch, branch.target) + end + + before do + project_with_default_branch.repository.create_branch(other_ref, 'master') + end + + it 'redirects to tree view for the default branch' do + get :show, params: { namespace_id: project_with_default_branch.namespace, id: project_with_default_branch } + expect(response).to redirect_to(tree_with_default_branch) + end + end + end end describe "when project repository is disabled" do diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb index 70a4a3ec822..54a3fc57a5d 100644 --- a/spec/factories/issues.rb +++ b/spec/factories/issues.rb @@ -66,6 +66,11 @@ FactoryBot.define do end end + trait :requirement do + issue_type { :requirement } + association :work_item_type, :default, :requirement + end + trait :task do issue_type { :task } association :work_item_type, :default, :task diff --git a/spec/factories/packages/npm/metadata_cache.rb b/spec/factories/packages/npm/metadata_cache.rb new file mode 100644 index 00000000000..b06915bcb46 --- /dev/null +++ b/spec/factories/packages/npm/metadata_cache.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :npm_metadata_cache, class: 'Packages::Npm::MetadataCache' do + project + sequence(:package_name) { |n| "@#{project.root_namespace.path}/package-#{n}" } + file { 'unnamed' } + size { 100.kilobytes } + end +end diff --git a/spec/factories/project_hooks.rb b/spec/factories/project_hooks.rb index a7f562df92d..3e70b897df6 100644 --- a/spec/factories/project_hooks.rb +++ b/spec/factories/project_hooks.rb @@ -7,7 +7,7 @@ FactoryBot.define do project trait :url_variables do - url_variables { { 'abc' => 'supers3cret' } } + url_variables { { 'abc' => 'supers3cret', 'def' => 'foobar' } } end trait :token do diff --git a/spec/features/admin/users/user_spec.rb b/spec/features/admin/users/user_spec.rb index 71e2429c4fe..403fd49fc65 100644 --- a/spec/features/admin/users/user_spec.rb +++ b/spec/features/admin/users/user_spec.rb @@ -271,6 +271,36 @@ RSpec.describe 'Admin::Users::User', feature_category: :user_management do icon = first('[data-testid="incognito-icon"]') expect(icon).not_to be nil end + + context 'when viewing the confirm email warning', :js do + let_it_be(:another_user) { create(:user, :unconfirmed) } + + let(:warning_alert) { page.find(:css, '[data-testid="alert-warning"]') } + let(:expected_styling) { { 'pointer-events' => 'none', 'cursor' => 'default' } } + + context 'with an email that does not contain HTML' do + before do + subject + end + + it 'displays the warning alert including the email' do + expect(warning_alert.text).to include("Please check your email (#{another_user.email}) to verify") + end + end + + context 'with an email that contains HTML' do + let(:malicious_email) { "malicious@test.com<form><input/title='<script>alert(document.domain)</script>'>" } + let(:another_user) { create(:user, confirmed_at: nil, unconfirmed_email: malicious_email) } + + before do + subject + end + + it 'displays the impersonation alert, excludes email, and disables links' do + expect(warning_alert.text).to include("check your email (#{another_user.unconfirmed_email}) to verify") + end + end + end end context 'ending impersonation' do diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb index d990c57fbf1..e6e5a1f9894 100644 --- a/spec/features/users/login_spec.rb +++ b/spec/features/users/login_spec.rb @@ -208,17 +208,14 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ end describe 'with two-factor authentication', :js do - def enter_code(code) - if page.has_content?("Sign in via 2FA code") - click_on("Sign in via 2FA code") - enter_code(code) - else - fill_in 'user_otp_attempt', with: code - click_button 'Verify code' - end + def enter_code(code, only_two_factor_webauthn_enabled: false) + click_on("Sign in via 2FA code") if only_two_factor_webauthn_enabled + + fill_in 'user_otp_attempt', with: code + click_button 'Verify code' end - shared_examples_for 'can login with recovery codes' do + shared_examples_for 'can login with recovery codes' do |only_two_factor_webauthn_enabled: false| context 'using backup code' do let(:codes) { user.generate_otp_backup_codes! } @@ -235,7 +232,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ .to increment(:user_authenticated_counter) .and increment(:user_two_factor_authenticated_counter) - enter_code(codes.sample) + enter_code(codes.sample, only_two_factor_webauthn_enabled: only_two_factor_webauthn_enabled) expect(page).to have_current_path root_path, ignore_query: true end @@ -245,7 +242,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ .to increment(:user_authenticated_counter) .and increment(:user_two_factor_authenticated_counter) - expect { enter_code(codes.sample) } + expect { enter_code(codes.sample, only_two_factor_webauthn_enabled: only_two_factor_webauthn_enabled) } .to change { user.reload.otp_backup_codes.size }.by(-1) end @@ -256,13 +253,13 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ .and increment(:user_session_destroyed_counter) random_code = codes.delete(codes.sample) - expect { enter_code(random_code) } + expect { enter_code(random_code, only_two_factor_webauthn_enabled: only_two_factor_webauthn_enabled) } .to change { user.reload.otp_backup_codes.size }.by(-1) gitlab_sign_out gitlab_sign_in(user) - expect { enter_code(codes.sample) } + expect { enter_code(codes.sample, only_two_factor_webauthn_enabled: only_two_factor_webauthn_enabled) } .to change { user.reload.otp_backup_codes.size }.by(-1) end @@ -272,7 +269,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ .and increment(:user_two_factor_authenticated_counter) expect(ActiveSession).to receive(:cleanup).with(user).once.and_call_original - enter_code(codes.sample) + enter_code(codes.sample, only_two_factor_webauthn_enabled: only_two_factor_webauthn_enabled) end end @@ -287,7 +284,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ user.save!(touch: false) expect(user.reload.otp_backup_codes.size).to eq 9 - enter_code(code) + enter_code(code, only_two_factor_webauthn_enabled: only_two_factor_webauthn_enabled) expect(page).to have_content('Invalid two-factor code.') end end @@ -382,7 +379,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ context 'when user with only Webauthn enabled' do let(:user) { create(:user, :two_factor_via_webauthn, registrations_count: 1) } - include_examples 'can login with recovery codes' + include_examples 'can login with recovery codes', only_two_factor_webauthn_enabled: true end end diff --git a/spec/finders/environments/environment_names_finder_spec.rb b/spec/finders/environments/environment_names_finder_spec.rb index 438f9e9ea7c..c2336c59119 100644 --- a/spec/finders/environments/environment_names_finder_spec.rb +++ b/spec/finders/environments/environment_names_finder_spec.rb @@ -6,6 +6,7 @@ RSpec.describe Environments::EnvironmentNamesFinder do describe '#execute' do let!(:group) { create(:group) } let!(:public_project) { create(:project, :public, namespace: group) } + let_it_be_with_reload(:public_project_with_private_environments) { create(:project, :public) } let!(:private_project) { create(:project, :private, namespace: group) } let!(:user) { create(:user) } @@ -14,6 +15,11 @@ RSpec.describe Environments::EnvironmentNamesFinder do create(:environment, name: 'gprd', project: public_project) create(:environment, name: 'gprd', project: private_project) create(:environment, name: 'gcny', project: private_project) + create(:environment, name: 'gprivprd', project: public_project_with_private_environments) + create(:environment, name: 'gprivstg', project: public_project_with_private_environments) + + public_project_with_private_environments.update!(namespace: group) + public_project_with_private_environments.project_feature.update!(environments_access_level: Featurable::PRIVATE) end context 'using a group' do @@ -23,7 +29,7 @@ RSpec.describe Environments::EnvironmentNamesFinder do names = described_class.new(group, user).execute - expect(names).to eq(%w[gcny gprd gstg]) + expect(names).to eq(%w[gcny gprd gprivprd gprivstg gstg]) end end @@ -33,7 +39,7 @@ RSpec.describe Environments::EnvironmentNamesFinder do names = described_class.new(group, user).execute - expect(names).to eq(%w[gcny gprd gstg]) + expect(names).to eq(%w[gcny gprd gprivprd gprivstg gstg]) end end @@ -57,8 +63,18 @@ RSpec.describe Environments::EnvironmentNamesFinder do end end + context 'with a public project reporter which has private environments' do + it 'returns environment names for public projects' do + public_project_with_private_environments.add_reporter(user) + + names = described_class.new(group, user).execute + + expect(names).to eq(%w[gprd gprivprd gprivstg gstg]) + end + end + context 'with a group guest' do - it 'returns environment names for all public projects' do + it 'returns environment names for public projects' do group.add_guest(user) names = described_class.new(group, user).execute @@ -68,7 +84,7 @@ RSpec.describe Environments::EnvironmentNamesFinder do end context 'with a non-member' do - it 'returns environment names for all public projects' do + it 'returns environment names for only public projects with public environments' do names = described_class.new(group, user).execute expect(names).to eq(%w[gprd gstg]) @@ -76,7 +92,7 @@ RSpec.describe Environments::EnvironmentNamesFinder do end context 'without a user' do - it 'returns environment names for all public projects' do + it 'returns environment names for only public projects with public environments' do names = described_class.new(group).execute expect(names).to eq(%w[gprd gstg]) diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb index 792a14e3064..1255a882114 100644 --- a/spec/finders/notes_finder_spec.rb +++ b/spec/finders/notes_finder_spec.rb @@ -106,6 +106,26 @@ RSpec.describe NotesFinder do end end + context 'for notes on public issue in public project' do + let_it_be(:public_project) { create(:project, :public) } + let_it_be(:guest_member) { create(:user) } + let_it_be(:reporter_member) { create(:user) } + let_it_be(:guest_project_member) { create(:project_member, :guest, user: guest_member, project: public_project) } + let_it_be(:reporter_project_member) { create(:project_member, :reporter, user: reporter_member, project: public_project) } + let_it_be(:internal_note) { create(:note_on_issue, project: public_project, internal: true) } + let_it_be(:public_note) { create(:note_on_issue, project: public_project) } + + it 'shows all notes when the current_user has reporter access' do + notes = described_class.new(reporter_member, project: public_project).execute + expect(notes).to contain_exactly internal_note, public_note + end + + it 'shows only public notes when the current_user has guest access' do + notes = described_class.new(guest_member, project: public_project).execute + expect(notes).to contain_exactly public_note + end + end + context 'for target type' do let(:project) { create(:project, :repository) } let!(:note1) { create :note_on_issue, project: project } diff --git a/spec/frontend/diffs/store/actions_spec.js b/spec/frontend/diffs/store/actions_spec.js index 351e62fb6e0..b988472f947 100644 --- a/spec/frontend/diffs/store/actions_spec.js +++ b/spec/frontend/diffs/store/actions_spec.js @@ -1598,6 +1598,54 @@ describe('DiffsStoreActions', () => { ); }); + describe('rereadNoteHash', () => { + beforeEach(() => { + window.location.hash = 'note_123'; + }); + + it('dispatches setCurrentDiffFileIdFromNote if the hash is a note URL', () => { + window.location.hash = 'note_123'; + + return testAction( + diffActions.rereadNoteHash, + {}, + {}, + [], + [{ type: 'setCurrentDiffFileIdFromNote', payload: '123' }], + ); + }); + + it('dispatches fetchFileByFile if the app is in fileByFile mode', () => { + window.location.hash = 'note_123'; + + return testAction( + diffActions.rereadNoteHash, + {}, + { viewDiffsFileByFile: true }, + [], + [{ type: 'setCurrentDiffFileIdFromNote', payload: '123' }, { type: 'fetchFileByFile' }], + ); + }); + + it('does not try to fetch the diff file if the app is not in fileByFile mode', () => { + window.location.hash = 'note_123'; + + return testAction( + diffActions.rereadNoteHash, + {}, + { viewDiffsFileByFile: false }, + [], + [{ type: 'setCurrentDiffFileIdFromNote', payload: '123' }], + ); + }); + + it('does nothing if the hash is not a note URL', () => { + window.location.hash = 'abcdef1234567890'; + + return testAction(diffActions.rereadNoteHash, {}, {}, [], []); + }); + }); + describe('setCurrentDiffFileIdFromNote', () => { it('commits SET_CURRENT_DIFF_FILE', () => { const commit = jest.fn(); diff --git a/spec/frontend/projects/commits/components/author_select_spec.js b/spec/frontend/projects/commits/components/author_select_spec.js index ff1d860fd53..b06463e73a7 100644 --- a/spec/frontend/projects/commits/components/author_select_spec.js +++ b/spec/frontend/projects/commits/components/author_select_spec.js @@ -3,6 +3,7 @@ import { shallowMount } from '@vue/test-utils'; import Vue, { nextTick } from 'vue'; import Vuex from 'vuex'; import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; +import setWindowLocation from 'helpers/set_window_location_helper'; import * as urlUtility from '~/lib/utils/url_utility'; import AuthorSelect from '~/projects/commits/components/author_select.vue'; import { createStore } from '~/projects/commits/store'; @@ -64,39 +65,23 @@ describe('Author Select', () => { const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem); describe('user is searching via "filter by commit message"', () => { - it('disables dropdown container', async () => { - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ hasSearchParam: true }); + beforeEach(() => { + setWindowLocation(`?search=foo`); + createComponent(); + }); - await nextTick(); + it('does not disable dropdown container', () => { expect(findDropdownContainer().attributes('disabled')).toBeUndefined(); }); - it('has correct tooltip message', async () => { - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ hasSearchParam: true }); - - await nextTick(); + it('has correct tooltip message', () => { expect(findDropdownContainer().attributes('title')).toBe( 'Searching by both author and message is currently not supported.', ); }); - it('disables dropdown', async () => { - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ hasSearchParam: false }); - - await nextTick(); - expect(findDropdown().attributes('disabled')).toBeUndefined(); - }); - - it('hasSearchParam if user types a truthy string', () => { - wrapper.vm.setSearchParam('false'); - - expect(wrapper.vm.hasSearchParam).toBe(true); + it('disables dropdown', () => { + expect(findDropdown().attributes('disabled')).toBe('true'); }); }); @@ -106,9 +91,8 @@ describe('Author Select', () => { }); it('displays the current selected author', async () => { - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ currentAuthor }); + setWindowLocation(`?author=${currentAuthor}`); + createComponent(); await nextTick(); expect(findDropdown().attributes('text')).toBe(currentAuthor); @@ -146,12 +130,14 @@ describe('Author Select', () => { expect(findDropdownItems().at(0).text()).toBe('Any Author'); }); - it('displays the project authors', async () => { - await nextTick(); + it('displays the project authors', () => { expect(findDropdownItems()).toHaveLength(authors.length + 1); }); it('has the correct props', async () => { + setWindowLocation(`?author=${currentAuthor}`); + createComponent(); + const [{ avatar_url: avatarUrl, username }] = authors; const result = { avatarUrl, @@ -159,16 +145,11 @@ describe('Author Select', () => { isChecked: true, }; - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ currentAuthor }); - await nextTick(); expect(findDropdownItems().at(1).props()).toEqual(expect.objectContaining(result)); }); - it("display the author's name", async () => { - await nextTick(); + it("display the author's name", () => { expect(findDropdownItems().at(1).text()).toBe(currentAuthor); }); diff --git a/spec/frontend/repository/utils/ref_switcher_utils_spec.js b/spec/frontend/repository/utils/ref_switcher_utils_spec.js index 7f708f13eaa..220dbf17398 100644 --- a/spec/frontend/repository/utils/ref_switcher_utils_spec.js +++ b/spec/frontend/repository/utils/ref_switcher_utils_spec.js @@ -1,5 +1,6 @@ import { generateRefDestinationPath } from '~/repository/utils/ref_switcher_utils'; import setWindowLocation from 'helpers/set_window_location_helper'; +import { TEST_HOST } from 'spec/test_constants'; import { refWithSpecialCharMock, encodedRefWithSpecialCharMock } from '../mock_data'; const projectRootPath = 'root/Project1'; @@ -16,16 +17,38 @@ describe('generateRefDestinationPath', () => { ${`${projectRootPath}/-/blob/${currentRef}/dir1/test.js`} | ${`${projectRootPath}/-/blob/${selectedRef}/dir1/test.js`} ${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js`} | ${`${projectRootPath}/-/blob/${selectedRef}/dir1/dir2/test.js`} ${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${`${projectRootPath}/-/blob/${selectedRef}/dir1/dir2/test.js#L123`} - `('generates the correct destination path for $currentPath', ({ currentPath, result }) => { + `('generates the correct destination path for $currentPath', ({ currentPath, result }) => { setWindowLocation(currentPath); - expect(generateRefDestinationPath(projectRootPath, currentRef, selectedRef)).toBe(result); + expect(generateRefDestinationPath(projectRootPath, currentRef, selectedRef)).toBe( + `${TEST_HOST}/${result}`, + ); + }); + + describe('when using symbolic ref names', () => { + it.each` + currentPath | nextRef | result + ${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${'someHash'} | ${`${projectRootPath}/-/blob/someHash/dir1/dir2/test.js#L123`} + ${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/heads/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/blob/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=heads#L123`} + ${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/tags/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/blob/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=tags#L123`} + ${`${projectRootPath}/-/tree/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/heads/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/tree/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=heads#L123`} + ${`${projectRootPath}/-/tree/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/tags/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/tree/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=tags#L123`} + ${`${projectRootPath}/-/tree/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/heads/refs/heads/branchNameContainsPrefix'} | ${`${projectRootPath}/-/tree/refs/heads/branchNameContainsPrefix/dir1/dir2/test.js?ref_type=heads#L123`} + `( + 'generates the correct destination path for $currentPath with ref type when it can be extracted', + ({ currentPath, result, nextRef }) => { + setWindowLocation(currentPath); + expect(generateRefDestinationPath(projectRootPath, currentRef, nextRef)).toBe( + `${TEST_HOST}/${result}`, + ); + }, + ); }); it('encodes the selected ref', () => { const result = `${projectRootPath}/-/tree/${encodedRefWithSpecialCharMock}`; expect(generateRefDestinationPath(projectRootPath, currentRef, refWithSpecialCharMock)).toBe( - result, + `${TEST_HOST}/${result}`, ); }); }); diff --git a/spec/helpers/hooks_helper_spec.rb b/spec/helpers/hooks_helper_spec.rb index a6cfbfe86ca..d8fa64e099a 100644 --- a/spec/helpers/hooks_helper_spec.rb +++ b/spec/helpers/hooks_helper_spec.rb @@ -26,7 +26,7 @@ RSpec.describe HooksHelper do it 'returns proper data' do expect(subject).to match( url: project_hook.url, - url_variables: Gitlab::Json.dump([{ key: 'abc' }]) + url_variables: Gitlab::Json.dump([{ key: 'abc' }, { key: 'def' }]) ) end end diff --git a/spec/lib/banzai/filter/issuable_reference_expansion_filter_spec.rb b/spec/lib/banzai/filter/issuable_reference_expansion_filter_spec.rb index 80061539a0b..2b86a4f8cfc 100644 --- a/spec/lib/banzai/filter/issuable_reference_expansion_filter_spec.rb +++ b/spec/lib/banzai/filter/issuable_reference_expansion_filter_spec.rb @@ -202,7 +202,7 @@ RSpec.describe Banzai::Filter::IssuableReferenceExpansionFilter, feature_categor filter(link, context) end.count - expect(control_count).to eq 10 + expect(control_count).to eq 11 expect do filter("#{link} #{link2}", context) diff --git a/spec/lib/gitlab/background_migration/nullify_last_error_from_project_mirror_data_spec.rb b/spec/lib/gitlab/background_migration/nullify_last_error_from_project_mirror_data_spec.rb new file mode 100644 index 00000000000..62f908ed79b --- /dev/null +++ b/spec/lib/gitlab/background_migration/nullify_last_error_from_project_mirror_data_spec.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BackgroundMigration::NullifyLastErrorFromProjectMirrorData, feature_category: :source_code_management do # rubocop:disable Layout/LineLength + it 'nullifies last_error column on all rows' do + namespaces = table(:namespaces) + projects = table(:projects) + project_import_states = table(:project_mirror_data) + + group = namespaces.create!(name: 'gitlab', path: 'gitlab-org') + + project_namespace_1 = namespaces.create!(name: 'gitlab', path: 'gitlab-org') + project_namespace_2 = namespaces.create!(name: 'gitlab', path: 'gitlab-org') + project_namespace_3 = namespaces.create!(name: 'gitlab', path: 'gitlab-org') + + project_1 = projects.create!( + namespace_id: group.id, + project_namespace_id: project_namespace_1.id, + name: 'test1' + ) + project_2 = projects.create!( + namespace_id: group.id, + project_namespace_id: project_namespace_2.id, + name: 'test2' + ) + project_3 = projects.create!( + namespace_id: group.id, + project_namespace_id: project_namespace_3.id, + name: 'test3' + ) + + project_import_state_1 = project_import_states.create!( + project_id: project_1.id, + status: 0, + last_update_started_at: 1.hour.ago, + last_update_scheduled_at: 1.hour.ago, + last_update_at: 1.hour.ago, + last_successful_update_at: 2.days.ago, + last_error: '13:fetch remote: "fatal: unable to look up user:pass@gitlab.com (port 9418) (nodename nor servname provided, or not known)\n": exit status 128.', # rubocop:disable Layout/LineLength + correlation_id_value: SecureRandom.uuid, + jid: SecureRandom.uuid + ) + + project_import_states.create!( + project_id: project_2.id, + status: 1, + last_update_started_at: 1.hour.ago, + last_update_scheduled_at: 1.hour.ago, + last_update_at: 1.hour.ago, + last_successful_update_at: nil, + next_execution_timestamp: 1.day.from_now, + last_error: '', + correlation_id_value: SecureRandom.uuid, + jid: SecureRandom.uuid + ) + + project_import_state_3 = project_import_states.create!( + project_id: project_3.id, + status: 2, + last_update_started_at: 1.hour.ago, + last_update_scheduled_at: 1.hour.ago, + last_update_at: 1.hour.ago, + last_successful_update_at: 1.hour.ago, + next_execution_timestamp: 1.day.from_now, + last_error: nil, + correlation_id_value: SecureRandom.uuid, + jid: SecureRandom.uuid + ) + + migration = described_class.new( + start_id: project_import_state_1.id, + end_id: project_import_state_3.id, + batch_table: :project_mirror_data, + batch_column: :id, + sub_batch_size: 1, + pause_ms: 0, + connection: ApplicationRecord.connection + ) + + w_last_error_count = -> { project_import_states.where.not(last_error: nil).count } # rubocop:disable CodeReuse/ActiveRecord + expect { migration.perform }.to change(&w_last_error_count).from(2).to(0) + end +end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 8bdd4e89598..715749a4195 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -711,6 +711,7 @@ project: - packages - package_files - rpm_repository_files +- npm_metadata_caches - packages_cleanup_policy - alerting_setting - project_setting diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb index d885051b93b..e51e62d5f0a 100644 --- a/spec/lib/gitlab/regex_spec.rb +++ b/spec/lib/gitlab/regex_spec.rb @@ -1171,7 +1171,7 @@ RSpec.describe Gitlab::Regex, feature_category: :tooling do end context 'HTML comment lines' do - subject { described_class::MARKDOWN_HTML_COMMENT_LINE_REGEX } + subject { Gitlab::UntrustedRegexp.new(described_class::MARKDOWN_HTML_COMMENT_LINE_REGEX_UNTRUSTED, multiline: true) } let(:expected) { [['<!-- an HTML comment -->'], ['<!-- another HTML comment -->']] } let(:markdown) do @@ -1189,20 +1189,20 @@ RSpec.describe Gitlab::Regex, feature_category: :tooling do it { is_expected.to match(%(<!-- single line comment -->)) } it { is_expected.not_to match(%(<!--\nblock comment\n-->)) } it { is_expected.not_to match(%(must start in first column <!-- comment -->)) } - it { expect(markdown.scan(subject)).to eq expected } + it { expect(subject.scan(markdown)).to eq expected } end context 'HTML comment blocks' do - subject { described_class::MARKDOWN_HTML_COMMENT_BLOCK_REGEX } + subject { Gitlab::UntrustedRegexp.new(described_class::MARKDOWN_HTML_COMMENT_BLOCK_REGEX_UNTRUSTED, multiline: true) } - let(:expected) { %(<!-- the start of an HTML comment\n- [ ] list item commented out\n-->) } + let(:expected) { %(<!-- the start of an HTML comment\n- [ ] list item commented out\nmore text -->) } let(:markdown) do <<~MARKDOWN Regular text <!-- the start of an HTML comment - [ ] list item commented out - --> + more text --> MARKDOWN end diff --git a/spec/lib/gitlab/untrusted_regexp_spec.rb b/spec/lib/gitlab/untrusted_regexp_spec.rb index 270c4beec97..66675b20107 100644 --- a/spec/lib/gitlab/untrusted_regexp_spec.rb +++ b/spec/lib/gitlab/untrusted_regexp_spec.rb @@ -137,6 +137,38 @@ RSpec.describe Gitlab::UntrustedRegexp do end end + describe '#extract_named_group' do + let(:re) { described_class.new('(?P<name>\w+) (?P<age>\d+)|(?P<name_only>\w+)') } + let(:text) { 'Bob 40' } + + it 'returns values for both named groups' do + matched = re.scan(text).first + + expect(re.extract_named_group(:name, matched)).to eq 'Bob' + expect(re.extract_named_group(:age, matched)).to eq '40' + end + + it 'returns nil if there was no match for group' do + matched = re.scan('Bob').first + + expect(re.extract_named_group(:name, matched)).to be_nil + expect(re.extract_named_group(:age, matched)).to be_nil + expect(re.extract_named_group(:name_only, matched)).to eq 'Bob' + end + + it 'returns nil if match is nil' do + matched = '(?P<age>\d+)'.scan(text).first + + expect(re.extract_named_group(:age, matched)).to be_nil + end + + it 'raises if name is not a capture group' do + matched = re.scan(text).first + + expect { re.extract_named_group(:foo, matched) }.to raise_error('Invalid named capture group: foo') + end + end + describe '#match' do context 'when there are matches' do it 'returns a match object' do diff --git a/spec/lib/gitlab/url_sanitizer_spec.rb b/spec/lib/gitlab/url_sanitizer_spec.rb index 0ffbf5f81e7..c02cbef8328 100644 --- a/spec/lib/gitlab/url_sanitizer_spec.rb +++ b/spec/lib/gitlab/url_sanitizer_spec.rb @@ -10,29 +10,36 @@ RSpec.describe Gitlab::UrlSanitizer do # We want to try with multi-line content because is how error messages are formatted described_class.sanitize(%Q{ remote: Not Found - fatal: repository '#{url}' not found + fatal: repository `#{url}` not found }) end where(:input, :output) do - 'http://user:pass@test.com/root/repoC.git/' | 'http://*****:*****@test.com/root/repoC.git/' - 'https://user:pass@test.com/root/repoA.git/' | 'https://*****:*****@test.com/root/repoA.git/' - 'ssh://user@host.test/path/to/repo.git' | 'ssh://*****@host.test/path/to/repo.git' - - # git protocol does not support authentication but clean any details anyway - 'git://user:pass@host.test/path/to/repo.git' | 'git://*****:*****@host.test/path/to/repo.git' - 'git://host.test/path/to/repo.git' | 'git://host.test/path/to/repo.git' + # http(s), ssh, git, relative, and schemeless URLs should all be masked correctly + urls = ['http://', 'https://', 'ssh://', 'git://', '//', ''].flat_map do |protocol| + [ + ["#{protocol}test.com", "#{protocol}test.com"], + ["#{protocol}test.com/", "#{protocol}test.com/"], + ["#{protocol}test.com/path/to/repo.git", "#{protocol}test.com/path/to/repo.git"], + ["#{protocol}user@test.com", "#{protocol}*****@test.com"], + ["#{protocol}user:pass@test.com", "#{protocol}*****:*****@test.com"], + ["#{protocol}user:@test.com", "#{protocol}*****@test.com"], + ["#{protocol}:pass@test.com", "#{protocol}:*****@test.com"] + ] + end # SCP-style URLs are left unmodified - 'user@server:project.git' | 'user@server:project.git' - 'user:pass@server:project.git' | 'user:pass@server:project.git' + urls << ['user@server:project.git', 'user@server:project.git'] + urls << ['user:@server:project.git', 'user:@server:project.git'] + urls << [':pass@server:project.git', ':pass@server:project.git'] + urls << ['user:pass@server:project.git', 'user:pass@server:project.git'] # return an empty string for invalid URLs - 'ssh://' | '' + urls << ['ssh://', ''] end with_them do - it { expect(sanitize_url(input)).to include("repository '#{output}' not found") } + it { expect(sanitize_url(input)).to include("repository `#{output}` not found") } end end diff --git a/spec/lib/rouge/formatters/html_gitlab_spec.rb b/spec/lib/rouge/formatters/html_gitlab_spec.rb index 79bfdb262c0..6fc1b395fc8 100644 --- a/spec/lib/rouge/formatters/html_gitlab_spec.rb +++ b/spec/lib/rouge/formatters/html_gitlab_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Rouge::Formatters::HTMLGitlab do +RSpec.describe Rouge::Formatters::HTMLGitlab, feature_category: :source_code_management do describe '#format' do subject { described_class.format(tokens, **options) } @@ -67,5 +67,24 @@ RSpec.describe Rouge::Formatters::HTMLGitlab do is_expected.to include(%{<span class="unicode-bidi has-tooltip" data-toggle="tooltip" title="#{message}">}).exactly(4).times end end + + context 'when space characters and zero-width spaces are used' do + let(:lang) { 'ruby' } + let(:tokens) { lexer.lex(code, continue: false) } + + let(:code) do + <<~JS + def\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000hello + JS + end + + it 'replaces the space characters with spaces' do + is_expected.to eq( + "<span id=\"LC1\" class=\"line\" lang=\"ruby\">" \ + "<span class=\"k\">def</span><span class=\"err\"> </span><span class=\"n\">hello</span>" \ + "</span>" + ) + end + end end end diff --git a/spec/migrations/20230322145403_add_project_id_foreign_key_to_packages_npm_metadata_caches_spec.rb b/spec/migrations/20230322145403_add_project_id_foreign_key_to_packages_npm_metadata_caches_spec.rb new file mode 100644 index 00000000000..647c583aa39 --- /dev/null +++ b/spec/migrations/20230322145403_add_project_id_foreign_key_to_packages_npm_metadata_caches_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require_migration! + +RSpec.describe AddProjectIdForeignKeyToPackagesNpmMetadataCaches, + feature_category: :package_registry do + let(:table) { described_class::SOURCE_TABLE } + let(:column) { described_class::COLUMN } + let(:foreign_key) { -> { described_class.new.foreign_keys_for(table, column).first } } + + it 'creates and drops the foreign key' do + reversible_migration do |migration| + migration.before -> do + expect(foreign_key.call).to be(nil) + end + + migration.after -> do + expect(foreign_key.call).to have_attributes(column: column.to_s) + end + end + end +end diff --git a/spec/migrations/nullify_last_error_from_project_mirror_data_spec.rb b/spec/migrations/nullify_last_error_from_project_mirror_data_spec.rb new file mode 100644 index 00000000000..6c5679b674e --- /dev/null +++ b/spec/migrations/nullify_last_error_from_project_mirror_data_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require_migration! + +RSpec.describe NullifyLastErrorFromProjectMirrorData, feature_category: :source_code_management do + let(:migration) { described_class::MIGRATION } + + before do + migrate! + end + + describe '#up' do + it 'schedules background jobs for each batch of projects' do + expect(migration).to( + have_scheduled_batched_migration( + table_name: :project_mirror_data, + column_name: :id, + interval: described_class::INTERVAL, + batch_size: described_class::BATCH_SIZE, + sub_batch_size: described_class::SUB_BATCH_SIZE + ) + ) + end + end + + describe '#down' do + before do + schema_migrate_down! + end + + it 'deletes all batched migration records' do + expect(migration).not_to have_scheduled_batched_migration + end + end +end diff --git a/spec/models/concerns/taskable_spec.rb b/spec/models/concerns/taskable_spec.rb index 14f346f353b..20de8995d13 100644 --- a/spec/models/concerns/taskable_spec.rb +++ b/spec/models/concerns/taskable_spec.rb @@ -35,11 +35,7 @@ RSpec.describe Taskable, feature_category: :team_planning do TaskList::Item.new('- [ ]', 'First item'), TaskList::Item.new('- [x]', 'Second item'), TaskList::Item.new('* [x]', 'First item'), - TaskList::Item.new('* [ ]', 'Second item'), - TaskList::Item.new('+ [ ]', 'No-break space (U+00A0)'), - TaskList::Item.new('+ [ ]', 'Figure space (U+2007)'), - TaskList::Item.new('+ [ ]', 'Narrow no-break space (U+202F)'), - TaskList::Item.new('+ [ ]', 'Thin space (U+2009)') + TaskList::Item.new('* [ ]', 'Second item') ] end diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb index 8fd76d2a835..48dfaff74d8 100644 --- a/spec/models/hooks/web_hook_spec.rb +++ b/spec/models/hooks/web_hook_spec.rb @@ -242,6 +242,22 @@ RSpec.describe WebHook, feature_category: :integrations do expect(hook.url_variables).to eq({}) end + it 'resets url variables if url is changed and url variables are appended' do + hook.url = 'http://suspicious.example.com/{abc}/{foo}' + hook.url_variables = hook.url_variables.merge('foo' => 'bar') + + expect(hook).not_to be_valid + expect(hook.url_variables).to eq({}) + end + + it 'resets url variables if url is changed and url variables are removed' do + hook.url = 'http://suspicious.example.com/{abc}' + hook.url_variables = hook.url_variables.except("def") + + expect(hook).not_to be_valid + expect(hook.url_variables).to eq({}) + end + it 'does not reset url variables if both url and url variables are changed' do hook.url = 'http://example.com/{one}/{two}' hook.url_variables = { 'one' => 'foo', 'two' => 'bar' } @@ -249,6 +265,18 @@ RSpec.describe WebHook, feature_category: :integrations do expect(hook).to be_valid expect(hook.url_variables).to eq({ 'one' => 'foo', 'two' => 'bar' }) end + + context 'without url variables' do + subject(:hook) { build_stubbed(:project_hook, project: project, url: 'http://example.com') } + + it 'does not reset url variables' do + hook.url = 'http://example.com/{one}/{two}' + hook.url_variables = { 'one' => 'foo', 'two' => 'bar' } + + expect(hook).to be_valid + expect(hook.url_variables).to eq({ 'one' => 'foo', 'two' => 'bar' }) + end + end end it "only consider these branch filter strategies are valid" do diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index eaac88d2964..dec62815366 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -1773,6 +1773,36 @@ RSpec.describe Issue, feature_category: :team_planning do it 'raises error when feature is invalid' do expect { issue.issue_type_supports?(:unkown_feature) }.to raise_error(ArgumentError) end + + context 'when issue_type_uses_work_item_types_table feature flag is disabled' do + before do + stub_feature_flags(issue_type_uses_work_item_types_table: false) + end + + it 'uses the issue_type column' do + expect(issue).to receive(:issue_type).and_call_original + expect(issue).not_to receive(:work_item_type).and_call_original + + issue.issue_type_supports?(:assignee) + end + end + + context 'when issue_type_uses_work_item_types_table feature flag is enabled' do + it 'uses the work_item_types table' do + expect(issue).not_to receive(:issue_type).and_call_original + expect(issue).to receive(:work_item_type).and_call_original + + issue.issue_type_supports?(:assignee) + end + + context 'when the issue is not persisted' do + it 'uses the default work item type' do + non_persisted_issue = build(:issue) + + expect(non_persisted_issue.issue_type_supports?(:assignee)).to be_truthy + end + end + end end describe '#supports_time_tracking?' do @@ -1920,4 +1950,10 @@ RSpec.describe Issue, feature_category: :team_planning do end end end + + describe '#work_item_type_with_default' do + subject { Issue.new.work_item_type_with_default } + + it { is_expected.to eq(WorkItems::Type.default_by_type(::Issue::DEFAULT_ISSUE_TYPE)) } + end end diff --git a/spec/models/packages/npm/metadata_cache_spec.rb b/spec/models/packages/npm/metadata_cache_spec.rb new file mode 100644 index 00000000000..fdee0bedc5b --- /dev/null +++ b/spec/models/packages/npm/metadata_cache_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Packages::Npm::MetadataCache, type: :model, feature_category: :package_registry do + let_it_be(:npm_metadata_cache) { create(:npm_metadata_cache) } + + describe 'relationships' do + it { is_expected.to belong_to(:project).inverse_of(:npm_metadata_caches) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:file) } + it { is_expected.to validate_presence_of(:project) } + it { is_expected.to validate_presence_of(:size) } + + describe '#package_name' do + it { is_expected.to validate_presence_of(:package_name) } + it { is_expected.to validate_uniqueness_of(:package_name).scoped_to(:project_id) } + it { is_expected.to allow_value('my.app-11.07.2018').for(:package_name) } + it { is_expected.to allow_value('@group-1/package').for(:package_name) } + it { is_expected.to allow_value('@any-scope/package').for(:package_name) } + it { is_expected.to allow_value('unscoped-package').for(:package_name) } + it { is_expected.not_to allow_value('my(dom$$$ain)com.my-app').for(:package_name) } + it { is_expected.not_to allow_value('@inv@lid-scope/package').for(:package_name) } + it { is_expected.not_to allow_value('@scope/../../package').for(:package_name) } + it { is_expected.not_to allow_value('@scope%2e%2e%fpackage').for(:package_name) } + it { is_expected.not_to allow_value('@scope/sub/package').for(:package_name) } + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index d4020708918..5c387faec33 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -143,6 +143,7 @@ RSpec.describe Project, factory_default: :keep, feature_category: :projects do it { is_expected.to have_many(:package_files).class_name('Packages::PackageFile') } it { is_expected.to have_many(:rpm_repository_files).class_name('Packages::Rpm::RepositoryFile').inverse_of(:project).dependent(:destroy) } it { is_expected.to have_many(:debian_distributions).class_name('Packages::Debian::ProjectDistribution').dependent(:destroy) } + it { is_expected.to have_many(:npm_metadata_caches).class_name('Packages::Npm::MetadataCache') } it { is_expected.to have_one(:packages_cleanup_policy).class_name('Packages::Cleanup::Policy').inverse_of(:project) } it { is_expected.to have_many(:pipeline_artifacts).dependent(:restrict_with_error) } it { is_expected.to have_many(:terraform_states).class_name('Terraform::State').inverse_of(:project) } diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 3759539677a..50f425f4efe 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -727,6 +727,39 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do end end + describe 'read_prometheus', feature_category: :metrics do + using RSpec::Parameterized::TableSyntax + + before do + project.project_feature.update!(metrics_dashboard_access_level: ProjectFeature::ENABLED) + end + + let(:policy) { :read_prometheus } + + where(:project_visibility, :role, :allowed) do + :public | :anonymous | false + :public | :guest | false + :public | :reporter | true + :internal | :anonymous | false + :internal | :guest | false + :internal | :reporter | true + :private | :anonymous | false + :private | :guest | false + :private | :reporter | true + end + + with_them do + let(:current_user) { public_send(role) } + let(:project) { public_send("#{project_visibility}_project") } + + if params[:allowed] + it { is_expected.to be_allowed(policy) } + else + it { is_expected.not_to be_allowed(policy) } + end + end + end + describe 'update_max_artifacts_size' do context 'when no user' do let(:current_user) { anonymous } @@ -1002,7 +1035,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do let(:current_user) { guest } it { is_expected.to be_allowed(:metrics_dashboard) } - it { is_expected.to be_allowed(:read_prometheus) } + it { is_expected.to be_disallowed(:read_prometheus) } it { is_expected.to be_allowed(:read_deployment) } it { is_expected.to be_allowed(:read_metrics_user_starred_dashboard) } it { is_expected.to be_allowed(:create_metrics_user_starred_dashboard) } @@ -1012,7 +1045,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do let(:current_user) { anonymous } it { is_expected.to be_allowed(:metrics_dashboard) } - it { is_expected.to be_allowed(:read_prometheus) } + it { is_expected.to be_disallowed(:read_prometheus) } it { is_expected.to be_allowed(:read_deployment) } it { is_expected.to be_disallowed(:read_metrics_user_starred_dashboard) } it { is_expected.to be_disallowed(:create_metrics_user_starred_dashboard) } @@ -1038,12 +1071,14 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do let(:current_user) { guest } it { is_expected.to be_disallowed(:metrics_dashboard) } + it { is_expected.to be_disallowed(:read_prometheus) } end context 'with anonymous' do let(:current_user) { anonymous } it { is_expected.to be_disallowed(:metrics_dashboard) } + it { is_expected.to be_disallowed(:read_prometheus) } end end @@ -1066,7 +1101,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do let(:current_user) { guest } it { is_expected.to be_allowed(:metrics_dashboard) } - it { is_expected.to be_allowed(:read_prometheus) } + it { is_expected.to be_disallowed(:read_prometheus) } it { is_expected.to be_allowed(:read_deployment) } it { is_expected.to be_allowed(:read_metrics_user_starred_dashboard) } it { is_expected.to be_allowed(:create_metrics_user_starred_dashboard) } @@ -1076,6 +1111,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do let(:current_user) { anonymous } it { is_expected.to be_disallowed(:metrics_dashboard) } + it { is_expected.to be_disallowed(:read_prometheus) } end end end @@ -1098,12 +1134,14 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do let(:current_user) { guest } it { is_expected.to be_disallowed(:metrics_dashboard) } + it { is_expected.to be_disallowed(:read_prometheus) } end context 'with anonymous' do let(:current_user) { anonymous } it { is_expected.to be_disallowed(:metrics_dashboard) } + it { is_expected.to be_disallowed(:read_prometheus) } end end @@ -1122,12 +1160,14 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do let(:current_user) { guest } it { is_expected.to be_disallowed(:metrics_dashboard) } + it { is_expected.to be_disallowed(:read_prometheus) } end context 'with anonymous' do let(:current_user) { anonymous } it { is_expected.to be_disallowed(:metrics_dashboard) } + it { is_expected.to be_disallowed(:read_prometheus) } end end end @@ -2068,7 +2108,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do :public | ProjectFeature::ENABLED | :anonymous | true :public | ProjectFeature::PRIVATE | :maintainer | true :public | ProjectFeature::PRIVATE | :developer | true - :public | ProjectFeature::PRIVATE | :guest | true + :public | ProjectFeature::PRIVATE | :guest | false :public | ProjectFeature::PRIVATE | :anonymous | false :public | ProjectFeature::DISABLED | :maintainer | false :public | ProjectFeature::DISABLED | :developer | false @@ -2080,7 +2120,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do :internal | ProjectFeature::ENABLED | :anonymous | false :internal | ProjectFeature::PRIVATE | :maintainer | true :internal | ProjectFeature::PRIVATE | :developer | true - :internal | ProjectFeature::PRIVATE | :guest | true + :internal | ProjectFeature::PRIVATE | :guest | false :internal | ProjectFeature::PRIVATE | :anonymous | false :internal | ProjectFeature::DISABLED | :maintainer | false :internal | ProjectFeature::DISABLED | :developer | false diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index b146dda5030..8853eff0b3e 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -572,6 +572,22 @@ RSpec.describe API::Repositories, feature_category: :source_code_management do context 'when authenticated', 'as a developer' do it_behaves_like 'repository compare' do let(:current_user) { user } + + context 'when user does not have read access to the parent project' do + let_it_be(:group) { create(:group) } + let(:forked_project) { fork_project(project, current_user, repository: true, namespace: group) } + + before do + forked_project.add_guest(current_user) + end + + it 'returns 403 error' do + get api(route, current_user), params: { from: 'improve/awesome', to: 'feature', from_project_id: forked_project.id } + + expect(response).to have_gitlab_http_status(:forbidden) + expect(json_response['message']).to eq("403 Forbidden - You don't have access to this fork's parent project") + end + end end end diff --git a/spec/services/merge_requests/push_options_handler_service_spec.rb b/spec/services/merge_requests/push_options_handler_service_spec.rb index 7ca795ecd1a..49ec8b09939 100644 --- a/spec/services/merge_requests/push_options_handler_service_spec.rb +++ b/spec/services/merge_requests/push_options_handler_service_spec.rb @@ -861,6 +861,21 @@ RSpec.describe MergeRequests::PushOptionsHandlerService, feature_category: :sour end end + describe 'when user does not have access to target project' do + let(:push_options) { { create: true, target: 'my-branch' } } + let(:changes) { default_branch_changes } + + before do + allow(user1).to receive(:can?).with(:read_code, project).and_return(false) + end + + it 'records an error', :sidekiq_inline do + service.execute + + expect(service.errors).to eq(["User access was denied"]) + end + end + describe 'when MRs are not enabled' do let(:project) { create(:project, :public, :repository).tap { |pr| pr.add_developer(user1) } } let(:push_options) { { create: true } } diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb index 5736bf885be..b4250fcf04d 100644 --- a/spec/services/web_hook_service_spec.rb +++ b/spec/services/web_hook_service_spec.rb @@ -130,8 +130,8 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state, context 'there is userinfo' do before do project_hook.update!( - url: 'http://{one}:{two}@example.com', - url_variables: { 'one' => 'a', 'two' => 'b' } + url: 'http://{foo}:{bar}@example.com', + url_variables: { 'foo' => 'a', 'bar' => 'b' } ) stub_full_request('http://example.com', method: :post) end diff --git a/spec/views/explore/projects/page_out_of_bounds.html.haml_spec.rb b/spec/views/explore/projects/page_out_of_bounds.html.haml_spec.rb new file mode 100644 index 00000000000..1ace28be5b4 --- /dev/null +++ b/spec/views/explore/projects/page_out_of_bounds.html.haml_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'explore/projects/page_out_of_bounds.html.haml', feature_category: :projects do + let(:page_limit) { 10 } + let(:unsafe_param) { 'hacked_using_unsafe_param!' } + + before do + assign(:max_page_number, page_limit) + + controller.params[:action] = 'index' + controller.params[:host] = unsafe_param + controller.params[:protocol] = unsafe_param + controller.params[:sort] = 'name_asc' + end + + it 'removes unsafe params from the link' do + render + + href = "/explore/projects?page=#{page_limit}&sort=name_asc" + button_text = format(_("Back to page %{number}"), number: page_limit) + expect(rendered).to have_link(button_text, href: href) + expect(rendered).not_to include(unsafe_param) + end +end |