diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-14 18:09:43 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-14 18:09:43 +0300 |
commit | 7a33080fff9a735cbe77968d67b13ffa92c0ffae (patch) | |
tree | 71a880649c8d3e551ec6bd94e93d08a9a9b5bde7 /spec | |
parent | 9223573b85bcfdd21953f52e0d2c5cb587e366a1 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
17 files changed, 961 insertions, 82 deletions
diff --git a/spec/features/projects/settings/branch_rules_settings_spec.rb b/spec/features/projects/settings/branch_rules_settings_spec.rb index a6ecfa2d231..5ef80521401 100644 --- a/spec/features/projects/settings/branch_rules_settings_spec.rb +++ b/spec/features/projects/settings/branch_rules_settings_spec.rb @@ -45,14 +45,5 @@ RSpec.describe 'Projects > Settings > Repository > Branch rules settings', featu expect(page).to have_content('Branch rules') end end - - context 'branch_rules feature flag disabled' do - it 'does not render branch rules content' do - stub_feature_flags(branch_rules: false) - request - - expect(page).to have_gitlab_http_status(:not_found) - end - end end end diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb index a801f633882..2439e624dd6 100644 --- a/spec/features/projects/settings/repository_settings_spec.rb +++ b/spec/features/projects/settings/repository_settings_spec.rb @@ -10,7 +10,6 @@ RSpec.describe 'Projects > Settings > Repository settings', feature_category: :g let(:role) { :developer } before do - stub_feature_flags(branch_rules: false) stub_feature_flags(mirror_only_branches_match_regex: false) project.add_role(user, role) sign_in(user) @@ -43,15 +42,7 @@ RSpec.describe 'Projects > Settings > Repository settings', feature_category: :g end context 'Branch rules', :js do - context 'branch_rules feature flag disabled', :js do - it 'does not render branch rules settings' do - visit project_settings_repository_path(project) - expect(page).not_to have_content('Branch rules') - end - end - it 'renders branch rules settings' do - stub_feature_flags(branch_rules: true) visit project_settings_repository_path(project) expect(page).to have_content('Branch rules') end diff --git a/spec/features/users/signup_spec.rb b/spec/features/users/signup_spec.rb index ccecd8bfcad..850dd0bbc5d 100644 --- a/spec/features/users/signup_spec.rb +++ b/spec/features/users/signup_spec.rb @@ -3,10 +3,8 @@ require 'spec_helper' RSpec.shared_examples 'Signup name validation' do |field, max_length, label| - flag_values = [true, false] - flag_values.each do |val| + shared_examples 'signup validation' do before do - stub_feature_flags(restyle_login_page: val) visit new_user_registration_path end @@ -42,6 +40,18 @@ RSpec.shared_examples 'Signup name validation' do |field, max_length, label| end end end + + include_examples 'signup validation' + + # Inline `shared_example 'signup validation'` again after feature flag + # `restyle_login_page` was removed. + context 'with feature flag restyle_login_page disabled' do + before do + stub_feature_flags(restyle_login_page: false) + end + + include_examples 'signup validation' + end end RSpec.describe 'Signup', :js, feature_category: :user_profile do @@ -49,25 +59,32 @@ RSpec.describe 'Signup', :js, feature_category: :user_profile do let(:new_user) { build_stubbed(:user) } - def fill_in_signup_form - fill_in 'new_user_username', with: new_user.username - fill_in 'new_user_email', with: new_user.email - fill_in 'new_user_first_name', with: new_user.first_name - fill_in 'new_user_last_name', with: new_user.last_name - fill_in 'new_user_password', with: new_user.password + let(:terms_text) do + <<~TEXT.squish + By clicking Register or registering through a third party you accept the + Terms of Use and acknowledge the Privacy Policy and Cookie Policy + TEXT end - def confirm_email - new_user_token = User.find_by_email(new_user.email).confirmation_token + shared_examples 'signup process' do + def fill_in_signup_form + fill_in 'new_user_username', with: new_user.username + fill_in 'new_user_email', with: new_user.email + fill_in 'new_user_first_name', with: new_user.first_name + fill_in 'new_user_last_name', with: new_user.last_name + fill_in 'new_user_password', with: new_user.password - visit user_confirmation_path(confirmation_token: new_user_token) - end + wait_for_all_requests + end + + def confirm_email + new_user_token = User.find_by_email(new_user.email).confirmation_token + + visit user_confirmation_path(confirmation_token: new_user_token) + end - flag_values = [true, false] - flag_values.each do |val| before do stub_feature_flags(arkose_labs_signup_challenge: false) - stub_feature_flags(restyle_login_page: val) stub_application_setting(require_admin_approval_after_user_signup: false) end @@ -162,7 +179,8 @@ RSpec.describe 'Signup', :js, feature_category: :user_profile do expect(page).to have_content("Invalid input, please avoid emojis") end - it 'shows a pending message if the username availability is being fetched' do + it 'shows a pending message if the username availability is being fetched', + quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/31484' do fill_in 'new_user_username', with: 'new-user' expect(find('.username > .validation-pending')).not_to have_css '.hide' @@ -263,7 +281,10 @@ RSpec.describe 'Signup', :js, feature_category: :user_profile do expect { click_button 'Register' }.to change { User.count }.by(1) expect(page).to have_current_path new_user_session_path, ignore_query: true - expect(page).to have_content("You have signed up successfully. However, we could not sign you in because your account is awaiting approval from your GitLab administrator") + expect(page).to have_content(<<~TEXT.squish) + You have signed up successfully. However, we could not sign you in + because your account is awaiting approval from your GitLab administrator + TEXT end end end @@ -305,13 +326,26 @@ RSpec.describe 'Signup', :js, feature_category: :user_profile do it 'renders text that the user confirms terms by signing in' do visit new_user_registration_path - expect(page).to have_content(/By clicking Register, I agree that I have read and accepted the Terms of Use and Privacy Policy/) + expect(page).to have_content(terms_text) fill_in_signup_form click_button 'Register' - expect(page).to have_current_path users_sign_up_welcome_path, ignore_query: true + expect(page).to have_current_path(users_sign_up_welcome_path), ignore_query: true + visit new_project_path + + select 'Software Developer', from: 'user_role' + click_button 'Get started!' + + created_user = User.find_by_username(new_user.username) + + expect(created_user.software_developer_role?).to be_truthy + expect(created_user.setup_for_company).to be_nil + expect(page).to have_current_path(new_project_path) end + + it_behaves_like 'Signup name validation', 'new_user_first_name', 127, 'First name' + it_behaves_like 'Signup name validation', 'new_user_last_name', 127, 'Last name' end context 'when reCAPTCHA and invisible captcha are enabled' do @@ -337,7 +371,8 @@ RSpec.describe 'Signup', :js, feature_category: :user_profile do expect { click_button 'Register' }.not_to change { User.count } expect(page).to have_content(_('There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.')) - expect(page).to have_content("Minimum length is #{Gitlab::CurrentSettings.minimum_password_length} characters") + expect(page).to have_content( + "Minimum length is #{Gitlab::CurrentSettings.minimum_password_length} characters") end end @@ -357,7 +392,6 @@ RSpec.describe 'Signup', :js, feature_category: :user_profile do visit new_user_registration_path fill_in_signup_form - wait_for_all_requests click_button 'Register' @@ -393,34 +427,22 @@ RSpec.describe 'Signup', :js, feature_category: :user_profile do end end - context 'when terms are enforced' do - before do - enforce_terms - end - - it 'renders text that the user confirms terms by signing in' do - visit new_user_registration_path - - expect(page).to have_content(/By clicking Register, I agree that I have read and accepted the Terms of Use and Privacy Policy/) - - fill_in_signup_form - click_button 'Register' - - visit new_project_path - - expect(page).to have_current_path(users_sign_up_welcome_path) + include_examples 'signup process' - select 'Software Developer', from: 'user_role' - click_button 'Get started!' - - created_user = User.find_by_username(new_user.username) + # Inline `shared_example 'signup process'` again after feature flag + # `restyle_login_page` was removed. + context 'with feature flag restyle_login_page disabled' do + let(:terms_text) do + <<~TEXT.squish + By clicking Register, I agree that I have read and accepted the Terms of + Use and Privacy Policy + TEXT + end - expect(created_user.software_developer_role?).to be_truthy - expect(created_user.setup_for_company).to be_nil - expect(page).to have_current_path(new_project_path) + before do + stub_feature_flags(restyle_login_page: false) end - it_behaves_like 'Signup name validation', 'new_user_first_name', 127, 'First name' - it_behaves_like 'Signup name validation', 'new_user_last_name', 127, 'Last name' + include_examples 'signup process' end end diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/pipelines.json b/spec/fixtures/api/schemas/public_api/v4/packages/pipelines.json new file mode 100644 index 00000000000..3432503212a --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/packages/pipelines.json @@ -0,0 +1,6 @@ +{ + "type": "array", + "items": { + "$ref": "../pipeline.json" + } +} diff --git a/spec/graphql/mutations/users/set_namespace_commit_email_spec.rb b/spec/graphql/mutations/users/set_namespace_commit_email_spec.rb new file mode 100644 index 00000000000..6d8e15ac791 --- /dev/null +++ b/spec/graphql/mutations/users/set_namespace_commit_email_spec.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::Users::SetNamespaceCommitEmail, feature_category: :user_profile do + include GraphqlHelpers + + let(:current_user) { create(:user) } + let(:group) { create(:group) } + let(:email) { create(:email, user: current_user) } + let(:input) { {} } + let(:namespace_id) { group.to_global_id } + let(:email_id) { email.to_global_id } + + shared_examples 'success' do + it 'creates namespace commit email with correct values' do + expect(resolve_mutation[:namespace_commit_email]) + .to have_attributes({ namespace_id: namespace_id.model_id.to_i, email_id: email_id.model_id.to_i }) + end + end + + describe '#resolve' do + subject(:resolve_mutation) do + described_class.new(object: nil, context: { current_user: current_user }, field: nil).resolve( + namespace_id: namespace_id, + email_id: email_id + ) + end + + context 'when current_user does not have permission' do + it 'raises an error' do + expect { resolve_mutation }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + .with_message(Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR) + end + end + + context 'when the user has permission' do + before do + group.add_reporter(current_user) + end + + context 'when the email does not belong to the target user' do + let(:email_id) { create(:email).to_global_id } + + it 'returns the validation error' do + expect(resolve_mutation[:errors]).to contain_exactly("Email must be provided.") + end + end + + context 'when namespace is a group' do + it_behaves_like 'success' + end + + context 'when namespace is a user' do + let(:namespace_id) { current_user.namespace.to_global_id } + + it_behaves_like 'success' + end + + context 'when namespace is a project' do + let_it_be(:project) { create(:project) } + + let(:namespace_id) { project.project_namespace.to_global_id } + + before do + project.add_reporter(current_user) + end + + it_behaves_like 'success' + end + end + end + + specify { expect(described_class).to require_graphql_authorizations(:read_namespace) } +end diff --git a/spec/graphql/resolvers/blobs_resolver_spec.rb b/spec/graphql/resolvers/blobs_resolver_spec.rb index 26eb6dc0abe..0d725f00d43 100644 --- a/spec/graphql/resolvers/blobs_resolver_spec.rb +++ b/spec/graphql/resolvers/blobs_resolver_spec.rb @@ -2,8 +2,9 @@ require 'spec_helper' -RSpec.describe Resolvers::BlobsResolver do +RSpec.describe Resolvers::BlobsResolver, feature_category: :source_code_management do include GraphqlHelpers + include RepoHelpers describe '.resolver_complexity' do it 'adds one per path being resolved' do @@ -59,15 +60,89 @@ RSpec.describe Resolvers::BlobsResolver do end end - context 'specifying a different ref' do + context 'when specifying a branch ref' do let(:ref) { 'add-pdf-file' } + let(:args) { { paths: paths, ref: ref, ref_type: ref_type } } let(:paths) { ['files/pdf/test.pdf', 'README.md'] } - it 'returns the specified blobs for that ref' do - is_expected.to contain_exactly( - have_attributes(path: 'files/pdf/test.pdf'), - have_attributes(path: 'README.md') - ) + context 'and no ref_type is specified' do + let(:ref_type) { nil } + + it 'returns the specified blobs for that ref' do + is_expected.to contain_exactly( + have_attributes(path: 'files/pdf/test.pdf'), + have_attributes(path: 'README.md') + ) + end + + context 'and a tag with the same name exists' do + let(:ref) { SecureRandom.uuid } + + before do + project.repository.create_branch(ref) + create_file_in_repo(project, ref, ref, 'branch_file', 'Test file', commit_message: 'Add new content') + project.repository.add_tag(project.owner, sample_commit.id, ref) + end + + it 'returns the specified blobs for the tag' do + is_expected.to contain_exactly( + have_attributes(path: 'README.md') + ) + end + end + end + + context 'and ref_type is for branches' do + let(:args) { { paths: paths, ref: ref, ref_type: 'heads' } } + + it 'returns nothing' do + is_expected.to contain_exactly( + have_attributes(path: 'files/pdf/test.pdf'), + have_attributes(path: 'README.md') + ) + end + end + + context 'and ref_type is for tags' do + let(:args) { { paths: paths, ref: ref, ref_type: 'tags' } } + + it 'returns nothing' do + is_expected.to be_empty + end + end + end + + context 'when specifying a tag ref' do + let(:ref) { 'v1.0.0' } + + let(:args) { { paths: paths, ref: ref, ref_type: ref_type } } + + context 'and no ref_type is specified' do + let(:ref_type) { nil } + + it 'returns the specified blobs for that ref' do + is_expected.to contain_exactly( + have_attributes(path: 'README.md') + ) + end + end + + context 'and ref_type is for tags' do + let(:ref_type) { 'tags' } + + it 'returns the specified blobs for that ref' do + is_expected.to contain_exactly( + have_attributes(path: 'README.md') + ) + end + end + + context 'and ref_type is for branches' do + let(:ref_type) { 'heads' } + + it 'returns nothing' do + is_expected.to be_empty + end end end diff --git a/spec/graphql/resolvers/last_commit_resolver_spec.rb b/spec/graphql/resolvers/last_commit_resolver_spec.rb index 5ac6ad59864..82bbdd4487c 100644 --- a/spec/graphql/resolvers/last_commit_resolver_spec.rb +++ b/spec/graphql/resolvers/last_commit_resolver_spec.rb @@ -61,5 +61,29 @@ RSpec.describe Resolvers::LastCommitResolver do expect(commit).to be_nil end end + + context 'when the ref is ambiguous' do + let(:ambiguous_ref) { 'v1.0.0' } + + before do + project.repository.create_branch(ambiguous_ref) + end + + context 'when tree is for a tag' do + let(:tree) { repository.tree(ambiguous_ref, ref_type: 'tags') } + + it 'resolves commit' do + expect(commit.id).to eq(repository.find_tag(ambiguous_ref).dereferenced_target.id) + end + end + + context 'when tree is for a branch' do + let(:tree) { repository.tree(ambiguous_ref, ref_type: 'heads') } + + it 'resolves commit' do + expect(commit.id).to eq(repository.find_branch(ambiguous_ref).target) + end + end + end end end diff --git a/spec/lib/extracts_ref_spec.rb b/spec/lib/extracts_ref_spec.rb index 93a09bf5a0a..ac403ad642a 100644 --- a/spec/lib/extracts_ref_spec.rb +++ b/spec/lib/extracts_ref_spec.rb @@ -57,5 +57,64 @@ RSpec.describe ExtractsRef do end end + describe '#ref_type' do + let(:params) { ActionController::Parameters.new(ref_type: 'heads') } + + it 'delegates to .ref_type' do + expect(described_class).to receive(:ref_type).with('heads') + ref_type + end + end + + describe '.ref_type' do + subject { described_class.ref_type(ref_type) } + + context 'when ref_type is nil' do + let(:ref_type) { nil } + + it { is_expected.to eq(nil) } + end + + context 'when ref_type is heads' do + let(:ref_type) { 'heads' } + + it { is_expected.to eq('heads') } + end + + context 'when ref_type is tags' do + let(:ref_type) { 'tags' } + + it { is_expected.to eq('tags') } + end + + context 'when ref_type is invalid' do + let(:ref_type) { 'invalid' } + + it { is_expected.to eq(nil) } + end + end + + describe '.qualify_ref' do + subject { described_class.qualify_ref(ref, ref_type) } + + context 'when ref_type is nil' do + let(:ref_type) { nil } + + it { is_expected.to eq(ref) } + end + + context 'when ref_type valid' do + let(:ref_type) { 'heads' } + + it { is_expected.to eq("refs/#{ref_type}/#{ref}") } + end + + context 'when ref_type is invalid' do + let(:ref_type) { 'invalid' } + + it { is_expected.to eq(ref) } + end + end + it_behaves_like 'extracts refs' end diff --git a/spec/lib/gitlab/pagination/cursor_based_keyset_spec.rb b/spec/lib/gitlab/pagination/cursor_based_keyset_spec.rb index dc62fcb4478..7cee65c13f7 100644 --- a/spec/lib/gitlab/pagination/cursor_based_keyset_spec.rb +++ b/spec/lib/gitlab/pagination/cursor_based_keyset_spec.rb @@ -14,6 +14,10 @@ RSpec.describe Gitlab::Pagination::CursorBasedKeyset do expect(subject.available_for_type?(Ci::Build.all)).to be_truthy end + it 'returns true for Packages::BuildInfo' do + expect(subject.available_for_type?(Packages::BuildInfo.all)).to be_truthy + end + it 'return false for other types of relations' do expect(subject.available_for_type?(User.all)).to be_falsey end @@ -56,6 +60,7 @@ RSpec.describe Gitlab::Pagination::CursorBasedKeyset do it 'return false for other types of relations' do expect(subject.available?(cursor_based_request_context, User.all)).to be_falsey expect(subject.available?(cursor_based_request_context, Ci::Build.all)).to be_falsey + expect(subject.available?(cursor_based_request_context, Packages::BuildInfo.all)).to be_falsey end end @@ -70,6 +75,10 @@ RSpec.describe Gitlab::Pagination::CursorBasedKeyset do it 'returns true for AuditEvent' do expect(subject.available?(cursor_based_request_context, AuditEvent.all)).to be_truthy end + + it 'returns true for Packages::BuildInfo' do + expect(subject.available?(cursor_based_request_context, Packages::BuildInfo.all)).to be_truthy + end end context 'with other order-by columns' do diff --git a/spec/lib/gitlab/project_authorizations_spec.rb b/spec/lib/gitlab/project_authorizations_spec.rb index b076bb65fb5..f3dcdfe2a9d 100644 --- a/spec/lib/gitlab/project_authorizations_spec.rb +++ b/spec/lib/gitlab/project_authorizations_spec.rb @@ -9,8 +9,10 @@ RSpec.describe Gitlab::ProjectAuthorizations, feature_category: :system_access d end end + let(:service) { described_class.new(user) } + subject(:authorizations) do - described_class.new(user).calculate + service.calculate end # Inline this shared example while cleaning up feature flag linear_project_authorization @@ -421,9 +423,53 @@ RSpec.describe Gitlab::ProjectAuthorizations, feature_category: :system_access d end end - context 'when feature_flag linear_project_authorization_is disabled' do + context 'it compares values for correctness' do + let_it_be(:user) { create(:user) } + + context 'when values returned by the queries are the same' do + it 'logs a message indicating that the values are the same' do + expect(Gitlab::AppJsonLogger).to receive(:info).with(event: 'linear_authorized_projects_check', + user_id: user.id, + matching_results: true) + service.calculate + end + end + + context 'when values returned by queries are diffrent' do + before do + create(:project_authorization) + allow(service).to receive(:calculate_with_linear_query).and_return(ProjectAuthorization.all) + end + + it 'logs a message indicating that the values are different' do + expect(Gitlab::AppJsonLogger).to receive(:warn).with(event: 'linear_authorized_projects_check', + user_id: user.id, + matching_results: false) + service.calculate + end + end + end + + context 'when feature_flag linear_project_authorization is disabled' do + before do + stub_feature_flags(linear_project_authorization: false) + end + + it_behaves_like 'project authorizations' + end + + context 'when feature_flag compare_project_authorization_linear_cte is disabled' do + before do + stub_feature_flags(compare_project_authorization_linear_cte: false) + end + + it_behaves_like 'project authorizations' + end + + context 'when feature_flag linear_project_authorization and compare_project_authorization_linear_cte are disabled' do before do stub_feature_flags(linear_project_authorization: false) + stub_feature_flags(compare_project_authorization_linear_cte: false) end it_behaves_like 'project authorizations' diff --git a/spec/policies/user_policy_spec.rb b/spec/policies/user_policy_spec.rb index 94b7e295167..9a2caeb7435 100644 --- a/spec/policies/user_policy_spec.rb +++ b/spec/policies/user_policy_spec.rb @@ -253,10 +253,12 @@ RSpec.describe UserPolicy do context 'when admin mode is enabled', :enable_admin_mode do it { is_expected.to be_allowed(:read_user_email_address) } + it { is_expected.to be_allowed(:admin_user_email_address) } end context 'when admin mode is disabled' do it { is_expected.not_to be_allowed(:read_user_email_address) } + it { is_expected.not_to be_allowed(:admin_user_email_address) } end end @@ -265,10 +267,12 @@ RSpec.describe UserPolicy do subject { described_class.new(current_user, current_user) } it { is_expected.to be_allowed(:read_user_email_address) } + it { is_expected.to be_allowed(:admin_user_email_address) } end context "requesting a different user's" do it { is_expected.not_to be_allowed(:read_user_email_address) } + it { is_expected.not_to be_allowed(:admin_user_email_address) } end end end diff --git a/spec/presenters/blob_presenter_spec.rb b/spec/presenters/blob_presenter_spec.rb index f10150b819a..e776716bd2d 100644 --- a/spec/presenters/blob_presenter_spec.rb +++ b/spec/presenters/blob_presenter_spec.rb @@ -31,6 +31,32 @@ RSpec.describe BlobPresenter do it { expect(presenter.replace_path).to eq("/#{project.full_path}/-/update/#{blob.commit_id}/#{blob.path}") } end + context 'when blob has ref_type' do + before do + blob.ref_type = 'heads' + end + + describe '#web_url' do + it { expect(presenter.web_url).to eq("http://localhost/#{project.full_path}/-/blob/#{blob.commit_id}/#{blob.path}?ref_type=heads") } + end + + describe '#web_path' do + it { expect(presenter.web_path).to eq("/#{project.full_path}/-/blob/#{blob.commit_id}/#{blob.path}?ref_type=heads") } + end + + describe '#edit_blob_path' do + it { expect(presenter.edit_blob_path).to eq("/#{project.full_path}/-/edit/#{blob.commit_id}/#{blob.path}?ref_type=heads") } + end + + describe '#raw_path' do + it { expect(presenter.raw_path).to eq("/#{project.full_path}/-/raw/#{blob.commit_id}/#{blob.path}?ref_type=heads") } + end + + describe '#replace_path' do + it { expect(presenter.replace_path).to eq("/#{project.full_path}/-/update/#{blob.commit_id}/#{blob.path}?ref_type=heads") } + end + end + describe '#can_current_user_push_to_branch' do let(:branch_exists) { true } diff --git a/spec/presenters/tree_entry_presenter_spec.rb b/spec/presenters/tree_entry_presenter_spec.rb index de84f36c5e6..0abf372b704 100644 --- a/spec/presenters/tree_entry_presenter_spec.rb +++ b/spec/presenters/tree_entry_presenter_spec.rb @@ -17,4 +17,20 @@ RSpec.describe TreeEntryPresenter do describe '#web_path' do it { expect(presenter.web_path).to eq("/#{project.full_path}/-/tree/#{tree.commit_id}/#{tree.path}") } end + + context 'when blob has ref_type' do + before do + tree.ref_type = 'heads' + end + + describe '.web_url' do + it { expect(presenter.web_url).to eq("http://localhost/#{project.full_path}/-/tree/#{tree.commit_id}/#{tree.path}?ref_type=heads") } + end + + describe '#web_path' do + it { + expect(presenter.web_path).to eq("/#{project.full_path}/-/tree/#{tree.commit_id}/#{tree.path}?ref_type=heads") + } + end + end end diff --git a/spec/requests/api/graphql/users/set_namespace_commit_email_spec.rb b/spec/requests/api/graphql/users/set_namespace_commit_email_spec.rb new file mode 100644 index 00000000000..1db6f83ce4f --- /dev/null +++ b/spec/requests/api/graphql/users/set_namespace_commit_email_spec.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Setting namespace commit email', feature_category: :user_profile do + include GraphqlHelpers + + let(:current_user) { create(:user) } + let(:group) { create(:group, :public) } + let(:email) { create(:email, :confirmed, user: current_user) } + let(:input) { {} } + let(:namespace_id) { group.to_global_id } + let(:email_id) { email.to_global_id } + + let(:resource_or_permission_error) do + "The resource that you are attempting to access does not exist or you don't have permission to perform this action" + end + + let(:mutation) do + variables = { + namespace_id: namespace_id, + email_id: email_id + } + graphql_mutation(:user_set_namespace_commit_email, variables.merge(input), + <<-QL.strip_heredoc + namespaceCommitEmail { + email { + id + } + } + errors + QL + ) + end + + def mutation_response + graphql_mutation_response(:user_set_namespace_commit_email) + end + + shared_examples 'success' do + it 'creates a namespace commit email' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(mutation_response.dig('namespaceCommitEmail', 'email', 'id')).to eq(email.to_global_id.to_s) + expect(graphql_errors).to be_nil + end + end + + before do + group.add_reporter(current_user) + end + + context 'when current_user is nil' do + it 'returns the top level error' do + post_graphql_mutation(mutation, current_user: nil) + + expect(graphql_errors.first).to match a_hash_including( + 'message' => resource_or_permission_error) + end + end + + context 'when the user cannot access the namespace' do + let(:namespace_id) { create(:group).to_global_id } + + it 'returns the top level error' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(graphql_errors).not_to be_empty + expect(graphql_errors.first).to match a_hash_including( + 'message' => resource_or_permission_error) + end + end + + context 'when the service returns an error' do + let(:email_id) { create(:email).to_global_id } + + it 'returns the error' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(mutation_response['errors']).to contain_exactly("Email must be provided.") + expect(mutation_response['namespaceCommitEmail']).to be_nil + end + end + + context 'when namespace is a group' do + it_behaves_like 'success' + end + + context 'when namespace is a user' do + let(:namespace_id) { current_user.namespace.to_global_id } + + it_behaves_like 'success' + end + + context 'when namespace is a project' do + let_it_be(:project) { create(:project) } + + let(:namespace_id) { project.project_namespace.to_global_id } + + before do + project.add_reporter(current_user) + end + + it_behaves_like 'success' + end +end diff --git a/spec/requests/api/project_packages_spec.rb b/spec/requests/api/project_packages_spec.rb index c003ae9cd48..b84b7e9c52d 100644 --- a/spec/requests/api/project_packages_spec.rb +++ b/spec/requests/api/project_packages_spec.rb @@ -3,9 +3,11 @@ require 'spec_helper' RSpec.describe API::ProjectPackages, feature_category: :package_registry do - let_it_be(:project) { create(:project, :public) } + using RSpec::Parameterized::TableSyntax - let(:user) { create(:user) } + let_it_be_with_reload(:project) { create(:project, :public) } + + let_it_be(:user) { create(:user) } let!(:package1) { create(:npm_package, :last_downloaded_at, project: project, version: '3.1.0', name: "@#{project.root_namespace.path}/foo1") } let(:package_url) { "/projects/#{project.id}/packages/#{package1.id}" } let!(:package2) { create(:nuget_package, project: project, version: '2.0.4') } @@ -101,7 +103,7 @@ RSpec.describe API::ProjectPackages, feature_category: :package_registry do end context 'project is private' do - let(:project) { create(:project, :private) } + let_it_be(:project) { create(:project, :private) } context 'for unauthenticated user' do it_behaves_like 'rejects packages access', :project, :no_type, :not_found @@ -235,7 +237,7 @@ RSpec.describe API::ProjectPackages, feature_category: :package_registry do expect do get api(package_url, user) - end.not_to exceed_query_limit(control) + end.not_to exceed_query_limit(control).with_threshold(4) end end @@ -286,7 +288,7 @@ RSpec.describe API::ProjectPackages, feature_category: :package_registry do end context 'project is private' do - let(:project) { create(:project, :private) } + let_it_be(:project) { create(:project, :private) } it 'returns 404 for non authenticated user' do get api(package_url) @@ -362,6 +364,235 @@ RSpec.describe API::ProjectPackages, feature_category: :package_registry do end end + describe 'GET /projects/:id/packages/:package_id/pipelines' do + let(:package_pipelines_url) { "/projects/#{project.id}/packages/#{package1.id}/pipelines" } + + let(:tokens) do + { + personal_access_token: personal_access_token.token, + job_token: job.token + } + end + + let_it_be(:personal_access_token) { create(:personal_access_token) } + let_it_be(:user) { personal_access_token.user } + let_it_be(:job) { create(:ci_build, :running, user: user, project: project) } + let(:headers) { {} } + + subject { get api(package_pipelines_url) } + + shared_examples 'returns package pipelines' do |expected_status| + it 'returns the first page of package pipelines' do + subject + + expect(response).to have_gitlab_http_status(expected_status) + expect(response).to match_response_schema('public_api/v4/packages/pipelines') + expect(json_response.length).to eq(3) + expect(json_response.pluck('id')).to eq(pipelines.reverse.map(&:id)) + end + end + + context 'without the need for a license' do + context 'when the package does not exist' do + let(:package_pipelines_url) { "/projects/#{project.id}/packages/0/pipelines" } + + it_behaves_like 'returning response status', :not_found + end + + context 'when there are no pipelines for the package' do + let(:package_pipelines_url) { "/projects/#{project.id}/packages/#{package2.id}/pipelines" } + + it 'returns an empty response' do + subject + + expect(response).to have_gitlab_http_status(:success) + expect(response).to match_response_schema('public_api/v4/packages/pipelines') + expect(json_response.length).to eq(0) + end + end + + context 'with valid package and pipelines' do + let!(:pipelines) do + create_list(:ci_pipeline, 3, user: user, project: project).each do |pipeline| + create(:package_build_info, package: package1, pipeline: pipeline) + end + end + + where(:visibility, :user_role, :member, :token_type, :valid_token, :shared_examples_name, :expected_status) do + :public | :developer | true | :personal_access_token | true | 'returns package pipelines' | :success + :public | :guest | true | :personal_access_token | true | 'returns package pipelines' | :success + :public | :developer | true | :personal_access_token | false | 'returning response status' | :unauthorized + :public | :guest | true | :personal_access_token | false | 'returning response status' | :unauthorized + :public | :developer | false | :personal_access_token | true | 'returns package pipelines' | :success + :public | :guest | false | :personal_access_token | true | 'returns package pipelines' | :success + :public | :developer | false | :personal_access_token | false | 'returning response status' | :unauthorized + :public | :guest | false | :personal_access_token | false | 'returning response status' | :unauthorized + :public | :anonymous | false | nil | true | 'returns package pipelines' | :success + :private | :developer | true | :personal_access_token | true | 'returns package pipelines' | :success + :private | :guest | true | :personal_access_token | true | 'returning response status' | :forbidden + :private | :developer | true | :personal_access_token | false | 'returning response status' | :unauthorized + :private | :guest | true | :personal_access_token | false | 'returning response status' | :unauthorized + :private | :developer | false | :personal_access_token | true | 'returning response status' | :not_found + :private | :guest | false | :personal_access_token | true | 'returning response status' | :not_found + :private | :developer | false | :personal_access_token | false | 'returning response status' | :unauthorized + :private | :guest | false | :personal_access_token | false | 'returning response status' | :unauthorized + :private | :anonymous | false | nil | true | 'returning response status' | :not_found + :public | :developer | true | :job_token | true | 'returns package pipelines' | :success + :public | :guest | true | :job_token | true | 'returns package pipelines' | :success + :public | :developer | true | :job_token | false | 'returning response status' | :unauthorized + :public | :guest | true | :job_token | false | 'returning response status' | :unauthorized + :public | :developer | false | :job_token | true | 'returns package pipelines' | :success + :public | :guest | false | :job_token | true | 'returns package pipelines' | :success + :public | :developer | false | :job_token | false | 'returning response status' | :unauthorized + :public | :guest | false | :job_token | false | 'returning response status' | :unauthorized + :private | :developer | true | :job_token | true | 'returns package pipelines' | :success + # TODO uncomment the spec below when https://gitlab.com/gitlab-org/gitlab/-/issues/370998 is resolved + # :private | :guest | true | :job_token | true | 'returning response status' | :forbidden + :private | :developer | true | :job_token | false | 'returning response status' | :unauthorized + :private | :guest | true | :job_token | false | 'returning response status' | :unauthorized + :private | :developer | false | :job_token | true | 'returning response status' | :not_found + :private | :guest | false | :job_token | true | 'returning response status' | :not_found + :private | :developer | false | :job_token | false | 'returning response status' | :unauthorized + :private | :guest | false | :job_token | false | 'returning response status' | :unauthorized + end + + with_them do + subject { get api(package_pipelines_url), headers: headers } + + let(:invalid_token) { 'invalid-token123' } + let(:token) { valid_token ? tokens[token_type] : invalid_token } + let(:headers) do + case token_type + when :personal_access_token + { Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER => token } + when :job_token + { Gitlab::Auth::AuthFinders::JOB_TOKEN_HEADER => token } + when nil + {} + end + end + + before do + project.update!(visibility: visibility.to_s) + project.send("add_#{user_role}", user) if member && user_role != :anonymous + end + + it_behaves_like params[:shared_examples_name], params[:expected_status] + end + end + + context 'pagination' do + shared_context 'setup pipeline records' do + let!(:pipelines) do + create_list(:package_build_info, 21, :with_pipeline, package: package1) + end + end + + shared_examples 'returns the default number of pipelines' do + it do + subject + + expect(json_response.size).to eq(20) + end + end + + shared_examples 'returns an error about the invalid per_page value' do + it do + subject + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['error']).to match(/per_page does not have a valid value/) + end + end + + context 'without pagination params' do + include_context 'setup pipeline records' + + it_behaves_like 'returns the default number of pipelines' + end + + context 'with valid per_page value' do + let(:per_page) { 11 } + + subject { get api(package_pipelines_url, user), params: { per_page: per_page } } + + include_context 'setup pipeline records' + + it 'returns the correct number of pipelines' do + subject + + expect(json_response.size).to eq(per_page) + end + end + + context 'with invalid pagination params' do + subject { get api(package_pipelines_url, user), params: { per_page: per_page } } + + context 'with non-positive per_page' do + let(:per_page) { -2 } + + it_behaves_like 'returns an error about the invalid per_page value' + end + + context 'with a too high value for per_page' do + let(:per_page) { 21 } + + it_behaves_like 'returns an error about the invalid per_page value' + end + end + + context 'with valid pagination params' do + let_it_be(:package1) { create(:npm_package, :last_downloaded_at, project: project) } + let_it_be(:build_info1) { create(:package_build_info, :with_pipeline, package: package1) } + let_it_be(:build_info2) { create(:package_build_info, :with_pipeline, package: package1) } + let_it_be(:build_info3) { create(:package_build_info, :with_pipeline, package: package1) } + + let(:pipeline1) { build_info1.pipeline } + let(:pipeline2) { build_info2.pipeline } + let(:pipeline3) { build_info3.pipeline } + + let(:per_page) { 2 } + + context 'with no cursor supplied' do + subject { get api(package_pipelines_url, user), params: { per_page: per_page } } + + it 'returns first 2 pipelines' do + subject + + expect(json_response.pluck('id')).to contain_exactly(pipeline3.id, pipeline2.id) + end + end + + context 'with a cursor parameter' do + let(:cursor) { Base64.urlsafe_encode64(Gitlab::Json.dump(cursor_attributes)) } + + subject { get api(package_pipelines_url, user), params: { per_page: per_page, cursor: cursor } } + + before do + subject + end + + context 'with a cursor for the next page' do + let(:cursor_attributes) { { "id" => build_info2.id, "_kd" => "n" } } + + it 'returns the next page of records' do + expect(json_response.pluck('id')).to contain_exactly(pipeline1.id) + end + end + + context 'with a cursor for the previous page' do + let(:cursor_attributes) { { "id" => build_info1.id, "_kd" => "p" } } + + it 'returns the previous page of records' do + expect(json_response.pluck('id')).to contain_exactly(pipeline3.id, pipeline2.id) + end + end + end + end + end + end + end + describe 'DELETE /projects/:id/packages/:package_id' do context 'without the need for a license' do context 'project is public' do @@ -379,7 +610,7 @@ RSpec.describe API::ProjectPackages, feature_category: :package_registry do end context 'project is private' do - let(:project) { create(:project, :private) } + let_it_be(:project) { create(:project, :private) } before do expect(::Packages::Maven::Metadata::SyncWorker).not_to receive(:perform_async) diff --git a/spec/services/users/set_namespace_commit_email_service_spec.rb b/spec/services/users/set_namespace_commit_email_service_spec.rb new file mode 100644 index 00000000000..4f64d454ecb --- /dev/null +++ b/spec/services/users/set_namespace_commit_email_service_spec.rb @@ -0,0 +1,195 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Users::SetNamespaceCommitEmailService, feature_category: :user_profile do + include AfterNextHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group) } + let_it_be(:email) { create(:email, user: user) } + let_it_be(:existing_achievement) { create(:achievement, namespace: group) } + + let(:namespace) { group } + let(:current_user) { user } + let(:target_user) { user } + let(:email_id) { email.id } + let(:params) { { user: target_user } } + let(:service) { described_class.new(current_user, namespace, email_id, params) } + + before_all do + group.add_reporter(user) + end + + shared_examples 'success' do + it 'creates namespace commit email' do + result = service.execute + + expect(result.payload[:namespace_commit_email]).to be_a(Users::NamespaceCommitEmail) + expect(result.payload[:namespace_commit_email]).to be_persisted + end + end + + describe '#execute' do + context 'when current_user is not provided' do + let(:current_user) { nil } + + it 'returns error message' do + expect(service.execute.message) + .to eq("User doesn't exist or you don't have permission to change namespace commit emails.") + end + end + + context 'when current_user does not have permission to change namespace commit emails' do + let(:target_user) { create(:user) } + + it 'returns error message' do + expect(service.execute.message) + .to eq("User doesn't exist or you don't have permission to change namespace commit emails.") + end + end + + context 'when target_user does not have permission to access the namespace' do + let(:namespace) { create(:group) } + + it 'returns error message' do + expect(service.execute.message).to eq("Namespace doesn't exist or you don't have permission.") + end + end + + context 'when namespace is not provided' do + let(:namespace) { nil } + + it 'returns error message' do + expect(service.execute.message).to eq('Namespace must be provided.') + end + end + + context 'when target user is not current user' do + context 'when current user is an admin' do + let(:current_user) { create(:user, :admin) } + + context 'when admin mode is enabled', :enable_admin_mode do + it 'creates namespace commit email' do + result = service.execute + + expect(result.payload[:namespace_commit_email]).to be_a(Users::NamespaceCommitEmail) + expect(result.payload[:namespace_commit_email]).to be_persisted + end + end + + context 'when admin mode is not enabled' do + it 'returns error message' do + expect(service.execute.message) + .to eq("User doesn't exist or you don't have permission to change namespace commit emails.") + end + end + end + + context 'when current user is not an admin' do + let(:current_user) { create(:user) } + + it 'returns error message' do + expect(service.execute.message) + .to eq("User doesn't exist or you don't have permission to change namespace commit emails.") + end + end + end + + context 'when namespace commit email does not exist' do + context 'when email_id is not provided' do + let(:email_id) { nil } + + it 'returns error message' do + expect(service.execute.message).to eq('Email must be provided.') + end + end + + context 'when model save fails' do + before do + allow_next(::Users::NamespaceCommitEmail).to receive(:save).and_return(false) + end + + it 'returns error message' do + expect(service.execute.message).to eq('Failed to save namespace commit email.') + end + end + + context 'when namepsace is a group' do + it_behaves_like 'success' + end + + context 'when namespace is a user' do + let(:namespace) { current_user.namespace } + + it_behaves_like 'success' + end + + context 'when namespace is a project' do + let_it_be(:project) { create(:project) } + + let(:namespace) { project.project_namespace } + + before do + project.add_reporter(current_user) + end + + it_behaves_like 'success' + end + end + + context 'when namespace commit email already exists' do + let!(:existing_namespace_commit_email) do + create(:namespace_commit_email, + user: target_user, + namespace: namespace, + email: create(:email, user: target_user)) + end + + context 'when email_id is not provided' do + let(:email_id) { nil } + + it 'destroys the namespace commit email' do + result = service.execute + + expect(result.message).to be_nil + expect(result.payload[:namespace_commit_email]).to be_nil + end + end + + context 'and email_id is provided' do + let(:email_id) { create(:email, user: current_user).id } + + it 'updates namespace commit email' do + result = service.execute + + existing_namespace_commit_email.reload + + expect(result.payload[:namespace_commit_email]).to eq(existing_namespace_commit_email) + expect(existing_namespace_commit_email.email_id).to eq(email_id) + end + end + + context 'when model save fails' do + before do + allow_any_instance_of(::Users::NamespaceCommitEmail).to receive(:save).and_return(false) # rubocop:disable RSpec/AnyInstanceOf + end + + it 'returns generic error message' do + expect(service.execute.message).to eq('Failed to save namespace commit email.') + end + + context 'with model errors' do + before do + allow_any_instance_of(::Users::NamespaceCommitEmail).to receive_message_chain(:errors, :empty?).and_return(false) # rubocop:disable RSpec/AnyInstanceOf + allow_any_instance_of(::Users::NamespaceCommitEmail).to receive_message_chain(:errors, :full_messages, :to_sentence).and_return('Model error') # rubocop:disable RSpec/AnyInstanceOf + end + + it 'returns the model error message' do + expect(service.execute.message).to eq('Model error') + end + end + end + end + end +end diff --git a/spec/support/shared_examples/features/work_items_shared_examples.rb b/spec/support/shared_examples/features/work_items_shared_examples.rb index ab57f4e2c55..128bd28410c 100644 --- a/spec/support/shared_examples/features/work_items_shared_examples.rb +++ b/spec/support/shared_examples/features/work_items_shared_examples.rb @@ -80,10 +80,13 @@ RSpec.shared_examples 'work items comments' do |type| it 'shows work item note actions' do set_comment - click_button "Comment" - + send_keys([modifier_key, :enter]) wait_for_requests + page.within(".main-notes-list") do + expect(page).to have_content comment + end + page.within('.timeline-entry.note.note-wrapper.note-comment:last-child') do expect(page).to have_selector('[data-testid="work-item-note-actions"]') |