From 05f0ebba3a2c8ddf39e436f412dc2ab5bf1353b2 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 18 Jan 2023 19:00:14 +0000 Subject: Add latest changes from gitlab-org/gitlab@15-8-stable-ee --- spec/requests/api/appearance_spec.rb | 10 +- spec/requests/api/boards_spec.rb | 2 +- spec/requests/api/bulk_imports_spec.rb | 200 ++++++++++++--- spec/requests/api/ci/jobs_spec.rb | 70 ++++++ spec/requests/api/ci/runner/jobs_artifacts_spec.rb | 39 +++ spec/requests/api/commits_spec.rb | 12 - spec/requests/api/debian_project_packages_spec.rb | 29 ++- spec/requests/api/environments_spec.rb | 74 ++++++ spec/requests/api/files_spec.rb | 120 +++++++-- spec/requests/api/graphql/ci/config_spec.rb | 62 +++-- spec/requests/api/graphql/ci/jobs_spec.rb | 8 +- .../api/graphql/group/merge_requests_spec.rb | 2 +- spec/requests/api/graphql/group_query_spec.rb | 2 +- spec/requests/api/graphql/issues_spec.rb | 65 ++++- .../graphql/merge_request/merge_request_spec.rb | 2 +- .../graphql/mutations/achievements/create_spec.rb | 78 ++++++ .../api/graphql/mutations/ci/job_play_spec.rb | 39 ++- .../api/graphql/mutations/groups/update_spec.rb | 19 +- .../mutations/members/groups/bulk_update_spec.rb | 130 ++++++++++ .../mutations/merge_requests/create_spec.rb | 2 +- .../merge_requests/reviewer_rereview_spec.rb | 2 +- .../mutations/merge_requests/set_assignees_spec.rb | 2 +- .../mutations/merge_requests/set_draft_spec.rb | 2 +- .../mutations/merge_requests/set_locked_spec.rb | 2 +- .../mutations/merge_requests/set_milestone_spec.rb | 2 +- .../mutations/merge_requests/set_reviewers_spec.rb | 2 +- .../merge_requests/set_subscription_spec.rb | 2 +- .../graphql/mutations/work_items/update_spec.rb | 4 +- .../api/graphql/project/branch_rules_spec.rb | 45 ++-- spec/requests/api/graphql/project/issues_spec.rb | 4 - spec/requests/api/graphql/project/jobs_spec.rb | 12 +- .../project/merge_request/diff_notes_spec.rb | 2 +- .../api/graphql/project/merge_request_spec.rb | 2 +- .../api/graphql/project/merge_requests_spec.rb | 2 +- spec/requests/api/graphql/project/pipeline_spec.rb | 17 +- spec/requests/api/graphql/project/runners_spec.rb | 12 - .../api/graphql/project/work_items_spec.rb | 2 +- spec/requests/api/graphql/user_spec.rb | 41 +++ spec/requests/api/graphql/work_item_spec.rb | 18 ++ spec/requests/api/group_boards_spec.rb | 2 +- spec/requests/api/group_export_spec.rb | 11 +- spec/requests/api/import_github_spec.rb | 139 +++++++++-- spec/requests/api/internal/base_spec.rb | 24 +- .../requests/api/issues/get_project_issues_spec.rb | 61 ++--- spec/requests/api/issues/issues_spec.rb | 5 + spec/requests/api/markdown_golden_master_spec.rb | 9 - spec/requests/api/merge_requests_spec.rb | 10 + spec/requests/api/ml/mlflow_spec.rb | 10 +- spec/requests/api/nuget_group_packages_spec.rb | 40 ++- spec/requests/api/pages_domains_spec.rb | 3 + .../api/project_debian_distributions_spec.rb | 25 +- spec/requests/api/project_export_spec.rb | 14 +- spec/requests/api/projects_spec.rb | 14 +- spec/requests/api/release/links_spec.rb | 27 ++ spec/requests/api/releases_spec.rb | 28 ++- spec/requests/api/repositories_spec.rb | 12 +- spec/requests/api/rubygem_packages_spec.rb | 26 +- spec/requests/api/search_spec.rb | 20 ++ spec/requests/api/settings_spec.rb | 64 ++++- .../api/snippet_repository_storage_moves_spec.rb | 2 +- spec/requests/api/suggestions_spec.rb | 2 +- spec/requests/api/todos_spec.rb | 32 ++- spec/requests/api/users_spec.rb | 277 +++++++++++++++++---- 63 files changed, 1636 insertions(+), 361 deletions(-) create mode 100644 spec/requests/api/graphql/mutations/achievements/create_spec.rb create mode 100644 spec/requests/api/graphql/mutations/members/groups/bulk_update_spec.rb delete mode 100644 spec/requests/api/markdown_golden_master_spec.rb (limited to 'spec/requests/api') diff --git a/spec/requests/api/appearance_spec.rb b/spec/requests/api/appearance_spec.rb index 84d5b091b8d..5aba7e096a7 100644 --- a/spec/requests/api/appearance_spec.rb +++ b/spec/requests/api/appearance_spec.rb @@ -23,6 +23,7 @@ RSpec.describe API::Appearance, 'Appearance', feature_category: :navigation do expect(json_response).to be_an Hash expect(json_response['description']).to eq('') expect(json_response['email_header_and_footer_enabled']).to be(false) + expect(json_response['pwa_icon']).to be_nil expect(json_response['favicon']).to be_nil expect(json_response['footer_message']).to eq('') expect(json_response['header_logo']).to be_nil @@ -33,7 +34,7 @@ RSpec.describe API::Appearance, 'Appearance', feature_category: :navigation do expect(json_response['new_project_guidelines']).to eq('') expect(json_response['profile_image_guidelines']).to eq('') expect(json_response['title']).to eq('') - expect(json_response['short_title']).to eq('') + expect(json_response['pwa_short_name']).to eq('') end end end @@ -52,7 +53,7 @@ RSpec.describe API::Appearance, 'Appearance', feature_category: :navigation do it "allows updating the settings" do put api("/application/appearance", admin), params: { title: "GitLab Test Instance", - short_title: "GitLab", + pwa_short_name: "GitLab PWA", description: "gitlab-test.example.com", new_project_guidelines: "Please read the FAQs for help.", profile_image_guidelines: "Custom profile image guidelines" @@ -62,6 +63,7 @@ RSpec.describe API::Appearance, 'Appearance', feature_category: :navigation do expect(json_response).to be_an Hash expect(json_response['description']).to eq('gitlab-test.example.com') expect(json_response['email_header_and_footer_enabled']).to be(false) + expect(json_response['pwa_icon']).to be_nil expect(json_response['favicon']).to be_nil expect(json_response['footer_message']).to eq('') expect(json_response['header_logo']).to be_nil @@ -72,7 +74,7 @@ RSpec.describe API::Appearance, 'Appearance', feature_category: :navigation do expect(json_response['new_project_guidelines']).to eq('Please read the FAQs for help.') expect(json_response['profile_image_guidelines']).to eq('Custom profile image guidelines') expect(json_response['title']).to eq('GitLab Test Instance') - expect(json_response['short_title']).to eq('GitLab') + expect(json_response['pwa_short_name']).to eq('GitLab PWA') end end @@ -118,12 +120,14 @@ RSpec.describe API::Appearance, 'Appearance', feature_category: :navigation do put api("/application/appearance", admin), params: { logo: fixture_file_upload("spec/fixtures/dk.png", "image/png"), header_logo: fixture_file_upload("spec/fixtures/dk.png", "image/png"), + pwa_icon: fixture_file_upload("spec/fixtures/dk.png", "image/png"), favicon: fixture_file_upload("spec/fixtures/dk.png", "image/png") } expect(response).to have_gitlab_http_status(:ok) expect(json_response['logo']).to eq("/uploads/-/system/appearance/logo/#{appearance.id}/dk.png") expect(json_response['header_logo']).to eq("/uploads/-/system/appearance/header_logo/#{appearance.id}/dk.png") + expect(json_response['pwa_icon']).to eq("/uploads/-/system/appearance/pwa_icon/#{appearance.id}/dk.png") expect(json_response['favicon']).to eq("/uploads/-/system/appearance/favicon/#{appearance.id}/dk.png") end diff --git a/spec/requests/api/boards_spec.rb b/spec/requests/api/boards_spec.rb index 69804c2c4a4..5f2ff22d0db 100644 --- a/spec/requests/api/boards_spec.rb +++ b/spec/requests/api/boards_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Boards, feature_category: :team_planning do +RSpec.describe API::Boards, :with_license, feature_category: :team_planning do let_it_be(:user) { create(:user) } let_it_be(:non_member) { create(:user) } let_it_be(:guest) { create(:user) } diff --git a/spec/requests/api/bulk_imports_spec.rb b/spec/requests/api/bulk_imports_spec.rb index 13f079c69e7..4fb4fbe6d5c 100644 --- a/spec/requests/api/bulk_imports_spec.rb +++ b/spec/requests/api/bulk_imports_spec.rb @@ -11,9 +11,26 @@ RSpec.describe API::BulkImports, feature_category: :importers do let_it_be(:entity_3) { create(:bulk_import_entity, bulk_import: import_2) } let_it_be(:failure_3) { create(:bulk_import_failure, entity: entity_3) } + before do + stub_application_setting(bulk_import_enabled: true) + end + + shared_examples 'disabled feature' do + it 'returns 404' do + stub_application_setting(bulk_import_enabled: false) + + request + + expect(response).to have_gitlab_http_status(:not_found) + end + end + describe 'GET /bulk_imports' do + let(:request) { get api('/bulk_imports', user), params: params } + let(:params) { {} } + it 'returns a list of bulk imports authored by the user' do - get api('/bulk_imports', user) + request expect(response).to have_gitlab_http_status(:ok) expect(json_response.pluck('id')).to contain_exactly(import_1.id, import_2.id) @@ -21,26 +38,38 @@ RSpec.describe API::BulkImports, feature_category: :importers do context 'sort parameter' do it 'sorts by created_at descending by default' do - get api('/bulk_imports', user) + request expect(response).to have_gitlab_http_status(:ok) expect(json_response.pluck('id')).to eq([import_2.id, import_1.id]) end - it 'sorts by created_at descending when explicitly specified' do - get api('/bulk_imports', user), params: { sort: 'desc' } + context 'when explicitly specified' do + context 'when descending' do + let(:params) { { sort: 'desc' } } - expect(response).to have_gitlab_http_status(:ok) - expect(json_response.pluck('id')).to eq([import_2.id, import_1.id]) - end + it 'sorts by created_at descending' do + request - it 'sorts by created_at ascending when explicitly specified' do - get api('/bulk_imports', user), params: { sort: 'asc' } + expect(response).to have_gitlab_http_status(:ok) + expect(json_response.pluck('id')).to match_array([import_2.id, import_1.id]) + end + end - expect(response).to have_gitlab_http_status(:ok) - expect(json_response.pluck('id')).to eq([import_1.id, import_2.id]) + context 'when ascending' do + let(:params) { { sort: 'asc' } } + + it 'sorts by created_at ascending when explicitly specified' do + request + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response.pluck('id')).to match_array([import_1.id, import_2.id]) + end + end end end + + include_examples 'disabled feature' end describe 'POST /bulk_imports' do @@ -56,21 +85,10 @@ RSpec.describe API::BulkImports, feature_category: :importers do end end - context 'when bulk_import feature flag is disabled' do - before do - stub_feature_flags(bulk_import: false) - end - - it 'returns 404' do - post api('/bulk_imports', user), params: {} - - expect(response).to have_gitlab_http_status(:not_found) - end - end - shared_examples 'starting a new migration' do - it 'starts a new migration' do - post api('/bulk_imports', user), params: { + let(:request) { post api('/bulk_imports', user), params: params } + let(:params) do + { configuration: { url: 'http://gitlab.example', access_token: 'access_token' @@ -83,11 +101,45 @@ RSpec.describe API::BulkImports, feature_category: :importers do }.merge(destination_param) ] } + end + + it 'starts a new migration' do + request expect(response).to have_gitlab_http_status(:created) expect(json_response['status']).to eq('created') end + + describe 'migrate projects flag' do + context 'when true' do + it 'sets true' do + params[:entities][0][:migrate_projects] = true + + request + + expect(user.bulk_imports.last.entities.pluck(:migrate_projects)).to contain_exactly(true) + end + end + + context 'when false' do + it 'sets false' do + params[:entities][0][:migrate_projects] = false + + request + + expect(user.bulk_imports.last.entities.pluck(:migrate_projects)).to contain_exactly(false) + end + end + + context 'when unspecified' do + it 'sets true' do + request + + expect(user.bulk_imports.last.entities.pluck(:migrate_projects)).to contain_exactly(true) + end + end + end end include_examples 'starting a new migration' do @@ -99,8 +151,8 @@ RSpec.describe API::BulkImports, feature_category: :importers do end context 'when both destination_name & destination_slug are provided' do - it 'returns a mutually exclusive error' do - post api('/bulk_imports', user), params: { + let(:params) do + { configuration: { url: 'http://gitlab.example', access_token: 'access_token' @@ -115,6 +167,10 @@ RSpec.describe API::BulkImports, feature_category: :importers do } ] } + end + + it 'returns a mutually exclusive error' do + request expect(response).to have_gitlab_http_status(:bad_request) @@ -123,8 +179,8 @@ RSpec.describe API::BulkImports, feature_category: :importers do end context 'when neither destination_name nor destination_slug is provided' do - it 'returns at_least_one_of error' do - post api('/bulk_imports', user), params: { + let(:params) do + { configuration: { url: 'http://gitlab.example', access_token: 'access_token' @@ -137,6 +193,10 @@ RSpec.describe API::BulkImports, feature_category: :importers do } ] } + end + + it 'returns at_least_one_of error' do + request expect(response).to have_gitlab_http_status(:bad_request) @@ -144,9 +204,57 @@ RSpec.describe API::BulkImports, feature_category: :importers do end end + context 'when the source_full_path is invalid' do + it 'returns invalid error' do + params[:entities][0][:source_full_path] = 'http://example.com/full_path' + + request + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['error']).to eq("entities[0][source_full_path] must be a relative path and not include protocol, sub-domain, " \ + "or domain information. E.g. 'source/full/path' not 'https://example.com/source/full/path'") + end + end + + context 'when the destination_namespace is invalid' do + it 'returns invalid error' do + params[:entities][0][:destination_namespace] = "?not a destination-namespace" + + request + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['error']).to eq("entities[0][destination_namespace] cannot start with a dash or forward slash, " \ + "or end with a period or forward slash. It can only contain alphanumeric " \ + "characters, periods, underscores, forward slashes and dashes. " \ + "E.g. 'destination_namespace' or 'destination/namespace'") + end + end + + context 'when the destination_namespace is an empty string' do + it 'accepts the param and starts a new migration' do + params[:entities][0][:destination_namespace] = '' + + request + expect(response).to have_gitlab_http_status(:created) + + expect(json_response['status']).to eq('created') + end + end + + context 'when the destination_slug is invalid' do + it 'returns invalid error' do + params[:entities][0][:destination_slug] = 'des?tin?atoi-slugg' + + request + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['error']).to include("entities[0][destination_slug] cannot start with a dash " \ + "or forward slash, or end with a period or forward slash. " \ + "It can only contain alphanumeric characters, periods, underscores, and dashes. " \ + "E.g. 'destination_namespace' not 'destination/namespace'") + end + end + context 'when provided url is blocked' do - it 'returns blocked url error' do - post api('/bulk_imports', user), params: { + let(:params) do + { configuration: { url: 'url', access_token: 'access_token' @@ -158,49 +266,71 @@ RSpec.describe API::BulkImports, feature_category: :importers do destination_namespace: 'destination_namespace' ] } + end + + it 'returns blocked url error' do + request expect(response).to have_gitlab_http_status(:unprocessable_entity) expect(json_response['message']).to eq('Validation failed: Url is blocked: Only allowed schemes are http, https') end end + + include_examples 'disabled feature' end describe 'GET /bulk_imports/entities' do + let(:request) { get api('/bulk_imports/entities', user) } + it 'returns a list of all import entities authored by the user' do - get api('/bulk_imports/entities', user) + request expect(response).to have_gitlab_http_status(:ok) expect(json_response.pluck('id')).to contain_exactly(entity_1.id, entity_2.id, entity_3.id) end + + include_examples 'disabled feature' end describe 'GET /bulk_imports/:id' do + let(:request) { get api("/bulk_imports/#{import_1.id}", user) } + it 'returns specified bulk import' do - get api("/bulk_imports/#{import_1.id}", user) + request expect(response).to have_gitlab_http_status(:ok) expect(json_response['id']).to eq(import_1.id) end + + include_examples 'disabled feature' end describe 'GET /bulk_imports/:id/entities' do + let(:request) { get api("/bulk_imports/#{import_2.id}/entities", user) } + it 'returns specified bulk import entities with failures' do - get api("/bulk_imports/#{import_2.id}/entities", user) + request expect(response).to have_gitlab_http_status(:ok) expect(json_response.pluck('id')).to contain_exactly(entity_3.id) expect(json_response.first['failures'].first['exception_class']).to eq(failure_3.exception_class) end + + include_examples 'disabled feature' end describe 'GET /bulk_imports/:id/entities/:entity_id' do + let(:request) { get api("/bulk_imports/#{import_1.id}/entities/#{entity_2.id}", user) } + it 'returns specified bulk import entity' do - get api("/bulk_imports/#{import_1.id}/entities/#{entity_2.id}", user) + request expect(response).to have_gitlab_http_status(:ok) expect(json_response['id']).to eq(entity_2.id) end + + include_examples 'disabled feature' end context 'when user is unauthenticated' do diff --git a/spec/requests/api/ci/jobs_spec.rb b/spec/requests/api/ci/jobs_spec.rb index 4e348ae64b6..875bfc5b94f 100644 --- a/spec/requests/api/ci/jobs_spec.rb +++ b/spec/requests/api/ci/jobs_spec.rb @@ -487,6 +487,76 @@ RSpec.describe API::Ci::Jobs, feature_category: :continuous_integration do end end + describe 'GET /projects/:id/jobs offset pagination' do + before do + running_job + end + + it 'returns one record for the first page' do + get api("/projects/#{project.id}/jobs", api_user), params: { per_page: 1 } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response.size).to eq(1) + expect(json_response.first['id']).to eq(running_job.id) + end + + it 'returns second record when passed in offset and per_page params' do + get api("/projects/#{project.id}/jobs", api_user), params: { page: 2, per_page: 1 } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response.size).to eq(1) + expect(json_response.first['id']).to eq(job.id) + end + end + + describe 'GET /projects/:id/jobs keyset pagination' do + before do + running_job + end + + it 'returns first page with cursor to next page' do + get api("/projects/#{project.id}/jobs", api_user), params: { pagination: 'keyset', per_page: 1 } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response.size).to eq(1) + expect(json_response.first['id']).to eq(running_job.id) + expect(response.headers["Link"]).to include("cursor") + next_cursor = response.headers["Link"].match("(?cursor=.*?)&")["cursor_data"] + + get api("/projects/#{project.id}/jobs", api_user), params: { pagination: 'keyset', per_page: 1 }.merge(Rack::Utils.parse_query(next_cursor)) + + expect(response).to have_gitlab_http_status(:ok) + json_response = Gitlab::Json.parse(response.body) + expect(json_response.size).to eq(1) + expect(json_response.first['id']).to eq(job.id) + expect(response.headers).not_to include("Link") + end + + it 'respects scope filters' do + get api("/projects/#{project.id}/jobs", api_user), params: { pagination: 'keyset', scope: ['success'] } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response.size).to eq(1) + expect(json_response.first['id']).to eq(job.id) + expect(response.headers).not_to include("Link") + end + + context 'with :jobs_api_keyset_pagination disabled' do + before do + stub_feature_flags(jobs_api_keyset_pagination: false) + end + + it 'defaults to offset pagination' do + get api("/projects/#{project.id}/jobs", api_user), params: { pagination: 'keyset', per_page: 1 } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response.size).to eq(1) + expect(json_response.first['id']).to eq(running_job.id) + expect(response.headers["Link"]).not_to include("cursor") + end + end + end + describe 'GET /projects/:id/jobs rate limited' do let(:query) { {} } diff --git a/spec/requests/api/ci/runner/jobs_artifacts_spec.rb b/spec/requests/api/ci/runner/jobs_artifacts_spec.rb index 1c119079c50..3d3d699542b 100644 --- a/spec/requests/api/ci/runner/jobs_artifacts_spec.rb +++ b/spec/requests/api/ci/runner/jobs_artifacts_spec.rb @@ -575,6 +575,45 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego end end + context 'when access level is private' do + subject(:request) { upload_artifacts(file_upload, headers_with_token, params) } + + let(:params) { { artifact_type: :archive, artifact_format: :zip, accessibility: 'private' } } + + it 'sets job artifact access level to private' do + subject + + expect(response).to have_gitlab_http_status(:created) + expect(job.reload.job_artifacts_archive).to be_private_accessibility + end + end + + context 'when access level is public' do + subject(:request) { upload_artifacts(file_upload, headers_with_token, params) } + + let(:params) { { artifact_type: :archive, artifact_format: :zip, accessibility: 'public' } } + + it 'sets job artifact access level to public' do + subject + + expect(response).to have_gitlab_http_status(:created) + expect(job.reload.job_artifacts_archive).to be_public_accessibility + end + end + + context 'when access level is unknown' do + subject(:request) { upload_artifacts(file_upload, headers_with_token, params) } + + let(:params) { { artifact_type: :archive, artifact_format: :zip } } + + it 'sets job artifact access level to public' do + subject + + expect(response).to have_gitlab_http_status(:created) + expect(job.reload.job_artifacts_archive).to be_public_accessibility + end + end + context 'when artifact_type is archive' do context 'when artifact_format is zip' do subject(:request) { upload_artifacts(file_upload, headers_with_token, params) } diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 5874d764b00..3932abd20cc 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -2337,18 +2337,6 @@ RSpec.describe API::Commits, feature_category: :source_code_management do expect(json_response['commit_source']).to eq('gitaly') end end - - context 'when feature flag is disabled' do - before do - stub_feature_flags(ssh_commit_signatures: false) - end - - it 'returns 404' do - get api(route, current_user) - - expect(response).to have_gitlab_http_status(:not_found) - end - end end end end diff --git a/spec/requests/api/debian_project_packages_spec.rb b/spec/requests/api/debian_project_packages_spec.rb index c27e165b39b..5258d26be17 100644 --- a/spec/requests/api/debian_project_packages_spec.rb +++ b/spec/requests/api/debian_project_packages_spec.rb @@ -5,7 +5,17 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d include HttpBasicAuthHelpers include WorkhorseHelpers - include_context 'Debian repository shared context', :project, true do + include_context 'Debian repository shared context', :project, false do + shared_examples 'accept GET request on private project with access to package registry for everyone' do + include_context 'Debian repository access', :private, :anonymous, :basic do + before do + container.project_feature.reload.update!(package_registry_access_level: ProjectFeature::PUBLIC) + end + + it_behaves_like 'Debian packages GET request', :success + end + end + context 'with invalid parameter' do let(:url) { "/projects/1/packages/debian/dists/with+space/InRelease" } @@ -16,54 +26,63 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/Release.gpg" } it_behaves_like 'Debian packages read endpoint', 'GET', :success, /^-----BEGIN PGP SIGNATURE-----/ + it_behaves_like 'accept GET request on private project with access to package registry for everyone' end describe 'GET projects/:id/packages/debian/dists/*distribution/Release' do let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/Release" } it_behaves_like 'Debian packages read endpoint', 'GET', :success, /^Codename: fixture-distribution\n$/ + it_behaves_like 'accept GET request on private project with access to package registry for everyone' end describe 'GET projects/:id/packages/debian/dists/*distribution/InRelease' do let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/InRelease" } it_behaves_like 'Debian packages read endpoint', 'GET', :success, /^-----BEGIN PGP SIGNED MESSAGE-----/ + it_behaves_like 'accept GET request on private project with access to package registry for everyone' end describe 'GET projects/:id/packages/debian/dists/*distribution/:component/binary-:architecture/Packages' do let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{component.name}/binary-#{architecture.name}/Packages" } it_behaves_like 'Debian packages read endpoint', 'GET', :success, /Description: This is an incomplete Packages file/ + it_behaves_like 'accept GET request on private project with access to package registry for everyone' end describe 'GET projects/:id/packages/debian/dists/*distribution/:component/binary-:architecture/by-hash/SHA256/:file_sha256' do let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{component.name}/binary-#{architecture.name}/by-hash/SHA256/#{component_file_older_sha256.file_sha256}" } it_behaves_like 'Debian packages read endpoint', 'GET', :success, /^Other SHA256$/ + it_behaves_like 'accept GET request on private project with access to package registry for everyone' end - describe 'GET projects/:id/packages/debian/dists/*distribution/source/Sources' do + describe 'GET projects/:id/packages/debian/dists/*distribution/:component/source/Sources' do let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{component.name}/source/Sources" } it_behaves_like 'Debian packages read endpoint', 'GET', :success, /Description: This is an incomplete Sources file/ + it_behaves_like 'accept GET request on private project with access to package registry for everyone' end - describe 'GET projects/:id/packages/debian/dists/*distribution/source/by-hash/SHA256/:file_sha256' do + describe 'GET projects/:id/packages/debian/dists/*distribution/:component/source/by-hash/SHA256/:file_sha256' do let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{component.name}/source/by-hash/SHA256/#{component_file_sources_older_sha256.file_sha256}" } it_behaves_like 'Debian packages read endpoint', 'GET', :success, /^Other SHA256$/ + it_behaves_like 'accept GET request on private project with access to package registry for everyone' end describe 'GET projects/:id/packages/debian/dists/*distribution/:component/debian-installer/binary-:architecture/Packages' do let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{component.name}/debian-installer/binary-#{architecture.name}/Packages" } it_behaves_like 'Debian packages read endpoint', 'GET', :success, /Description: This is an incomplete D-I Packages file/ + it_behaves_like 'accept GET request on private project with access to package registry for everyone' end describe 'GET projects/:id/packages/debian/dists/*distribution/:component/debian-installer/binary-:architecture/by-hash/SHA256/:file_sha256' do let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{component.name}/debian-installer/binary-#{architecture.name}/by-hash/SHA256/#{component_file_di_older_sha256.file_sha256}" } it_behaves_like 'Debian packages read endpoint', 'GET', :success, /^Other SHA256$/ + it_behaves_like 'accept GET request on private project with access to package registry for everyone' end describe 'GET projects/:id/packages/debian/pool/:codename/:letter/:package_name/:package_version/:file_name' do @@ -90,6 +109,10 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d end end end + + it_behaves_like 'accept GET request on private project with access to package registry for everyone' do + let(:file_name) { 'sample_1.2.3~alpha2.dsc' } + end end describe 'PUT projects/:id/packages/debian/:file_name' do diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb index d06e70a1a02..6164555ad19 100644 --- a/spec/requests/api/environments_spec.rb +++ b/spec/requests/api/environments_spec.rb @@ -4,12 +4,14 @@ require 'spec_helper' RSpec.describe API::Environments, feature_category: :continuous_delivery do let_it_be(:user) { create(:user) } + let_it_be(:developer) { create(:user) } let_it_be(:non_member) { create(:user) } let_it_be(:project) { create(:project, :private, :repository, namespace: user.namespace) } let_it_be_with_reload(:environment) { create(:environment, project: project) } before do project.add_maintainer(user) + project.add_developer(developer) end describe 'GET /projects/:id/environments', :aggregate_failures do @@ -69,6 +71,34 @@ RSpec.describe API::Environments, feature_category: :continuous_delivery do expect(json_response.size).to eq(0) end + context "when params[:search] is less than #{described_class::MIN_SEARCH_LENGTH} characters" do + before do + stub_feature_flags(environment_search_api_min_chars: false) + end + + it 'returns a normal response' do + get api("/projects/#{project.id}/environments?search=ab", user) + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.size).to eq(0) + end + + context 'and environment_search_api_min_chars flag is enabled for the project' do + before do + stub_feature_flags(environment_search_api_min_chars: project) + end + + it 'returns with status 400' do + get api("/projects/#{project.id}/environments?search=ab", user) + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']).to include("Search query is less than #{described_class::MIN_SEARCH_LENGTH} characters") + end + end + end + it 'returns environment by valid state' do get api("/projects/#{project.id}/environments?states=available", user) @@ -154,6 +184,50 @@ RSpec.describe API::Environments, feature_category: :continuous_delivery do end end + describe 'POST /projects/:id/environments/stop_stale' do + context 'as a maintainer' do + it 'returns a 200' do + post api("/projects/#{project.id}/environments/stop_stale", user), params: { before: 1.week.ago.to_date.to_s } + + expect(response).to have_gitlab_http_status(:ok) + end + + it 'returns a 400 for bad input date' do + post api("/projects/#{project.id}/environments/stop_stale", user), params: { before: 1.day.ago.to_date.to_s } + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']).to eq('400 Bad request - Invalid Date') + end + + it 'returns a 400 for service error' do + expect_next_instance_of(::Environments::StopStaleService) do |service| + expect(service).to receive(:execute).and_return(ServiceResponse.error(message: 'Test Error')) + end + + post api("/projects/#{project.id}/environments/stop_stale", user), params: { before: 1.week.ago.to_date.to_s } + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']).to eq('Test Error') + end + end + + context 'a non member' do + it 'rejects the request' do + post api("/projects/#{project.id}/environments/stop_stale", non_member), params: { before: 1.week.ago.to_date.to_s } + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'a developer' do + it 'rejects the request' do + post api("/projects/#{project.id}/environments/stop_stale", developer), params: { before: 1.week.ago.to_date.to_s } + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + end + describe 'PUT /projects/:id/environments/:environment_id' do it 'returns a 200 if name and external_url are changed' do url = 'https://mepmep.whatever.ninja' diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index 9cee3c06bb1..f4066c54c47 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -6,6 +6,24 @@ RSpec.describe API::Files, feature_category: :source_code_management do include RepoHelpers let_it_be(:group) { create(:group, :public) } + let(:helper) do + fake_class = Class.new do + include ::API::Helpers::HeadersHelpers + + attr_reader :headers + + def initialize + @headers = {} + end + + def header(key, value) + @headers[key] = value + end + end + + fake_class.new + end + let_it_be_with_refind(:user) { create(:user) } let_it_be(:inherited_guest) { create(:user) } let_it_be(:inherited_reporter) { create(:user) } @@ -37,25 +55,9 @@ RSpec.describe API::Files, feature_category: :source_code_management do } end - let(:author_email) { 'user@example.org' } - let(:author_name) { 'John Doe' } - - let(:helper) do - fake_class = Class.new do - include ::API::Helpers::HeadersHelpers - - attr_reader :headers - - def initialize - @headers = {} - end - - def header(key, value) - @headers[key] = value - end - end - - fake_class.new + shared_context 'with author parameters' do + let(:author_email) { 'user@example.org' } + let(:author_name) { 'John Doe' } end before_all do @@ -702,6 +704,80 @@ RSpec.describe API::Files, feature_category: :source_code_management do end end + describe 'HEAD /projects/:id/repository/files/:file_path/raw' do + let(:request) { head api(route(file_path) + '/raw', current_user), params: params } + + describe 'response headers' do + subject { response.headers } + + context 'and user is a developer' do + let(:current_user) { user } + + it 'responds with blob data' do + request + headers = response.headers + expect(headers['X-Gitlab-File-Name']).to eq(file_name) + expect(headers['X-Gitlab-File-Path']).to eq('files/ruby/popen.rb') + expect(headers['X-Gitlab-Content-Sha256']).to eq('c440cd09bae50c4632cc58638ad33c6aa375b6109d811e76a9cc3a613c1e8887') + expect(headers['X-Gitlab-Ref']).to eq('master') + expect(headers['X-Gitlab-Blob-Id']).to eq('7e3e39ebb9b2bf433b4ad17313770fbe4051649c') + expect(headers['X-Gitlab-Commit-Id']).to eq(project.repository.commit.id) + expect(headers['X-Gitlab-Last-Commit-Id']).to eq('570e7b2abdd848b95f2f578043fc23bd6f6fd24d') + end + + context 'when lfs parameter is true and the project has lfs enabled' do + before do + allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) + project.update_attribute(:lfs_enabled, true) + end + + let(:request) { head api(route('files%2Flfs%2Flfs_object.iso') + '/raw', current_user), params: params.merge(lfs: true) } + + context 'and the file has an lfs object' do + let_it_be(:lfs_object) { create(:lfs_object, :with_file, oid: '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897') } + + it 'responds with 404' do + request + + expect(response).to have_gitlab_http_status(:not_found) + end + + context 'and the project has access to the lfs object' do + before do + project.lfs_objects << lfs_object + end + + context 'and lfs uses AWS' do + before do + stub_lfs_object_storage(config: Gitlab.config.lfs.object_store.merge(connection: { + provider: 'AWS', + aws_access_key_id: '', + aws_secret_access_key: '' + })) + lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE) + end + + it 'redirects to the lfs object file with a signed url' do + request + + expect(response).to have_gitlab_http_status(:found) + expect(response.location).to include(lfs_object.reload.file.path) + expect(response.location).to include('X-Amz-SignedHeaders') + end + end + end + end + end + end + + context 'and user is a guest' do + it_behaves_like '403 response' do + let(:request) { head api(route(file_path), guest), params: params } + end + end + end + end + describe 'GET /projects/:id/repository/files/:file_path/raw' do shared_examples_for 'repository raw files' do it 'returns 400 when file path is invalid' do @@ -1006,6 +1082,8 @@ RSpec.describe API::Files, feature_category: :source_code_management do end context 'when specifying an author' do + include_context 'with author parameters' + it 'creates a new file with the specified author' do params.merge!(author_email: author_email, author_name: author_name) post api(route('new_file_with_author%2Etxt'), user), params: params @@ -1163,6 +1241,8 @@ RSpec.describe API::Files, feature_category: :source_code_management do end context 'when specifying an author' do + include_context 'with author parameters' + it 'updates a file with the specified author' do params.merge!(author_email: author_email, author_name: author_name, content: 'New content') @@ -1236,6 +1316,8 @@ RSpec.describe API::Files, feature_category: :source_code_management do end context 'when specifying an author' do + include_context 'with author parameters' + before do params.merge!(author_email: author_email, author_name: author_name) end diff --git a/spec/requests/api/graphql/ci/config_spec.rb b/spec/requests/api/graphql/ci/config_spec.rb index 8154f132430..5f43a0806f3 100644 --- a/spec/requests/api/graphql/ci/config_spec.rb +++ b/spec/requests/api/graphql/ci/config_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' RSpec.describe 'Query.ciConfig', feature_category: :continuous_integration do include GraphqlHelpers include StubRequests + include RepoHelpers subject(:post_graphql_query) { post_graphql(query, current_user: user) } @@ -245,17 +246,22 @@ RSpec.describe 'Query.ciConfig', feature_category: :continuous_integration do ) end - before do - allow_next_instance_of(Repository) do |repository| - allow(repository).to receive(:blob_data_at).with(an_instance_of(String), 'other_file.yml') do - YAML.dump( - build: { - script: 'build' - } - ) - end + let(:project_files) do + { + 'other_file.yml' => <<~YAML + build: + script: build + YAML + } + end + + around do |example| + create_and_delete_files(project, project_files) do + example.run end + end + before do post_graphql_query end @@ -370,25 +376,33 @@ RSpec.describe 'Query.ciConfig', feature_category: :continuous_integration do ) end - before do - allow_next_instance_of(Repository) do |repository| - allow(repository).to receive(:blob_data_at).with(an_instance_of(String), 'other_file.yml') do - YAML.dump( - build: { - script: 'build' - } - ) - end + let(:project_files) do + { + 'other_file.yml' => <<~YAML + build: + script: build + YAML + } + end - allow(repository).to receive(:blob_data_at).with(an_instance_of(String), 'other_project_file.yml') do - YAML.dump( - other_project_test: { - script: 'other_project_test' - } - ) + let(:other_project_files) do + { + 'other_project_file.yml' => <<~YAML + other_project_test: + script: other_project_test + YAML + } + end + + around do |example| + create_and_delete_files(project, project_files) do + create_and_delete_files(other_project, other_project_files) do + example.run end end + end + before do stub_full_request('https://gitlab.com/gitlab-org/gitlab/raw/1234/.hello.yml').to_return(body: remote_file_content) post_graphql_query diff --git a/spec/requests/api/graphql/ci/jobs_spec.rb b/spec/requests/api/graphql/ci/jobs_spec.rb index 7a1dc614dcf..131cdb77107 100644 --- a/spec/requests/api/graphql/ci/jobs_spec.rb +++ b/spec/requests/api/graphql/ci/jobs_spec.rb @@ -88,10 +88,10 @@ RSpec.describe 'Query.project.pipeline', feature_category: :continuous_integrati build_stage = create(:ci_stage, position: 2, name: 'build', project: project, pipeline: pipeline) test_stage = create(:ci_stage, position: 3, name: 'test', project: project, pipeline: pipeline) - create(:ci_build, pipeline: pipeline, name: 'docker 1 2', scheduling_type: :stage, stage: build_stage, stage_idx: build_stage.position) - create(:ci_build, pipeline: pipeline, name: 'docker 2 2', stage: build_stage, stage_idx: build_stage.position, scheduling_type: :dag) - create(:ci_build, pipeline: pipeline, name: 'rspec 1 2', scheduling_type: :stage, stage: test_stage, stage_idx: test_stage.position) - test_job = create(:ci_build, pipeline: pipeline, name: 'rspec 2 2', scheduling_type: :dag, stage: test_stage, stage_idx: test_stage.position) + create(:ci_build, pipeline: pipeline, name: 'docker 1 2', scheduling_type: :stage, ci_stage: build_stage, stage_idx: build_stage.position) + create(:ci_build, pipeline: pipeline, name: 'docker 2 2', ci_stage: build_stage, stage_idx: build_stage.position, scheduling_type: :dag) + create(:ci_build, pipeline: pipeline, name: 'rspec 1 2', scheduling_type: :stage, ci_stage: test_stage, stage_idx: test_stage.position) + test_job = create(:ci_build, pipeline: pipeline, name: 'rspec 2 2', scheduling_type: :dag, ci_stage: test_stage, stage_idx: test_stage.position) create(:ci_build_need, build: test_job, name: 'my test job') end diff --git a/spec/requests/api/graphql/group/merge_requests_spec.rb b/spec/requests/api/graphql/group/merge_requests_spec.rb index 6976685ecc0..adaee3031a9 100644 --- a/spec/requests/api/graphql/group/merge_requests_spec.rb +++ b/spec/requests/api/graphql/group/merge_requests_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' # Based on ee/spec/requests/api/epics_spec.rb # Should follow closely in order to ensure all situations are covered -RSpec.describe 'Query.group.mergeRequests', feature_category: :code_review do +RSpec.describe 'Query.group.mergeRequests', feature_category: :code_review_workflow do include GraphqlHelpers let_it_be(:group) { create(:group) } diff --git a/spec/requests/api/graphql/group_query_spec.rb b/spec/requests/api/graphql/group_query_spec.rb index bc288c0a98b..ce5816999a6 100644 --- a/spec/requests/api/graphql/group_query_spec.rb +++ b/spec/requests/api/graphql/group_query_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' # Based on spec/requests/api/groups_spec.rb # Should follow closely in order to ensure all situations are covered -RSpec.describe 'getting group information', feature_category: :subgroups do +RSpec.describe 'getting group information', :with_license, feature_category: :subgroups do include GraphqlHelpers include UploadHelpers diff --git a/spec/requests/api/graphql/issues_spec.rb b/spec/requests/api/graphql/issues_spec.rb index ba6f8ec2cab..e67c92d6c33 100644 --- a/spec/requests/api/graphql/issues_spec.rb +++ b/spec/requests/api/graphql/issues_spec.rb @@ -2,11 +2,13 @@ require 'spec_helper' +# rubocop:disable RSpec/MultipleMemoizedHelpers RSpec.describe 'getting an issue list at root level', feature_category: :team_planning do include GraphqlHelpers let_it_be(:developer) { create(:user) } let_it_be(:reporter) { create(:user) } + let_it_be(:current_user) { developer } let_it_be(:group1) { create(:group).tap { |group| group.add_developer(developer) } } let_it_be(:group2) { create(:group).tap { |group| group.add_developer(developer) } } let_it_be(:project_a) { create(:project, :repository, :public, group: group1) } @@ -82,9 +84,11 @@ RSpec.describe 'getting an issue list at root level', feature_category: :team_pl end let_it_be(:issues, reload: true) { [issue_a, issue_b, issue_c, issue_d, issue_e] } + # we need to always provide at least one filter to the query so it doesn't fail + let_it_be(:base_params) { { iids: issues.map { |issue| issue.iid.to_s } } } let(:issue_filter_params) { {} } - let(:current_user) { developer } + let(:all_query_params) { base_params.merge(**issue_filter_params) } let(:fields) do <<~QUERY nodes { id } @@ -95,6 +99,16 @@ RSpec.describe 'getting an issue list at root level', feature_category: :team_pl group2.add_reporter(reporter) end + shared_examples 'query that requires at least one filter' do + it 'requires at least one filter to be provided to the query' do + post_graphql(query, current_user: developer) + + expect(graphql_errors).to contain_exactly( + hash_including('message' => _('You must provide at least one filter argument for this query')) + ) + end + end + context 'when the root_level_issues_query feature flag is disabled' do before do stub_feature_flags(root_level_issues_query: false) @@ -107,20 +121,31 @@ RSpec.describe 'getting an issue list at root level', feature_category: :team_pl end end + context 'when no filters are provided' do + let(:all_query_params) { {} } + + it_behaves_like 'query that requires at least one filter' + end + + context 'when only non filter arguments are provided' do + let(:all_query_params) { { sort: :SEVERITY_ASC } } + + it_behaves_like 'query that requires at least one filter' + end + # All new specs should be added to the shared example if the change also # affects the `issues` query at the root level of the API. # Shared example also used in spec/requests/api/graphql/project/issues_spec.rb it_behaves_like 'graphql issue list request spec' do let_it_be(:external_user) { create(:user) } + let_it_be(:another_user) { reporter } let(:public_projects) { [project_a, project_c] } - let(:another_user) { reporter } let(:issue_nodes_path) { %w[issues nodes] } # filters let(:expected_negated_assignee_issues) { [issue_b, issue_c, issue_d, issue_e] } - let(:expected_unioned_assignee_issues) { [issue_a, issue_c] } let(:voted_issues) { [issue_a, issue_c] } let(:no_award_issues) { [issue_b, issue_d, issue_e] } let(:locked_discussion_issues) { [issue_b, issue_d] } @@ -148,9 +173,6 @@ RSpec.describe 'getting an issue list at root level', feature_category: :team_pl let(:same_project_issue2) { issue_e } before_all do - issue_a.assignee_ids = developer.id - issue_c.assignee_ids = reporter.id - create(:award_emoji, :upvote, user: developer, awardable: issue_a) create(:award_emoji, :upvote, user: developer, awardable: issue_c) end @@ -158,7 +180,7 @@ RSpec.describe 'getting an issue list at root level', feature_category: :team_pl def pagination_query(params) graphql_query_for( :issues, - params, + base_params.merge(**params.to_h), "#{page_info} nodes { id }" ) end @@ -177,6 +199,32 @@ RSpec.describe 'getting an issue list at root level', feature_category: :team_pl end end + context 'with rate limiting' do + it_behaves_like 'rate limited endpoint', rate_limit_key: :search_rate_limit, graphql: true do + let_it_be(:current_user) { developer } + + let(:error_message) do + 'This endpoint has been requested with the search argument too many times. Try again later.' + end + + def request + post_graphql(query({ search: 'test' }), current_user: developer) + end + end + + it_behaves_like 'rate limited endpoint', rate_limit_key: :search_rate_limit_unauthenticated, graphql: true do + let_it_be(:current_user) { nil } + + let(:error_message) do + 'This endpoint has been requested with the search argument too many times. Try again later.' + end + + def request + post_graphql(query({ search: 'test' })) + end + end + end + def execute_query post_query end @@ -185,7 +233,7 @@ RSpec.describe 'getting an issue list at root level', feature_category: :team_pl post_graphql(query, current_user: request_user) end - def query(params = issue_filter_params) + def query(params = all_query_params) graphql_query_for( :issues, params, @@ -193,3 +241,4 @@ RSpec.describe 'getting an issue list at root level', feature_category: :team_pl ) end end +# rubocop:enable RSpec/MultipleMemoizedHelpers diff --git a/spec/requests/api/graphql/merge_request/merge_request_spec.rb b/spec/requests/api/graphql/merge_request/merge_request_spec.rb index 213697bacc1..02ea7bac920 100644 --- a/spec/requests/api/graphql/merge_request/merge_request_spec.rb +++ b/spec/requests/api/graphql/merge_request/merge_request_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Query.merge_request(id)', feature_category: :code_review do +RSpec.describe 'Query.merge_request(id)', feature_category: :code_review_workflow do include GraphqlHelpers let_it_be(:project) { create(:project, :empty_repo) } diff --git a/spec/requests/api/graphql/mutations/achievements/create_spec.rb b/spec/requests/api/graphql/mutations/achievements/create_spec.rb new file mode 100644 index 00000000000..1713f050540 --- /dev/null +++ b/spec/requests/api/graphql/mutations/achievements/create_spec.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::Achievements::Create, feature_category: :users do + include GraphqlHelpers + include WorkhorseHelpers + + let_it_be(:developer) { create(:user) } + let_it_be(:maintainer) { create(:user) } + let_it_be(:group) { create(:group) } + + let(:mutation) { graphql_mutation(:achievements_create, params) } + let(:name) { 'Name' } + let(:description) { 'Description' } + let(:revokeable) { false } + let(:avatar) { fixture_file_upload("spec/fixtures/dk.png") } + let(:params) do + { + namespace_id: group.to_global_id, + name: name, + avatar: avatar, + description: description, + revokeable: revokeable + } + end + + subject { post_graphql_mutation_with_uploads(mutation, current_user: current_user) } + + def mutation_response + graphql_mutation_response(:achievements_create) + end + + before_all do + group.add_developer(developer) + group.add_maintainer(maintainer) + end + + context 'when the user does not have permission' do + let(:current_user) { developer } + let(:avatar) {} + + it_behaves_like 'a mutation that returns a top-level access error' + + it 'does not create an achievement' do + expect { subject }.not_to change { Achievements::Achievement.count } + end + end + + context 'when the user has permission' do + let(:current_user) { maintainer } + + context 'when the params are invalid' do + let(:name) {} + + it 'returns the validation error' do + subject + + expect(graphql_errors.to_s).to include('provided invalid value for name (Expected value to not be null)') + end + end + + it 'creates an achievement' do + expect { subject }.to change { Achievements::Achievement.count }.by(1) + end + + it 'returns the new achievement' do + subject + + expect(graphql_data_at(:achievements_create, :achievement)).to match a_hash_including( + 'name' => name, + 'namespace' => a_hash_including('id' => group.to_global_id.to_s), + 'description' => description, + 'revokeable' => revokeable + ) + end + end +end diff --git a/spec/requests/api/graphql/mutations/ci/job_play_spec.rb b/spec/requests/api/graphql/mutations/ci/job_play_spec.rb index 014a5e0f1c7..9ba80e51dee 100644 --- a/spec/requests/api/graphql/mutations/ci/job_play_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/job_play_spec.rb @@ -8,17 +8,25 @@ RSpec.describe 'JobPlay', feature_category: :continuous_integration do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project) } let_it_be(:pipeline) { create(:ci_pipeline, project: project, user: user) } - let_it_be(:job) { create(:ci_build, pipeline: pipeline, name: 'build') } + let_it_be(:job) { create(:ci_build, :playable, pipeline: pipeline, name: 'build') } - let(:mutation) do - variables = { + let(:variables) do + { id: job.to_global_id.to_s } + end + + let(:mutation) do graphql_mutation(:job_play, variables, <<-QL errors job { id + manualVariables { + nodes { + key + } + } } QL ) @@ -43,4 +51,29 @@ RSpec.describe 'JobPlay', feature_category: :continuous_integration do expect(response).to have_gitlab_http_status(:success) expect(mutation_response['job']['id']).to eq(job_id) end + + context 'when given variables' do + let(:variables) do + { + id: job.to_global_id.to_s, + variables: [ + { key: 'MANUAL_VAR_1', value: 'test var' }, + { key: 'MANUAL_VAR_2', value: 'test var 2' } + ] + } + end + + it 'provides those variables to the job', :aggregated_errors do + expect_next_instance_of(Ci::PlayBuildService) do |instance| + expect(instance).to receive(:execute).with(an_instance_of(Ci::Build), variables[:variables]).and_call_original + end + + post_graphql_mutation(mutation, current_user: user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['job']['manualVariables']['nodes'].pluck('key')).to contain_exactly( + 'MANUAL_VAR_1', 'MANUAL_VAR_2' + ) + end + end end diff --git a/spec/requests/api/graphql/mutations/groups/update_spec.rb b/spec/requests/api/graphql/mutations/groups/update_spec.rb index ea3d42a4463..a9acc593229 100644 --- a/spec/requests/api/graphql/mutations/groups/update_spec.rb +++ b/spec/requests/api/graphql/mutations/groups/update_spec.rb @@ -11,7 +11,7 @@ RSpec.describe 'GroupUpdate', feature_category: :subgroups do let(:variables) do { full_path: group.full_path, - shared_runners_setting: 'DISABLED_WITH_OVERRIDE' + shared_runners_setting: 'DISABLED_AND_OVERRIDABLE' } end @@ -52,6 +52,23 @@ RSpec.describe 'GroupUpdate', feature_category: :subgroups do expect(group.reload.shared_runners_setting).to eq(variables[:shared_runners_setting].downcase) end + context 'when using DISABLED_WITH_OVERRIDE (deprecated)' do + let(:variables) do + { + full_path: group.full_path, + shared_runners_setting: 'DISABLED_WITH_OVERRIDE' + } + end + + it 'updates shared runners settings with disabled_and_overridable' do + post_graphql_mutation(mutation, current_user: user) + + expect(response).to have_gitlab_http_status(:success) + expect(graphql_errors).to be_nil + expect(group.reload.shared_runners_setting).to eq('disabled_and_overridable') + end + end + context 'when bad arguments are provided' do let(:variables) { { full_path: '', shared_runners_setting: 'INVALID' } } diff --git a/spec/requests/api/graphql/mutations/members/groups/bulk_update_spec.rb b/spec/requests/api/graphql/mutations/members/groups/bulk_update_spec.rb new file mode 100644 index 00000000000..ad70129a7bc --- /dev/null +++ b/spec/requests/api/graphql/mutations/members/groups/bulk_update_spec.rb @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'GroupMemberBulkUpdate', feature_category: :subgroups do + include GraphqlHelpers + + let_it_be(:current_user) { create(:user) } + let_it_be(:user1) { create(:user) } + let_it_be(:user2) { create(:user) } + let_it_be(:group) { create(:group) } + let_it_be(:group_member1) { create(:group_member, group: group, user: user1) } + let_it_be(:group_member2) { create(:group_member, group: group, user: user2) } + let_it_be(:mutation_name) { :group_member_bulk_update } + + let(:input) do + { + 'group_id' => group.to_global_id.to_s, + 'user_ids' => [user1.to_global_id.to_s, user2.to_global_id.to_s], + 'access_level' => 'GUEST' + } + end + + let(:extra_params) { { expires_at: 10.days.from_now } } + let(:input_params) { input.merge(extra_params) } + let(:mutation) { graphql_mutation(mutation_name, input_params) } + let(:mutation_response) { graphql_mutation_response(mutation_name) } + + context 'when user is not logged-in' do + it_behaves_like 'a mutation that returns a top-level access error' + end + + context 'when user is not an owner' do + before do + group.add_maintainer(current_user) + end + + it_behaves_like 'a mutation that returns a top-level access error' + end + + context 'when user is an owner' do + before do + group.add_owner(current_user) + end + + shared_examples 'updates the user access role' do + specify do + post_graphql_mutation(mutation, current_user: current_user) + + new_access_levels = mutation_response['groupMembers'].map { |member| member['accessLevel']['integerValue'] } + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['errors']).to be_empty + expect(new_access_levels).to all(be Gitlab::Access::GUEST) + end + end + + it_behaves_like 'updates the user access role' + + context 'when inherited members are passed' do + let_it_be(:subgroup) { create(:group, parent: group) } + let_it_be(:subgroup_member) { create(:group_member, group: subgroup) } + + let(:input) do + { + 'group_id' => group.to_global_id.to_s, + 'user_ids' => [user1.to_global_id.to_s, user2.to_global_id.to_s, subgroup_member.user.to_global_id.to_s], + 'access_level' => 'GUEST' + } + end + + it 'does not update the members' do + post_graphql_mutation(mutation, current_user: current_user) + + error = Mutations::Members::Groups::BulkUpdate::INVALID_MEMBERS_ERROR + expect(json_response['errors'].first['message']).to include(error) + end + end + + context 'when members count is more than the allowed limit' do + let(:max_members_update_limit) { 1 } + + before do + stub_const('Mutations::Members::Groups::BulkUpdate::MAX_MEMBERS_UPDATE_LIMIT', max_members_update_limit) + end + + it 'does not update the members' do + post_graphql_mutation(mutation, current_user: current_user) + + error = Mutations::Members::Groups::BulkUpdate::MAX_MEMBERS_UPDATE_ERROR + expect(json_response['errors'].first['message']).to include(error) + end + end + + context 'when the update service raises access denied error' do + before do + allow_next_instance_of(Members::UpdateService) do |instance| + allow(instance).to receive(:execute).and_raise(Gitlab::Access::AccessDeniedError) + end + end + + it 'does not update the members' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(mutation_response['groupMembers']).to be_nil + expect(mutation_response['errors']) + .to contain_exactly("Unable to update members, please check user permissions.") + end + end + + context 'when the update service returns an error message' do + before do + allow_next_instance_of(Members::UpdateService) do |instance| + error_result = { + message: 'Expires at cannot be a date in the past', + status: :error, + members: [group_member1] + } + allow(instance).to receive(:execute).and_return(error_result) + end + end + + it 'will pass through the error' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(mutation_response['groupMembers'].first['id']).to eq(group_member1.to_global_id.to_s) + expect(mutation_response['errors']).to contain_exactly('Expires at cannot be a date in the past') + end + end + end +end diff --git a/spec/requests/api/graphql/mutations/merge_requests/create_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/create_spec.rb index c954fd50cc4..59f41c5e878 100644 --- a/spec/requests/api/graphql/mutations/merge_requests/create_spec.rb +++ b/spec/requests/api/graphql/mutations/merge_requests/create_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Creation of a new merge request', feature_category: :code_review do +RSpec.describe 'Creation of a new merge request', feature_category: :code_review_workflow do include GraphqlHelpers let_it_be(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/merge_requests/reviewer_rereview_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/reviewer_rereview_spec.rb index c41161eff2b..7a1b3982111 100644 --- a/spec/requests/api/graphql/mutations/merge_requests/reviewer_rereview_spec.rb +++ b/spec/requests/api/graphql/mutations/merge_requests/reviewer_rereview_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Setting assignees of a merge request', feature_category: :code_review do +RSpec.describe 'Setting assignees of a merge request', feature_category: :code_review_workflow do include GraphqlHelpers let(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb index 364d13291db..b5f2042c42a 100644 --- a/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb +++ b/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Setting assignees of a merge request', :assume_throttled, feature_category: :code_review do +RSpec.describe 'Setting assignees of a merge request', :assume_throttled, feature_category: :code_review_workflow do include GraphqlHelpers let_it_be(:project) { create(:project, :repository) } diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_draft_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_draft_spec.rb index b48a94fbeb9..0c2e2975350 100644 --- a/spec/requests/api/graphql/mutations/merge_requests/set_draft_spec.rb +++ b/spec/requests/api/graphql/mutations/merge_requests/set_draft_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Setting Draft status of a merge request', feature_category: :code_review do +RSpec.describe 'Setting Draft status of a merge request', feature_category: :code_review_workflow do include GraphqlHelpers let(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_locked_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_locked_spec.rb index d88982c508c..73a38adf723 100644 --- a/spec/requests/api/graphql/mutations/merge_requests/set_locked_spec.rb +++ b/spec/requests/api/graphql/mutations/merge_requests/set_locked_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Setting locked status of a merge request', feature_category: :code_review do +RSpec.describe 'Setting locked status of a merge request', feature_category: :code_review_workflow do include GraphqlHelpers let(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_milestone_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_milestone_spec.rb index a0f0e45d1fc..3907ebad9ce 100644 --- a/spec/requests/api/graphql/mutations/merge_requests/set_milestone_spec.rb +++ b/spec/requests/api/graphql/mutations/merge_requests/set_milestone_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Setting milestone of a merge request', feature_category: :code_review do +RSpec.describe 'Setting milestone of a merge request', feature_category: :code_review_workflow do include GraphqlHelpers let(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_reviewers_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_reviewers_spec.rb index a5be2a95c8b..fd87112be33 100644 --- a/spec/requests/api/graphql/mutations/merge_requests/set_reviewers_spec.rb +++ b/spec/requests/api/graphql/mutations/merge_requests/set_reviewers_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Setting reviewers of a merge request', :assume_throttled, feature_category: :code_review do +RSpec.describe 'Setting reviewers of a merge request', :assume_throttled, feature_category: :code_review_workflow do include GraphqlHelpers let_it_be(:project) { create(:project, :repository) } diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_subscription_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_subscription_spec.rb index daf1f529847..0e77b048646 100644 --- a/spec/requests/api/graphql/mutations/merge_requests/set_subscription_spec.rb +++ b/spec/requests/api/graphql/mutations/merge_requests/set_subscription_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Setting subscribed status of a merge request', feature_category: :code_review do +RSpec.describe 'Setting subscribed status of a merge request', feature_category: :code_review_workflow do include GraphqlHelpers it_behaves_like 'a subscribable resource api' do diff --git a/spec/requests/api/graphql/mutations/work_items/update_spec.rb b/spec/requests/api/graphql/mutations/work_items/update_spec.rb index 14cb18d04b8..b33a394d023 100644 --- a/spec/requests/api/graphql/mutations/work_items/update_spec.rb +++ b/spec/requests/api/graphql/mutations/work_items/update_spec.rb @@ -489,10 +489,10 @@ RSpec.describe 'Update a work item', feature_category: :team_planning do expect(response).to have_gitlab_http_status(:success) expect(widgets_response).to include( { - 'children' => { 'edges' => [ + 'children' => { 'edges' => match_array([ { 'node' => { 'id' => valid_child2.to_global_id.to_s } }, { 'node' => { 'id' => valid_child1.to_global_id.to_s } } - ] }, + ]) }, 'parent' => nil, 'type' => 'HIERARCHY' } diff --git a/spec/requests/api/graphql/project/branch_rules_spec.rb b/spec/requests/api/graphql/project/branch_rules_spec.rb index 7f6a66e2377..2ca37a49149 100644 --- a/spec/requests/api/graphql/project/branch_rules_spec.rb +++ b/spec/requests/api/graphql/project/branch_rules_spec.rb @@ -69,12 +69,6 @@ RSpec.describe 'getting list of branch rules for a project', feature_category: : before do create(:protected_branch, project: project) - allow_next_instance_of(Resolvers::ProjectResolver) do |resolver| - allow(resolver).to receive(:resolve) - .with(full_path: project.full_path) - .and_return(project) - end - allow(project.repository).to receive(:branch_names).and_call_original end it 'avoids N+1 queries', :use_sql_query_cache, :aggregate_failures do @@ -93,7 +87,6 @@ RSpec.describe 'getting list of branch rules for a project', feature_category: : end.not_to exceed_all_query_limit(control) expect_n_matching_branches_count_fields(3) - expect(project.repository).to have_received(:branch_names).at_least(2).times end def expect_n_matching_branches_count_fields(count) @@ -110,16 +103,16 @@ RSpec.describe 'getting list of branch rules for a project', feature_category: : let_it_be(:branch_name_b) { 'diff-*' } let_it_be(:branch_rules) { [branch_rule_a, branch_rule_b] } let_it_be(:branch_rule_a) do - create(:protected_branch, project: project, name: branch_name_a, id: 9999) + create(:protected_branch, project: project, name: branch_name_a) end let_it_be(:branch_rule_b) do - create(:protected_branch, project: project, name: branch_name_b, id: 10000) + create(:protected_branch, project: project, name: branch_name_b) end - # branchRules are returned in reverse order, newest first, sorted by primary_key. - let(:branch_rule_b_data) { branch_rules_data.dig(0, 'node') } + # branchRules are returned in alphabetical order let(:branch_rule_a_data) { branch_rules_data.dig(1, 'node') } + let(:branch_rule_b_data) { branch_rules_data.dig(0, 'node') } before do post_graphql(query, current_user: current_user, variables: variables) @@ -128,22 +121,28 @@ RSpec.describe 'getting list of branch rules for a project', feature_category: : it_behaves_like 'a working graphql query' it 'includes all fields', :use_sql_query_cache, :aggregate_failures do - expect(branch_rule_a_data['name']).to eq(branch_name_a) - expect(branch_rule_a_data['isDefault']).to be(true).or be(false) - expect(branch_rule_a_data['branchProtection']).to be_present - expect(branch_rule_a_data['matchingBranchesCount']).to eq(1) - expect(branch_rule_a_data['createdAt']).to be_present - expect(branch_rule_a_data['updatedAt']).to be_present + expect(branch_rule_a_data).to include( + 'name' => branch_name_a, + 'isDefault' => be_boolean, + 'isProtected' => true, + 'matchingBranchesCount' => 1, + 'branchProtection' => be_kind_of(Hash), + 'createdAt' => be_kind_of(String), + 'updatedAt' => be_kind_of(String) + ) wildcard_count = TestEnv::BRANCH_SHA.keys.count do |branch_name| branch_name.starts_with?('diff-') end - expect(branch_rule_b_data['name']).to eq(branch_name_b) - expect(branch_rule_b_data['isDefault']).to be(true).or be(false) - expect(branch_rule_b_data['branchProtection']).to be_present - expect(branch_rule_b_data['matchingBranchesCount']).to eq(wildcard_count) - expect(branch_rule_b_data['createdAt']).to be_present - expect(branch_rule_b_data['updatedAt']).to be_present + expect(branch_rule_b_data).to include( + 'name' => branch_name_b, + 'isDefault' => be_boolean, + 'isProtected' => true, + 'matchingBranchesCount' => wildcard_count, + 'branchProtection' => be_kind_of(Hash), + 'createdAt' => be_kind_of(String), + 'updatedAt' => be_kind_of(String) + ) end context 'when limiting the number of results' do diff --git a/spec/requests/api/graphql/project/issues_spec.rb b/spec/requests/api/graphql/project/issues_spec.rb index ec5e3c6f0de..cc41795f770 100644 --- a/spec/requests/api/graphql/project/issues_spec.rb +++ b/spec/requests/api/graphql/project/issues_spec.rb @@ -91,7 +91,6 @@ RSpec.describe 'getting an issue list for a project', feature_category: :team_pl # filters let(:expected_negated_assignee_issues) { [issue_b, issue_c, issue_d, issue_e] } - let(:expected_unioned_assignee_issues) { [issue_a, issue_b] } let(:voted_issues) { [issue_a] } let(:no_award_issues) { [issue_b, issue_c, issue_d, issue_e] } let(:locked_discussion_issues) { [issue_a] } @@ -119,9 +118,6 @@ RSpec.describe 'getting an issue list for a project', feature_category: :team_pl let(:same_project_issue2) { issue_b } before_all do - issue_a.assignee_ids = current_user.id - issue_b.assignee_ids = another_user.id - create(:award_emoji, :upvote, user: current_user, awardable: issue_a) end diff --git a/spec/requests/api/graphql/project/jobs_spec.rb b/spec/requests/api/graphql/project/jobs_spec.rb index d05d4a2f4b6..aea6cad9e62 100644 --- a/spec/requests/api/graphql/project/jobs_spec.rb +++ b/spec/requests/api/graphql/project/jobs_spec.rb @@ -33,10 +33,10 @@ RSpec.describe 'Query.project.jobs', feature_category: :continuous_integration d it 'does not generate N+1 queries', :request_store, :use_sql_query_cache do build_stage = create(:ci_stage, position: 1, name: 'build', project: project, pipeline: pipeline) test_stage = create(:ci_stage, position: 2, name: 'test', project: project, pipeline: pipeline) - create(:ci_build, pipeline: pipeline, stage_idx: build_stage.position, name: 'docker 1 2', stage: build_stage) - create(:ci_build, pipeline: pipeline, stage_idx: build_stage.position, name: 'docker 2 2', stage: build_stage) - create(:ci_build, pipeline: pipeline, stage_idx: test_stage.position, name: 'rspec 1 2', stage: test_stage) - test_job = create(:ci_build, pipeline: pipeline, stage_idx: test_stage.position, name: 'rspec 2 2', stage: test_stage) + create(:ci_build, pipeline: pipeline, name: 'docker 1 2', ci_stage: build_stage) + create(:ci_build, pipeline: pipeline, name: 'docker 2 2', ci_stage: build_stage) + create(:ci_build, pipeline: pipeline, name: 'rspec 1 2', ci_stage: test_stage) + test_job = create(:ci_build, pipeline: pipeline, name: 'rspec 2 2', ci_stage: test_stage) create(:ci_build_need, build: test_job, name: 'docker 1 2') post_graphql(query, current_user: user) @@ -45,8 +45,8 @@ RSpec.describe 'Query.project.jobs', feature_category: :continuous_integration d post_graphql(query, current_user: user) end - create(:ci_build, name: 'test-a', stage: test_stage, stage_idx: test_stage.position, pipeline: pipeline) - test_b_job = create(:ci_build, name: 'test-b', stage: test_stage, stage_idx: test_stage.position, pipeline: pipeline) + create(:ci_build, name: 'test-a', ci_stage: test_stage, pipeline: pipeline) + test_b_job = create(:ci_build, name: 'test-b', ci_stage: test_stage, pipeline: pipeline) create(:ci_build_need, build: test_b_job, name: 'docker 2 2') expect do diff --git a/spec/requests/api/graphql/project/merge_request/diff_notes_spec.rb b/spec/requests/api/graphql/project/merge_request/diff_notes_spec.rb index 36e148468bc..4884e04ab23 100644 --- a/spec/requests/api/graphql/project/merge_request/diff_notes_spec.rb +++ b/spec/requests/api/graphql/project/merge_request/diff_notes_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'getting notes for a merge request', feature_category: :code_review do +RSpec.describe 'getting notes for a merge request', feature_category: :code_review_workflow do include GraphqlHelpers let_it_be(:noteable) { create(:merge_request) } diff --git a/spec/requests/api/graphql/project/merge_request_spec.rb b/spec/requests/api/graphql/project/merge_request_spec.rb index b7aafdf305a..6aa96cfc070 100644 --- a/spec/requests/api/graphql/project/merge_request_spec.rb +++ b/spec/requests/api/graphql/project/merge_request_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'getting merge request information nested in a project', feature_category: :code_review do +RSpec.describe 'getting merge request information nested in a project', feature_category: :code_review_workflow do include GraphqlHelpers let_it_be(:project) { create(:project, :repository, :public) } diff --git a/spec/requests/api/graphql/project/merge_requests_spec.rb b/spec/requests/api/graphql/project/merge_requests_spec.rb index b3b4c8fe0d5..8407faa967e 100644 --- a/spec/requests/api/graphql/project/merge_requests_spec.rb +++ b/spec/requests/api/graphql/project/merge_requests_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'getting merge request listings nested in a project', feature_category: :code_review do +RSpec.describe 'getting merge request listings nested in a project', feature_category: :code_review_workflow do include GraphqlHelpers let_it_be(:group) { create(:group) } diff --git a/spec/requests/api/graphql/project/pipeline_spec.rb b/spec/requests/api/graphql/project/pipeline_spec.rb index 0eeb382510e..abfdf07c288 100644 --- a/spec/requests/api/graphql/project/pipeline_spec.rb +++ b/spec/requests/api/graphql/project/pipeline_spec.rb @@ -348,10 +348,10 @@ RSpec.describe 'getting pipeline information nested in a project', feature_categ it 'does not generate N+1 queries', :request_store, :use_sql_query_cache do build_stage = create(:ci_stage, position: 1, name: 'build', project: project, pipeline: pipeline) test_stage = create(:ci_stage, position: 2, name: 'test', project: project, pipeline: pipeline) - create(:ci_build, pipeline: pipeline, stage_idx: build_stage.position, name: 'docker 1 2', stage: build_stage) - create(:ci_build, pipeline: pipeline, stage_idx: build_stage.position, name: 'docker 2 2', stage: build_stage) - create(:ci_build, pipeline: pipeline, stage_idx: test_stage.position, name: 'rspec 1 2', stage: test_stage) - test_job = create(:ci_build, pipeline: pipeline, stage_idx: test_stage.position, name: 'rspec 2 2', stage: test_stage) + create(:ci_build, pipeline: pipeline, name: 'docker 1 2', ci_stage: build_stage) + create(:ci_build, pipeline: pipeline, name: 'docker 2 2', ci_stage: build_stage) + create(:ci_build, pipeline: pipeline, name: 'rspec 1 2', ci_stage: test_stage) + test_job = create(:ci_build, pipeline: pipeline, name: 'rspec 2 2', ci_stage: test_stage) create(:ci_build_need, build: test_job, name: 'docker 1 2') post_graphql(query, current_user: current_user) @@ -360,8 +360,8 @@ RSpec.describe 'getting pipeline information nested in a project', feature_categ post_graphql(query, current_user: current_user) end - create(:ci_build, name: 'test-a', stage: test_stage, stage_idx: test_stage.position, pipeline: pipeline) - test_b_job = create(:ci_build, name: 'test-b', stage: test_stage, stage_idx: test_stage.position, pipeline: pipeline) + create(:ci_build, name: 'test-a', ci_stage: test_stage, pipeline: pipeline) + test_b_job = create(:ci_build, name: 'test-b', ci_stage: test_stage, pipeline: pipeline) create(:ci_build_need, build: test_b_job, name: 'docker 2 2') expect do @@ -409,7 +409,8 @@ RSpec.describe 'getting pipeline information nested in a project', feature_categ it 'does not generate N+1 queries', :request_store, :use_sql_query_cache do # create extra statuses - create(:generic_commit_status, :pending, name: 'generic-build-a', pipeline: pipeline, stage_idx: 0, stage: 'build') + external_stage = create(:ci_stage, position: 10, name: 'external', project: project, pipeline: pipeline) + create(:generic_commit_status, :pending, name: 'generic-build-a', pipeline: pipeline, ci_stage: external_stage) create(:ci_bridge, :failed, name: 'deploy-a', pipeline: pipeline, stage_idx: 2, stage: 'deploy') # warm up @@ -419,7 +420,7 @@ RSpec.describe 'getting pipeline information nested in a project', feature_categ post_graphql(query, current_user: current_user) end - create(:generic_commit_status, :pending, name: 'generic-build-b', pipeline: pipeline, stage_idx: 0, stage: 'build') + create(:generic_commit_status, :pending, name: 'generic-build-b', pipeline: pipeline, ci_stage: external_stage) create(:ci_build, :failed, name: 'test-a', pipeline: pipeline, stage_idx: 1, stage: 'test') create(:ci_build, :running, name: 'test-b', pipeline: pipeline, stage_idx: 1, stage: 'test') create(:ci_build, :pending, name: 'deploy-b', pipeline: pipeline, stage_idx: 2, stage: 'deploy') diff --git a/spec/requests/api/graphql/project/runners_spec.rb b/spec/requests/api/graphql/project/runners_spec.rb index 7304de7bec6..bee7ce2e372 100644 --- a/spec/requests/api/graphql/project/runners_spec.rb +++ b/spec/requests/api/graphql/project/runners_spec.rb @@ -53,16 +53,4 @@ RSpec.describe 'Project.runners', feature_category: :runner do expect(graphql_data_at(:project, :runners, :nodes)).to be_empty end end - - context 'when on_demand_scans_runner_tags feature flag is disabled' do - before do - stub_feature_flags(on_demand_scans_runner_tags: false) - end - - it 'returns no runners' do - post_graphql(query, current_user: user) - - expect(graphql_data_at(:project, :runners, :nodes)).to be_empty - end - end end diff --git a/spec/requests/api/graphql/project/work_items_spec.rb b/spec/requests/api/graphql/project/work_items_spec.rb index a59da706a8a..de35c943749 100644 --- a/spec/requests/api/graphql/project/work_items_spec.rb +++ b/spec/requests/api/graphql/project/work_items_spec.rb @@ -263,7 +263,7 @@ RSpec.describe 'getting a work item list for a project', feature_category: :team GRAPHQL end - before do + before_all do create_notes(item1, "some note1") create_notes(item2, "some note2") end diff --git a/spec/requests/api/graphql/user_spec.rb b/spec/requests/api/graphql/user_spec.rb index 2e1e4971767..3e82d783a18 100644 --- a/spec/requests/api/graphql/user_spec.rb +++ b/spec/requests/api/graphql/user_spec.rb @@ -58,4 +58,45 @@ RSpec.describe 'User', feature_category: :users do ) end end + + describe 'email fields' do + before_all do + current_user.commit_email = current_user.emails.first.email + current_user.save! + end + + let_it_be(:query) do + graphql_query_for( + :user, + { username: current_user.username }, + 'emails { nodes { email } } commitEmail namespaceCommitEmails { nodes { id } }' + ) + end + + let_it_be(:email_1) { create(:email, user: current_user) } + let_it_be(:email_2) { create(:email, user: current_user) } + let_it_be(:namespace_commit_email_1) { create(:namespace_commit_email, email: email_1) } + let_it_be(:namespace_commit_email_2) { create(:namespace_commit_email, email: email_2) } + + context 'with permission' do + it 'returns the relevant email details' do + post_graphql(query, current_user: current_user) + + expect(graphql_data['user']['emails']['nodes'].pluck('email')).to match_array( + current_user.emails.map(&:email)) + expect(graphql_data['user']['namespaceCommitEmails']['nodes']).not_to be_empty + expect(graphql_data['user']['commitEmail']).to eq(current_user.commit_email) + end + end + + context 'without permission' do + it 'does not return email details' do + post_graphql(query, current_user: create(:user)) + + expect(graphql_data['user']['emails']['nodes']).to be_empty + expect(graphql_data['user']['namespaceCommitEmails']['nodes']).to be_empty + expect(graphql_data['user']['commitEmail']).to be_nil + end + end + end end diff --git a/spec/requests/api/graphql/work_item_spec.rb b/spec/requests/api/graphql/work_item_spec.rb index df7dbaea420..6b5d437df83 100644 --- a/spec/requests/api/graphql/work_item_spec.rb +++ b/spec/requests/api/graphql/work_item_spec.rb @@ -193,6 +193,24 @@ RSpec.describe 'Query.work_item(id)', feature_category: :team_planning do ) end end + + context 'when ordered by default by created_at' do + let_it_be(:newest_child) { create(:work_item, :task, project: project, created_at: 5.minutes.from_now) } + let_it_be(:oldest_child) { create(:work_item, :task, project: project, created_at: 5.minutes.ago) } + let_it_be(:newest_link) { create(:parent_link, work_item_parent: work_item, work_item: newest_child) } + let_it_be(:oldest_link) { create(:parent_link, work_item_parent: work_item, work_item: oldest_child) } + + let(:hierarchy_widget) { work_item_data['widgets'].find { |widget| widget['type'] == 'HIERARCHY' } } + let(:hierarchy_children) { hierarchy_widget['children']['nodes'] } + + it 'places the oldest child item to the beginning of the children list' do + expect(hierarchy_children.first['id']).to eq(oldest_child.to_gid.to_s) + end + + it 'places the newest child item to the end of the children list' do + expect(hierarchy_children.last['id']).to eq(newest_child.to_gid.to_s) + end + end end describe 'assignees widget' do diff --git a/spec/requests/api/group_boards_spec.rb b/spec/requests/api/group_boards_spec.rb index 01f0e6e2061..acc30b2c137 100644 --- a/spec/requests/api/group_boards_spec.rb +++ b/spec/requests/api/group_boards_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::GroupBoards, feature_category: :team_planning do +RSpec.describe API::GroupBoards, :with_license, feature_category: :team_planning do let_it_be(:user) { create(:user) } let_it_be(:non_member) { create(:user) } let_it_be(:guest) { create(:user) } diff --git a/spec/requests/api/group_export_spec.rb b/spec/requests/api/group_export_spec.rb index 565365506a7..9dd5fe6f7c4 100644 --- a/spec/requests/api/group_export_spec.rb +++ b/spec/requests/api/group_export_spec.rb @@ -173,6 +173,8 @@ RSpec.describe API::GroupExport, feature_category: :importers do let(:status_path) { "/groups/#{group.id}/export_relations/status" } before do + stub_application_setting(bulk_import_enabled: true) + group.add_owner(user) end @@ -212,11 +214,12 @@ RSpec.describe API::GroupExport, feature_category: :importers do context 'when export_file.file does not exist' do it 'returns 404' do - allow(upload).to receive(:export_file).and_return(nil) + allow(export).to receive(:upload).and_return(nil) get api(download_path, user) expect(response).to have_gitlab_http_status(:not_found) + expect(json_response['message']).to eq('404 Not found') end end end @@ -234,5 +237,11 @@ RSpec.describe API::GroupExport, feature_category: :importers do expect(json_response.pluck('status')).to contain_exactly(-1, 0, 1) end end + + context 'when bulk import is disabled' do + it_behaves_like '404 response' do + let(:request) { get api(path, user) } + end + end end end diff --git a/spec/requests/api/import_github_spec.rb b/spec/requests/api/import_github_spec.rb index dce82f1cf37..0d75bb94144 100644 --- a/spec/requests/api/import_github_spec.rb +++ b/spec/requests/api/import_github_spec.rb @@ -6,33 +6,35 @@ RSpec.describe API::ImportGithub, feature_category: :importers do let(:token) { "asdasd12345" } let(:provider) { :github } let(:access_params) { { github_access_token: token } } + let(:provider_username) { user.username } + let(:provider_user) { double('provider', login: provider_username).as_null_object } + let(:provider_repo) do + { + name: 'vim', + full_name: "#{provider_username}/vim", + owner: double('provider', login: provider_username), + description: 'provider', + private: false, + clone_url: 'https://fake.url/vim.git', + has_wiki: true + } + end - describe "POST /import/github" do - let(:user) { create(:user) } - let(:project) { create(:project) } - let(:provider_username) { user.username } - let(:provider_user) { double('provider', login: provider_username) } - let(:provider_repo) do - { - name: 'vim', - full_name: "#{provider_username}/vim", - owner: double('provider', login: provider_username), - description: 'provider', - private: false, - clone_url: 'https://fake.url/vim.git', - has_wiki: true - } - end + let(:client) { double('client', user: provider_user, repository: provider_repo) } - before do - Grape::Endpoint.before_each do |endpoint| - allow(endpoint).to receive(:client).and_return(double('client', user: provider_user, repository: provider_repo).as_null_object) - end + before do + Grape::Endpoint.before_each do |endpoint| + allow(endpoint).to receive(:client).and_return(client) end + end - after do - Grape::Endpoint.before_each nil - end + after do + Grape::Endpoint.before_each nil + end + + describe "POST /import/github" do + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project) } it 'rejects requests when Github Importer is disabled' do stub_application_setting(import_sources: nil) @@ -90,6 +92,23 @@ RSpec.describe API::ImportGithub, feature_category: :importers do expect(response).to have_gitlab_http_status(:unprocessable_entity) end + context 'when target_namespace is blank' do + it 'returns 400 response' do + allow(Gitlab::LegacyGithubImport::ProjectCreator) + .to receive(:new).with(provider_repo, provider_repo[:name], user.namespace, user, type: provider, **access_params) + .and_return(double(execute: project)) + + post api("/import/github", user), params: { + target_namespace: '', + personal_access_token: token, + repo_id: non_existing_record_id + } + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['error']).to eq 'target_namespace is empty' + end + end + context 'when unauthenticated user' do it 'returns 403 response' do post api("/import/github"), params: { @@ -150,4 +169,78 @@ RSpec.describe API::ImportGithub, feature_category: :importers do end end end + + describe 'POST /import/github/gists' do + let_it_be(:user) { create(:user) } + let(:params) { { personal_access_token: token } } + + context 'when feature github_import_gists is enabled' do + before do + stub_feature_flags(github_import_gists: true) + end + + context 'when gists import was started' do + before do + allow(Import::Github::GistsImportService) + .to receive(:new).with(user, client, access_params) + .and_return(double(execute: { status: :success })) + end + + it 'returns 202' do + post api('/import/github/gists', user), params: params + + expect(response).to have_gitlab_http_status(:accepted) + end + end + + context 'when gists import is in progress' do + before do + allow(Import::Github::GistsImportService) + .to receive(:new).with(user, client, access_params) + .and_return(double(execute: { status: :error, message: 'Import already in progress', http_status: :unprocessable_entity })) + end + + it 'returns 422 error' do + post api('/import/github/gists', user), params: params + + expect(response).to have_gitlab_http_status(:unprocessable_entity) + expect(json_response['errors']).to eq('Import already in progress') + end + end + + context 'when unauthenticated user' do + it 'returns 403 error' do + post api('/import/github/gists'), params: params + + expect(response).to have_gitlab_http_status(:unauthorized) + end + end + + context 'when rate limit reached' do + before do + allow(Import::Github::GistsImportService) + .to receive(:new).with(user, client, access_params) + .and_raise(Gitlab::GithubImport::RateLimitError) + end + + it 'returns 429 error' do + post api('/import/github/gists', user), params: params + + expect(response).to have_gitlab_http_status(:too_many_requests) + end + end + end + + context 'when feature github_import_gists is disabled' do + before do + stub_feature_flags(github_import_gists: false) + end + + it 'returns 404 error' do + post api('/import/github/gists', user), params: params + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end end diff --git a/spec/requests/api/internal/base_spec.rb b/spec/requests/api/internal/base_spec.rb index f9284f21aaa..767f3e8b5b5 100644 --- a/spec/requests/api/internal/base_spec.rb +++ b/spec/requests/api/internal/base_spec.rb @@ -453,28 +453,10 @@ RSpec.describe API::Internal::Base, feature_category: :authentication_and_author expect(json_response['message']['error']).to eq('This endpoint has been requested too many times. Try again later.') end - context 'when rate_limit_gitlab_shell feature flag is disabled' do - before do - stub_feature_flags(rate_limit_gitlab_shell: false) - end - - it 'is not throttled by rate limiter' do - expect(::Gitlab::ApplicationRateLimiter).not_to receive(:throttled?) - - subject - end - end + it 'is not throttled by rate limiter' do + expect(::Gitlab::ApplicationRateLimiter).not_to receive(:throttled?) - context 'when rate_limit_gitlab_shell_by_ip feature flag is disabled' do - before do - stub_feature_flags(rate_limit_gitlab_shell_by_ip: false) - end - - it 'is not throttled by rate limiter' do - expect(::Gitlab::ApplicationRateLimiter).not_to receive(:throttled?) - - subject - end + subject end context 'when the IP is in a trusted range' do diff --git a/spec/requests/api/issues/get_project_issues_spec.rb b/spec/requests/api/issues/get_project_issues_spec.rb index 70966d23576..6fc3903103b 100644 --- a/spec/requests/api/issues/get_project_issues_spec.rb +++ b/spec/requests/api/issues/get_project_issues_spec.rb @@ -11,15 +11,24 @@ RSpec.describe API::Issues, feature_category: :team_planning do let_it_be(:group) { create(:group, :public) } - let(:user2) { create(:user) } - let(:non_member) { create(:user) } + let_it_be(:user2) { create(:user) } + let_it_be(:non_member) { create(:user) } let_it_be(:guest) { create(:user) } let_it_be(:author) { create(:author) } let_it_be(:assignee) { create(:assignee) } - let(:admin) { create(:user, :admin) } - let(:issue_title) { 'foo' } - let(:issue_description) { 'closed' } - let!(:closed_issue) do + let_it_be(:admin) { create(:user, :admin) } + + let_it_be(:milestone) { create(:milestone, title: '1.0.0', project: project) } + let_it_be(:empty_milestone) do + create(:milestone, title: '2.0.0', project: project) + end + + let(:no_milestone_title) { 'None' } + let(:any_milestone_title) { 'Any' } + + let_it_be(:issue_title) { 'foo' } + let_it_be(:issue_description) { 'closed' } + let_it_be(:closed_issue) do create :closed_issue, author: user, assignees: [user], @@ -31,7 +40,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do closed_at: 1.hour.ago end - let!(:confidential_issue) do + let_it_be(:confidential_issue) do create :issue, :confidential, project: project, @@ -41,7 +50,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do updated_at: 2.hours.ago end - let!(:issue) do + let_it_be(:issue) do create :issue, author: user, assignees: [user], @@ -53,22 +62,12 @@ RSpec.describe API::Issues, feature_category: :team_planning do description: issue_description end - let_it_be(:label) do - create(:label, title: 'label', color: '#FFAABB', project: project) - end + let_it_be(:label) { create(:label, title: 'label', color: '#FFAABB', project: project) } + let_it_be(:label_link) { create(:label_link, label: label, target: issue) } - let!(:label_link) { create(:label_link, label: label, target: issue) } - let(:milestone) { create(:milestone, title: '1.0.0', project: project) } - let_it_be(:empty_milestone) do - create(:milestone, title: '2.0.0', project: project) - end + let_it_be(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) } - let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) } - - let(:no_milestone_title) { 'None' } - let(:any_milestone_title) { 'Any' } - - let!(:merge_request1) do + let_it_be(:merge_request1) do create(:merge_request, :simple, author: user, @@ -77,7 +76,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do description: "closes #{issue.to_reference}") end - let!(:merge_request2) do + let_it_be(:merge_request2) do create(:merge_request, :simple, author: user, @@ -101,7 +100,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do shared_examples 'project issues statistics' do it 'returns project issues statistics' do - get api("/issues_statistics", user), params: params + get api("/projects/#{project.id}/issues_statistics", current_user), params: params expect(response).to have_gitlab_http_status(:ok) expect(json_response['statistics']).not_to be_nil @@ -138,6 +137,8 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'issues_statistics' do + let(:current_user) { nil } + context 'no state is treated as all state' do let(:params) { {} } let(:counts) { { all: 2, closed: 1, opened: 1 } } @@ -534,30 +535,32 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'issues_statistics' do + let(:current_user) { user } + context 'no state is treated as all state' do let(:params) { {} } - let(:counts) { { all: 2, closed: 1, opened: 1 } } + let(:counts) { { all: 3, closed: 1, opened: 2 } } it_behaves_like 'project issues statistics' end context 'statistics when all state is passed' do let(:params) { { state: :all } } - let(:counts) { { all: 2, closed: 1, opened: 1 } } + let(:counts) { { all: 3, closed: 1, opened: 2 } } it_behaves_like 'project issues statistics' end context 'closed state is treated as all state' do let(:params) { { state: :closed } } - let(:counts) { { all: 2, closed: 1, opened: 1 } } + let(:counts) { { all: 3, closed: 1, opened: 2 } } it_behaves_like 'project issues statistics' end context 'opened state is treated as all state' do let(:params) { { state: :opened } } - let(:counts) { { all: 2, closed: 1, opened: 1 } } + let(:counts) { { all: 3, closed: 1, opened: 2 } } it_behaves_like 'project issues statistics' end @@ -592,7 +595,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do context 'sort does not affect statistics ' do let(:params) { { state: :opened, order_by: 'updated_at' } } - let(:counts) { { all: 2, closed: 1, opened: 1 } } + let(:counts) { { all: 3, closed: 1, opened: 2 } } it_behaves_like 'project issues statistics' end diff --git a/spec/requests/api/issues/issues_spec.rb b/spec/requests/api/issues/issues_spec.rb index 94f0443e14a..b89db82b150 100644 --- a/spec/requests/api/issues/issues_spec.rb +++ b/spec/requests/api/issues/issues_spec.rb @@ -145,6 +145,11 @@ RSpec.describe API::Issues, feature_category: :team_planning do let(:result) { issuable.id } end + it_behaves_like 'issuable API rate-limited search' do + let(:url) { '/issues' } + let(:issuable) { issue } + end + it 'returns authentication error without any scope' do get api('/issues') diff --git a/spec/requests/api/markdown_golden_master_spec.rb b/spec/requests/api/markdown_golden_master_spec.rb deleted file mode 100644 index 1bb5a1d67ae..00000000000 --- a/spec/requests/api/markdown_golden_master_spec.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -# See spec/fixtures/markdown/markdown_golden_master_examples.yml for documentation on how this spec works. -RSpec.describe API::Markdown, 'Golden Master', feature_category: :team_planning do - markdown_yml_file_path = File.expand_path('../../fixtures/markdown/markdown_golden_master_examples.yml', __dir__) - include_context 'API::Markdown Golden Master shared context', markdown_yml_file_path -end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 0b69000ae7e..4cd93603c31 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -55,6 +55,11 @@ RSpec.describe API::MergeRequests, feature_category: :source_code_management do let(:issuable) { merge_request } let(:result) { [merge_request_merged.id, merge_request_locked.id, merge_request_closed.id, merge_request.id] } end + + it_behaves_like 'issuable API rate-limited search' do + let(:url) { endpoint_path } + let(:issuable) { merge_request } + end end context 'when authenticated' do @@ -663,6 +668,11 @@ RSpec.describe API::MergeRequests, feature_category: :source_code_management do let(:result) { [merge_request_merged.id, merge_request_locked.id, merge_request_closed.id, merge_request.id] } end + it_behaves_like 'issuable API rate-limited search' do + let(:url) { '/merge_requests' } + let(:issuable) { merge_request } + end + it "returns authentication error without any scope" do get api("/merge_requests") diff --git a/spec/requests/api/ml/mlflow_spec.rb b/spec/requests/api/ml/mlflow_spec.rb index c1ed7d56ba4..fdf115f7e92 100644 --- a/spec/requests/api/ml/mlflow_spec.rb +++ b/spec/requests/api/ml/mlflow_spec.rb @@ -347,6 +347,7 @@ RSpec.describe API::Ml::Mlflow, feature_category: :mlops do { experiment_id: experiment.iid.to_s, start_time: Time.now.to_i, + run_name: "A new Run", tags: [ { key: 'hello', value: 'world' } ] @@ -359,6 +360,7 @@ RSpec.describe API::Ml::Mlflow, feature_category: :mlops do expected_properties = { 'experiment_id' => params[:experiment_id], 'user_id' => current_user.id.to_s, + 'run_name' => "A new Run", 'start_time' => params[:start_time], 'status' => 'RUNNING', 'lifecycle_stage' => 'active' @@ -407,7 +409,7 @@ RSpec.describe API::Ml::Mlflow, feature_category: :mlops do 'experiment_id' => candidate.experiment.iid.to_s, 'user_id' => candidate.user.id.to_s, 'start_time' => candidate.start_time, - 'artifact_uri' => "http://www.example.com/api/v4/projects/#{project_id}/packages/generic/ml_candidate_#{candidate.iid}/-/", + 'artifact_uri' => "http://www.example.com/api/v4/projects/#{project_id}/packages/generic/ml_candidate_#{candidate.id}/-/", 'status' => "RUNNING", 'lifecycle_stage' => "active" } @@ -426,8 +428,8 @@ RSpec.describe API::Ml::Mlflow, feature_category: :mlops do { 'key' => candidate.params[1].name, 'value' => candidate.params[1].value } ], 'tags' => [ - { 'key' => 'metadata_1', 'value' => 'value1' }, - { 'key' => 'metadata_2', 'value' => 'value2' } + { 'key' => candidate.metadata[0].name, 'value' => candidate.metadata[0].value }, + { 'key' => candidate.metadata[1].name, 'value' => candidate.metadata[1].value } ] }) end @@ -450,7 +452,7 @@ RSpec.describe API::Ml::Mlflow, feature_category: :mlops do 'user_id' => candidate.user.id.to_s, 'start_time' => candidate.start_time, 'end_time' => params[:end_time], - 'artifact_uri' => "http://www.example.com/api/v4/projects/#{project_id}/packages/generic/ml_candidate_#{candidate.iid}/-/", + 'artifact_uri' => "http://www.example.com/api/v4/projects/#{project_id}/packages/generic/ml_candidate_#{candidate.id}/-/", 'status' => 'FAILED', 'lifecycle_stage' => 'active' } diff --git a/spec/requests/api/nuget_group_packages_spec.rb b/spec/requests/api/nuget_group_packages_spec.rb index 9de612f7bc7..4335ad75ab6 100644 --- a/spec/requests/api/nuget_group_packages_spec.rb +++ b/spec/requests/api/nuget_group_packages_spec.rb @@ -17,25 +17,51 @@ RSpec.describe API::NugetGroupPackages, feature_category: :package_registry do shared_examples 'handling all endpoints' do describe 'GET /api/v4/groups/:id/-/packages/nuget' do - it_behaves_like 'handling nuget service requests', anonymous_requests_example_name: 'rejects nuget packages access', anonymous_requests_status: :unauthorized do + it_behaves_like 'handling nuget service requests', + example_names_with_status: { + anonymous_requests_example_name: 'rejects nuget packages access', + anonymous_requests_status: :unauthorized, + guest_requests_example_name: 'process nuget service index request', + guest_requests_status: :success + } do let(:url) { "/groups/#{target.id}/-/packages/nuget/index.json" } end end describe 'GET /api/v4/groups/:id/-/packages/nuget/metadata/*package_name/index' do - it_behaves_like 'handling nuget metadata requests with package name', anonymous_requests_example_name: 'rejects nuget packages access', anonymous_requests_status: :unauthorized do + it_behaves_like 'handling nuget metadata requests with package name', + example_names_with_status: + { + anonymous_requests_example_name: 'rejects nuget packages access', + anonymous_requests_status: :unauthorized, + guest_requests_example_name: 'rejects nuget packages access', + guest_requests_status: :not_found + } do let(:url) { "/groups/#{target.id}/-/packages/nuget/metadata/#{package_name}/index.json" } end end describe 'GET /api/v4/groups/:id/-/packages/nuget/metadata/*package_name/*package_version' do - it_behaves_like 'handling nuget metadata requests with package name and package version', anonymous_requests_example_name: 'rejects nuget packages access', anonymous_requests_status: :unauthorized do + it_behaves_like 'handling nuget metadata requests with package name and package version', + example_names_with_status: + { + anonymous_requests_example_name: 'rejects nuget packages access', + anonymous_requests_status: :unauthorized, + guest_requests_example_name: 'rejects nuget packages access', + guest_requests_status: :not_found + } do let(:url) { "/groups/#{target.id}/-/packages/nuget/metadata/#{package_name}/#{package.version}.json" } end end describe 'GET /api/v4/groups/:id/-/packages/nuget/query' do - it_behaves_like 'handling nuget search requests', anonymous_requests_example_name: 'rejects nuget packages access', anonymous_requests_status: :unauthorized do + it_behaves_like 'handling nuget search requests', + example_names_with_status: { + anonymous_requests_example_name: 'rejects nuget packages access', + anonymous_requests_status: :unauthorized, + guest_requests_example_name: 'process empty nuget search request', + guest_requests_status: :success + } do let(:url) { "/groups/#{target.id}/-/packages/nuget/query?#{query_parameters.to_query}" } end end @@ -133,13 +159,13 @@ RSpec.describe API::NugetGroupPackages, feature_category: :package_registry do describe 'GET /api/v4/groups/:id/-/packages/nuget/metadata/*package_name/index' do let(:url) { "/groups/#{group.id}/-/packages/nuget/metadata/#{package_name}/index.json" } - it_behaves_like 'returning response status', :forbidden + it_behaves_like 'returning response status', :success end describe 'GET /api/v4/groups/:id/-/packages/nuget/metadata/*package_name/*package_version' do let(:url) { "/groups/#{group.id}/-/packages/nuget/metadata/#{package_name}/#{package.version}.json" } - it_behaves_like 'returning response status', :forbidden + it_behaves_like 'returning response status', :success end describe 'GET /api/v4/groups/:id/-/packages/nuget/query' do @@ -150,7 +176,7 @@ RSpec.describe API::NugetGroupPackages, feature_category: :package_registry do let(:query_parameters) { { q: search_term, take: take, skip: skip, prerelease: include_prereleases }.compact } let(:url) { "/groups/#{group.id}/-/packages/nuget/query?#{query_parameters.to_query}" } - it_behaves_like 'returning response status', :forbidden + it_behaves_like 'returning response status', :success end end diff --git a/spec/requests/api/pages_domains_spec.rb b/spec/requests/api/pages_domains_spec.rb index 65fcf9e006a..ba1fb5105b8 100644 --- a/spec/requests/api/pages_domains_spec.rb +++ b/spec/requests/api/pages_domains_spec.rb @@ -265,6 +265,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do project_id: project.id, namespace_id: project.namespace.id, root_namespace_id: project.root_namespace.id, + domain_id: kind_of(Numeric), domain: params[:domain] ) @@ -393,6 +394,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do project_id: project.id, namespace_id: project.namespace.id, root_namespace_id: project.root_namespace.id, + domain_id: pages_domain_secure.id, domain: pages_domain_secure.domain ) end @@ -556,6 +558,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do project_id: project.id, namespace_id: project.namespace.id, root_namespace_id: project.root_namespace.id, + domain_id: pages_domain.id, domain: pages_domain.domain ) diff --git a/spec/requests/api/project_debian_distributions_spec.rb b/spec/requests/api/project_debian_distributions_spec.rb index 9807f177c5d..dfe93e9fbad 100644 --- a/spec/requests/api/project_debian_distributions_spec.rb +++ b/spec/requests/api/project_debian_distributions_spec.rb @@ -5,7 +5,17 @@ RSpec.describe API::ProjectDebianDistributions, feature_category: :package_regis include HttpBasicAuthHelpers include WorkhorseHelpers - include_context 'Debian repository shared context', :project, true do + include_context 'Debian repository shared context', :project, false do + shared_examples 'accept GET request on private project with access to package registry for everyone' do + include_context 'Debian repository access', :private, :anonymous, :basic do + before do + container.project_feature.reload.update!(package_registry_access_level: ProjectFeature::PUBLIC) + end + + it_behaves_like 'Debian distributions GET request', :success + end + end + describe 'POST projects/:id/debian_distributions' do let(:method) { :post } let(:url) { "/projects/#{container.id}/debian_distributions" } @@ -18,24 +28,37 @@ RSpec.describe API::ProjectDebianDistributions, feature_category: :package_regis it_behaves_like 'Debian distributions write endpoint', 'GET', :bad_request, /^{"message":{"codename":\["has already been taken"\]}}$/ end + + context 'with access to package registry for everyone' do + include_context 'Debian repository access', :private, :anonymous, :basic do + before do + container.project_feature.reload.update!(package_registry_access_level: ProjectFeature::PUBLIC) + end + + it_behaves_like 'Debian distributions POST request', :not_found + end + end end describe 'GET projects/:id/debian_distributions' do let(:url) { "/projects/#{container.id}/debian_distributions" } it_behaves_like 'Debian distributions read endpoint', 'GET', :success, /^\[{.*"codename":"existing-codename",.*"components":\["existing-component"\],.*"architectures":\["all","existing-arch"\]/ + it_behaves_like 'accept GET request on private project with access to package registry for everyone' end describe 'GET projects/:id/debian_distributions/:codename' do let(:url) { "/projects/#{container.id}/debian_distributions/#{distribution.codename}" } it_behaves_like 'Debian distributions read endpoint', 'GET', :success, /^{.*"codename":"existing-codename",.*"components":\["existing-component"\],.*"architectures":\["all","existing-arch"\]/ + it_behaves_like 'accept GET request on private project with access to package registry for everyone' end describe 'GET projects/:id/debian_distributions/:codename/key.asc' do let(:url) { "/projects/#{container.id}/debian_distributions/#{distribution.codename}/key.asc" } it_behaves_like 'Debian distributions read endpoint', 'GET', :success, /^-----BEGIN PGP PUBLIC KEY BLOCK-----/ + it_behaves_like 'accept GET request on private project with access to package registry for everyone' end describe 'PUT projects/:id/debian_distributions/:codename' do diff --git a/spec/requests/api/project_export_spec.rb b/spec/requests/api/project_export_spec.rb index fdd76c63069..096f0b73b4c 100644 --- a/spec/requests/api/project_export_spec.rb +++ b/spec/requests/api/project_export_spec.rb @@ -511,6 +511,10 @@ RSpec.describe API::ProjectExport, :clean_gitlab_redis_cache, feature_category: let_it_be(:status_path) { "/projects/#{project.id}/export_relations/status" } + before do + stub_application_setting(bulk_import_enabled: true) + end + context 'when user is a maintainer' do before do project.add_maintainer(user) @@ -584,9 +588,9 @@ RSpec.describe API::ProjectExport, :clean_gitlab_redis_cache, feature_category: end end - context 'with bulk_import FF disabled' do + context 'with bulk_import is disabled' do before do - stub_feature_flags(bulk_import: false) + stub_application_setting(bulk_import_enabled: false) end describe 'POST /projects/:id/export_relations' do @@ -641,5 +645,11 @@ RSpec.describe API::ProjectExport, :clean_gitlab_redis_cache, feature_category: end end end + + context 'when bulk import is disabled' do + it_behaves_like '404 response' do + let(:request) { get api(path, user) } + end + end end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 6e8168c0ee1..d62f8a32453 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -1169,7 +1169,7 @@ RSpec.describe API::Projects do expect(response).to have_gitlab_http_status(:bad_request) end - it "assigns attributes to project", :aggregate_failures do + it 'assigns attributes to project', :aggregate_failures do project = attributes_for(:project, { path: 'camelCasePath', issues_enabled: false, @@ -1198,6 +1198,11 @@ RSpec.describe API::Projects do attrs[:feature_flags_access_level] = 'disabled' attrs[:infrastructure_access_level] = 'disabled' attrs[:monitor_access_level] = 'disabled' + attrs[:snippets_access_level] = 'disabled' + attrs[:wiki_access_level] = 'disabled' + attrs[:builds_access_level] = 'disabled' + attrs[:merge_requests_access_level] = 'disabled' + attrs[:issues_access_level] = 'disabled' end post api('/projects', user), params: project @@ -1228,6 +1233,11 @@ RSpec.describe API::Projects do expect(project.project_feature.feature_flags_access_level).to eq(ProjectFeature::DISABLED) expect(project.project_feature.infrastructure_access_level).to eq(ProjectFeature::DISABLED) expect(project.project_feature.monitor_access_level).to eq(ProjectFeature::DISABLED) + expect(project.project_feature.wiki_access_level).to eq(ProjectFeature::DISABLED) + expect(project.project_feature.builds_access_level).to eq(ProjectFeature::DISABLED) + expect(project.project_feature.merge_requests_access_level).to eq(ProjectFeature::DISABLED) + expect(project.project_feature.issues_access_level).to eq(ProjectFeature::DISABLED) + expect(project.project_feature.snippets_access_level).to eq(ProjectFeature::DISABLED) end it 'assigns container_registry_enabled to project', :aggregate_failures do @@ -2278,7 +2288,7 @@ RSpec.describe API::Projects do end end - context 'when authenticated as an admin' do + context 'when authenticated as an admin', :with_license do before do stub_container_registry_config(enabled: true, host_port: 'registry.example.org:5000') end diff --git a/spec/requests/api/release/links_spec.rb b/spec/requests/api/release/links_spec.rb index 6036960c43c..4a7821fcb0a 100644 --- a/spec/requests/api/release/links_spec.rb +++ b/spec/requests/api/release/links_spec.rb @@ -222,6 +222,24 @@ RSpec.describe API::Release::Links, feature_category: :release_orchestration do expect(response).to match_response_schema('release/link') end + context 'when using `direct_asset_path`' do + before do + params[:direct_asset_path] = params.delete(:filepath) + end + + it 'creates a new release link successfully' do + expect do + post api("/projects/#{project.id}/releases/v0.1/assets/links", maintainer), params: params + end.to change { Releases::Link.count }.by(1) + + release.reload + + expect(last_release_link.name).to eq('awesome-app.dmg') + expect(last_release_link.filepath).to eq('/binaries/awesome-app.dmg') + expect(last_release_link.url).to eq('https://example.com/download/awesome-app.dmg') + end + end + context 'when using JOB-TOKEN auth' do let(:job) { create(:ci_build, :running, user: maintainer) } @@ -357,6 +375,15 @@ RSpec.describe API::Release::Links, feature_category: :release_orchestration do expect(response).to match_response_schema('release/link') end + context 'when using `direct_asset_path`' do + it 'updates the release link' do + put api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer), + params: params.merge(direct_asset_path: '/binaries/awesome-app.msi') + + expect(json_response['direct_asset_url']).to eq("http://localhost/#{project.namespace.path}/#{project.name}/-/releases/#{release.tag}/downloads/binaries/awesome-app.msi") + end + end + context 'when using JOB-TOKEN auth' do let(:job) { create(:ci_build, :running, user: maintainer) } diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb index a1aff9a6b1c..e209ad2b2d5 100644 --- a/spec/requests/api/releases_spec.rb +++ b/spec/requests/api/releases_spec.rb @@ -573,7 +573,7 @@ RSpec.describe API::Releases, feature_category: :release_orchestration do end end - describe 'GET /projects/:id/releases/:tag_name/downloads/*file_path' do + describe 'GET /projects/:id/releases/:tag_name/downloads/*direct_asset_path' do let!(:release) { create(:release, project: project, tag: 'v0.1', author: maintainer) } let!(:link) { create(:release_link, release: release, url: "#{url}#{filepath}", filepath: filepath) } let(:filepath) { '/bin/bigfile.exe' } @@ -637,6 +637,16 @@ RSpec.describe API::Releases, feature_category: :release_orchestration do end end + context 'when direct_asset_path is used' do + let(:direct_asset_path) { filepath } + + it 'redirects to the file download URL successfully' do + get api("/projects/#{project.id}/releases/v0.1/downloads#{direct_asset_path}", maintainer) + + expect(response).to redirect_to("#{url}#{direct_asset_path}") + end + end + context 'when filepath does not exists' do it 'returns 404 for maintater' do get api("/projects/#{project.id}/releases/v0.1/downloads/bin/not_existing.exe", maintainer) @@ -911,6 +921,22 @@ RSpec.describe API::Releases, feature_category: :release_orchestration do end.not_to change { Project.find_by_id(project.id).repository.tag_count } end + context 'when using `direct_asset_path` for the asset link' do + before do + params[:direct_asset_path] = params.delete(:filepath) + end + + it 'creates a new release successfully' do + expect do + post api("/projects/#{project.id}/releases", maintainer), params: params + end.to change { Release.count }.by(1) + + release = project.releases.last + + expect(release.links.last.filepath).to eq('/permanent/path/to/runbook') + end + end + context 'with protected tag' do context 'when user has access to the protected tag' do let!(:protected_tag) { create(:protected_tag, :developers_can_create, name: '*', project: project) } diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 393ada1da4f..555ba2bc978 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -353,15 +353,9 @@ RSpec.describe API::Repositories, feature_category: :source_code_management do expect(response).to have_gitlab_http_status(:too_many_requests) end - context "when hotlinking detection is enabled" do - before do - stub_feature_flags(repository_archive_hotlinking_interception: true) - end - - it_behaves_like "hotlink interceptor" do - let(:http_request) do - get api(route, current_user), headers: headers - end + it_behaves_like "hotlink interceptor" do + let(:http_request) do + get api(route, current_user), headers: headers end end end diff --git a/spec/requests/api/rubygem_packages_spec.rb b/spec/requests/api/rubygem_packages_spec.rb index 6f048fa57a8..34cf6033811 100644 --- a/spec/requests/api/rubygem_packages_spec.rb +++ b/spec/requests/api/rubygem_packages_spec.rb @@ -55,11 +55,11 @@ RSpec.describe API::RubygemPackages, feature_category: :package_registry do end where(:user_role, :token_type, :valid_token, :status) do - :guest | :personal_access_token | true | :not_found + :guest | :personal_access_token | true | :forbidden :guest | :personal_access_token | false | :unauthorized :guest | :deploy_token | true | :not_found :guest | :deploy_token | false | :unauthorized - :guest | :job_token | true | :not_found + :guest | :job_token | true | :forbidden :guest | :job_token | false | :unauthorized :reporter | :personal_access_token | true | :not_found :reporter | :personal_access_token | false | :unauthorized @@ -174,6 +174,17 @@ RSpec.describe API::RubygemPackages, feature_category: :package_registry do end end + context 'with access to package registry for everyone' do + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, property: 'i_package_rubygems_user' } } + + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + project.project_feature.update!(package_registry_access_level: ProjectFeature::PUBLIC) + end + + it_behaves_like 'Rubygems gem download', :anonymous, :success + end + context 'with package files pending destruction' do let_it_be(:package_file_pending_destruction) { create(:package_file, :pending_destruction, :xml, package: package, file_name: file_name) } @@ -423,5 +434,16 @@ RSpec.describe API::RubygemPackages, feature_category: :package_registry do it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] end end + + context 'with access to package registry for everyone' do + let(:params) { {} } + + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + project.project_feature.update!(package_registry_access_level: ProjectFeature::PUBLIC) + end + + it_behaves_like 'dependency endpoint success', :anonymous, :success + end end end diff --git a/spec/requests/api/search_spec.rb b/spec/requests/api/search_spec.rb index 430d3b7d187..035f53db12e 100644 --- a/spec/requests/api/search_spec.rb +++ b/spec/requests/api/search_spec.rb @@ -377,6 +377,26 @@ RSpec.describe API::Search, feature_category: :global_search do end end + context 'global search is disabled for the given scope' do + it 'returns forbidden response' do + allow_next_instance_of(SearchService) do |instance| + allow(instance).to receive(:global_search_enabled_for_scope?).and_return false + end + get api(endpoint, user), params: { search: 'awesome', scope: 'issues' } + expect(response).to have_gitlab_http_status(:forbidden) + end + end + + context 'global search is enabled for the given scope' do + it 'returns forbidden response' do + allow_next_instance_of(SearchService) do |instance| + allow(instance).to receive(:global_search_enabled_for_scope?).and_return true + end + get api(endpoint, user), params: { search: 'awesome', scope: 'issues' } + expect(response).to have_gitlab_http_status(:ok) + end + end + it 'increments the custom search sli error rate with error false if no error occurred' do expect(Gitlab::Metrics::GlobalSearchSlis).to receive(:record_error_rate).with( error: false, diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index e93ef52ef03..4d85849cff3 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -65,6 +65,7 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu expect(json_response['can_create_group']).to eq(true) expect(json_response['jira_connect_application_key']).to eq(nil) expect(json_response['jira_connect_proxy_url']).to eq(nil) + expect(json_response['user_defaults_to_private_profile']).to eq(false) end end @@ -166,7 +167,9 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu can_create_group: false, jira_connect_application_key: '123', jira_connect_proxy_url: 'http://example.com', - bulk_import_enabled: false + bulk_import_enabled: false, + allow_runner_registration_token: true, + user_defaults_to_private_profile: true } expect(response).to have_gitlab_http_status(:ok) @@ -232,6 +235,8 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu expect(json_response['jira_connect_application_key']).to eq('123') expect(json_response['jira_connect_proxy_url']).to eq('http://example.com') expect(json_response['bulk_import_enabled']).to be(false) + expect(json_response['allow_runner_registration_token']).to be(true) + expect(json_response['user_defaults_to_private_profile']).to be(true) end end @@ -801,5 +806,62 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu .to include(a_string_matching('is not a number')) end end + + context 'with housekeeping enabled' do + it 'at least one of housekeeping_incremental_repack_period or housekeeping_optimize_repository_period is required' do + put api("/application/settings", admin), params: { + housekeeping_enabled: true + } + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['error']).to eq( + "housekeeping_incremental_repack_period, housekeeping_optimize_repository_period are missing, exactly one parameter must be provided" + ) + end + + context 'when housekeeping_incremental_repack_period is specified' do + it 'requires all three housekeeping settings' do + put api("/application/settings", admin), params: { + housekeeping_enabled: true, + housekeeping_incremental_repack_period: 10 + } + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['error']).to eq( + "housekeeping_full_repack_period, housekeeping_gc_period, housekeeping_incremental_repack_period provide all or none of parameters" + ) + end + + it 'returns housekeeping_optimize_repository_period value for all housekeeping settings attributes' do + put api("/application/settings", admin), params: { + housekeeping_enabled: true, + housekeeping_gc_period: 150, + housekeeping_full_repack_period: 125, + housekeeping_incremental_repack_period: 100 + } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['housekeeping_optimize_repository_period']).to eq(100) + expect(json_response['housekeeping_full_repack_period']).to eq(100) + expect(json_response['housekeeping_gc_period']).to eq(100) + expect(json_response['housekeeping_incremental_repack_period']).to eq(100) + end + end + + context 'when housekeeping_optimize_repository_period is specified' do + it 'returns housekeeping_optimize_repository_period value for all housekeeping settings attributes' do + put api("/application/settings", admin), params: { + housekeeping_enabled: true, + housekeeping_optimize_repository_period: 100 + } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['housekeeping_optimize_repository_period']).to eq(100) + expect(json_response['housekeeping_full_repack_period']).to eq(100) + expect(json_response['housekeeping_gc_period']).to eq(100) + expect(json_response['housekeeping_incremental_repack_period']).to eq(100) + end + end + end end end diff --git a/spec/requests/api/snippet_repository_storage_moves_spec.rb b/spec/requests/api/snippet_repository_storage_moves_spec.rb index 6081531aee9..9afd8147eb6 100644 --- a/spec/requests/api/snippet_repository_storage_moves_spec.rb +++ b/spec/requests/api/snippet_repository_storage_moves_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::SnippetRepositoryStorageMoves, feature_category: :gitaly do +RSpec.describe API::SnippetRepositoryStorageMoves, :with_license, feature_category: :gitaly do it_behaves_like 'repository_storage_moves API', 'snippets' do let_it_be(:container) { create(:snippet, :repository).tap { |snippet| snippet.create_repository } } let_it_be(:storage_move) { create(:snippet_repository_storage_move, :scheduled, container: container) } diff --git a/spec/requests/api/suggestions_spec.rb b/spec/requests/api/suggestions_spec.rb index 93b2435c601..4a4692684e3 100644 --- a/spec/requests/api/suggestions_spec.rb +++ b/spec/requests/api/suggestions_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Suggestions, feature_category: :code_review do +RSpec.describe API::Suggestions, feature_category: :code_review_workflow do let(:project) { create(:project, :repository) } let(:user) { create(:user) } diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb index 5a342f79926..8c3bdd5a9f0 100644 --- a/spec/requests/api/todos_spec.rb +++ b/spec/requests/api/todos_spec.rb @@ -6,6 +6,7 @@ RSpec.describe API::Todos, feature_category: :source_code_management do include DesignManagementTestHelpers let_it_be(:group) { create(:group) } + let_it_be(:group_2) { create(:group) } let_it_be(:project_1) { create(:project, :repository, group: group) } let_it_be(:project_2) { create(:project) } let_it_be(:author_1) { create(:user) } @@ -15,7 +16,8 @@ RSpec.describe API::Todos, feature_category: :source_code_management do let_it_be(:work_item) { create(:work_item, :task, project: project_1) } let_it_be(:merge_request) { create(:merge_request, source_project: project_1) } let_it_be(:alert) { create(:alert_management_alert, project: project_1) } - let_it_be(:group_request_todo) { create(:todo, author: author_1, user: john_doe, target: group, action: Todo::MEMBER_ACCESS_REQUESTED) } + let_it_be(:project_request_todo) { create(:todo, author: author_1, user: john_doe, target: project_2, action: Todo::MEMBER_ACCESS_REQUESTED) } + let_it_be(:group_request_todo) { create(:todo, author: author_1, user: john_doe, target: group_2, action: Todo::MEMBER_ACCESS_REQUESTED) } let_it_be(:alert_todo) { create(:todo, project: project_1, author: john_doe, user: john_doe, target: alert) } let_it_be(:merge_request_todo) { create(:todo, project: project_1, author: author_2, user: john_doe, target: merge_request) } let_it_be(:pending_1) { create(:todo, :mentioned, project: project_1, author: author_1, user: john_doe, target: issue) } @@ -72,7 +74,7 @@ RSpec.describe API::Todos, feature_category: :source_code_management do expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers expect(json_response).to be_an Array - expect(json_response.length).to eq(7) + expect(json_response.length).to eq(8) expect(json_response[0]).to include( 'id' => pending_5.id, @@ -133,11 +135,23 @@ RSpec.describe API::Todos, feature_category: :source_code_management do 'target_type' => 'Namespace', 'action_name' => 'member_access_requested', 'target' => hash_including( - 'id' => group.id, - 'name' => group.name, - 'full_path' => group.full_path + 'id' => group_2.id, + 'name' => group_2.name, + 'full_path' => group_2.full_path ), - 'target_url' => Gitlab::Routing.url_helpers.group_group_members_url(group, tab: 'access_requests') + 'target_url' => Gitlab::Routing.url_helpers.group_group_members_url(group_2, tab: 'access_requests') + ) + + expect(json_response[7]).to include( + 'target_type' => 'Project', + 'action_name' => 'member_access_requested', + 'target' => hash_including( + 'id' => project_2.id, + 'name' => project_2.name, + 'path' => project_2.path + ), + 'target_url' => Gitlab::Routing.url_helpers.project_project_members_url(project_2, tab: 'access_requests'), + 'body' => project_2.full_path ) end @@ -149,7 +163,7 @@ RSpec.describe API::Todos, feature_category: :source_code_management do get api('/todos', john_doe) - expect(json_response.count).to eq(7) + expect(json_response.count).to eq(8) expect(json_response.map { |t| t['id'] }).not_to include(no_access_todo.id, pending_4.id) end end @@ -242,8 +256,10 @@ RSpec.describe API::Todos, feature_category: :source_code_management do merge_request_3 = create(:merge_request, :jira_branch, source_project: new_todo.project) create(:on_commit_todo, project: new_todo.project, author: author_1, user: john_doe, target: merge_request_3) create(:todo, project: new_todo.project, author: author_2, user: john_doe, target: merge_request_3) + create(:todo, author: author_2, user: john_doe, target: project_2, action: Todo::MEMBER_ACCESS_REQUESTED) + create(:todo, author: author_2, user: john_doe, target: group_2, action: Todo::MEMBER_ACCESS_REQUESTED) - expect { get api('/todos', john_doe) }.not_to exceed_query_limit(control1).with_threshold(6) + expect { get api('/todos', john_doe) }.not_to exceed_query_limit(control1).with_threshold(5) control2 = ActiveRecord::QueryRecorder.new { get api('/todos', john_doe) } create_issue_todo_for(john_doe) diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index bfb71d95f5e..c063187fdf4 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -1454,6 +1454,46 @@ RSpec.describe API::Users, feature_category: :users do include_examples 'does not allow the "read_user" scope' end + + context "`private_profile` attribute" do + context "based on the application setting" do + before do + stub_application_setting(user_defaults_to_private_profile: true) + end + + let(:params) { attributes_for(:user) } + + shared_examples_for 'creates the user with the value of `private_profile` based on the application setting' do + specify do + post api("/users", admin), params: params + + expect(response).to have_gitlab_http_status(:created) + user = User.find_by(id: json_response['id'], private_profile: true) + expect(user).to be_present + end + end + + context 'when the attribute is not overridden in params' do + it_behaves_like 'creates the user with the value of `private_profile` based on the application setting' + end + + context 'when the attribute is overridden in params' do + it 'creates the user with the value of `private_profile` same as the value of the overridden param' do + post api("/users", admin), params: params.merge(private_profile: false) + + expect(response).to have_gitlab_http_status(:created) + user = User.find_by(id: json_response['id'], private_profile: false) + expect(user).to be_present + end + + context 'overridden as `nil`' do + let(:params) { attributes_for(:user, private_profile: nil) } + + it_behaves_like 'creates the user with the value of `private_profile` based on the application setting' + end + end + end + end end describe "PUT /users/:id" do @@ -1634,12 +1674,6 @@ RSpec.describe API::Users, feature_category: :users do expect(user.reload.external?).to be_truthy end - it "private profile is false by default" do - put api("/users/#{user.id}", admin), params: {} - - expect(user.reload.private_profile).to eq(false) - end - it "does have default values for theme and color-scheme ID" do put api("/users/#{user.id}", admin), params: {} @@ -1647,13 +1681,6 @@ RSpec.describe API::Users, feature_category: :users do expect(user.reload.color_scheme_id).to eq(Gitlab::ColorSchemes.default.id) end - it "updates private profile" do - put api("/users/#{user.id}", admin), params: { private_profile: true } - - expect(response).to have_gitlab_http_status(:ok) - expect(user.reload.private_profile).to eq(true) - end - it "updates viewing diffs file by file" do put api("/users/#{user.id}", admin), params: { view_diffs_file_by_file: true } @@ -1661,22 +1688,40 @@ RSpec.describe API::Users, feature_category: :users do expect(user.reload.user_preference.view_diffs_file_by_file?).to eq(true) end - it "updates private profile to false when nil is given" do - user.update!(private_profile: true) + context 'updating `private_profile`' do + it "updates private profile" do + current_value = user.private_profile + new_value = !current_value - put api("/users/#{user.id}", admin), params: { private_profile: nil } + put api("/users/#{user.id}", admin), params: { private_profile: new_value } - expect(response).to have_gitlab_http_status(:ok) - expect(user.reload.private_profile).to eq(false) - end + expect(response).to have_gitlab_http_status(:ok) + expect(user.reload.private_profile).to eq(new_value) + end + + context 'when `private_profile` is set to `nil`' do + before do + stub_application_setting(user_defaults_to_private_profile: true) + end - it "does not modify private profile when field is not provided" do - user.update!(private_profile: true) + it "updates private_profile to value of the application setting" do + user.update!(private_profile: false) - put api("/users/#{user.id}", admin), params: {} + put api("/users/#{user.id}", admin), params: { private_profile: nil } - expect(response).to have_gitlab_http_status(:ok) - expect(user.reload.private_profile).to eq(true) + expect(response).to have_gitlab_http_status(:ok) + expect(user.reload.private_profile).to eq(true) + end + end + + it "does not modify private profile when field is not provided" do + user.update!(private_profile: true) + + put api("/users/#{user.id}", admin), params: {} + + expect(response).to have_gitlab_http_status(:ok) + expect(user.reload.private_profile).to eq(true) + end end it "does not modify theme or color-scheme ID when field is not provided" do @@ -3617,6 +3662,15 @@ RSpec.describe API::Users, feature_category: :users do expect(response.body).to eq('true') expect(user.reload.state).to eq('blocked') end + + it 'saves a custom attribute', :freeze_time, feature_category: :insider_threat do + block_user + + custom_attribute = user.custom_attributes.last + + expect(custom_attribute.key).to eq(UserCustomAttribute::BLOCKED_BY) + expect(custom_attribute.value).to eq("#{admin.username}/#{admin.id}+#{Time.current}") + end end context 'with an ldap blocked user' do @@ -3708,6 +3762,15 @@ RSpec.describe API::Users, feature_category: :users do expect(response).to have_gitlab_http_status(:created) expect(blocked_user.reload.state).to eq('active') end + + it 'saves a custom attribute', :freeze_time, feature_category: :insider_threat do + unblock_user + + custom_attribute = blocked_user.custom_attributes.last + + expect(custom_attribute.key).to eq(UserCustomAttribute::UNBLOCKED_BY) + expect(custom_attribute.value).to eq("#{admin.username}/#{admin.id}+#{Time.current}") + end end context 'with a ldap blocked user' do @@ -4045,60 +4108,164 @@ RSpec.describe API::Users, feature_category: :users do end end - describe 'GET /user/status' do - let(:path) { '/user/status' } + describe '/user/status' do + let(:user_status) { create(:user_status, clear_status_at: 8.hours.from_now) } + let(:user_with_status) { user_status.user } + let(:params) { {} } + let(:request_user) { user } - it_behaves_like 'rendering user status' - end + shared_examples '/user/status successful response' do + context 'when request is successful' do + let(:params) { { emoji: 'smirk', message: 'hello world' } } - describe 'PUT /user/status' do - it 'saves the status' do - put api('/user/status', user), params: { emoji: 'smirk', message: 'hello world' } + it 'saves the status' do + set_user_status - expect(response).to have_gitlab_http_status(:success) - expect(json_response['emoji']).to eq('smirk') + expect(response).to have_gitlab_http_status(:success) + expect(json_response['emoji']).to eq('smirk') + expect(json_response['message']).to eq('hello world') + end + end end - it 'renders errors when the status was invalid' do - put api('/user/status', user), params: { emoji: 'does not exist', message: 'hello world' } + shared_examples '/user/status unsuccessful response' do + context 'when request is unsuccessful' do + let(:params) { { emoji: 'does not exist', message: 'hello world' } } - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['message']['emoji']).to be_present + it 'renders errors' do + set_user_status + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']['emoji']).to be_present + end + end end - it 'deletes the status when passing empty values' do - put api('/user/status', user) + shared_examples '/user/status passing nil for params' do + context 'when passing nil for params' do + let(:params) { { emoji: nil, message: nil, clear_status_after: nil } } + let(:request_user) { user_with_status } - expect(response).to have_gitlab_http_status(:success) - expect(user.reload.status).to be_nil + it 'deletes the status' do + set_user_status + + expect(response).to have_gitlab_http_status(:success) + expect(user_with_status.status).to be_nil + end + end end - context 'when clear_status_after is given' do - it 'sets the clear_status_at column' do - freeze_time do + shared_examples '/user/status clear_status_after field' do + context 'when clear_status_after is valid', :freeze_time do + let(:params) { { emoji: 'smirk', message: 'hello world', clear_status_after: '3_hours' } } + + it 'sets the clear_status_at column' do expected_clear_status_at = 3.hours.from_now - put api('/user/status', user), params: { emoji: 'smirk', message: 'hello world', clear_status_after: '3_hours' } + set_user_status expect(response).to have_gitlab_http_status(:success) - expect(user.status.reload.clear_status_at).to be_within(1.minute).of(expected_clear_status_at) - expect(Time.parse(json_response["clear_status_at"])).to be_within(1.minute).of(expected_clear_status_at) + expect(user.status.clear_status_at).to be_like_time(expected_clear_status_at) + expect(Time.parse(json_response["clear_status_at"])).to be_like_time(expected_clear_status_at) end end - it 'unsets the clear_status_at column' do - user.create_status!(clear_status_at: 5.hours.ago) + context 'when clear_status_after is nil' do + let(:params) { { emoji: 'smirk', message: 'hello world', clear_status_after: nil } } + let(:request_user) { user_with_status } - put api('/user/status', user), params: { emoji: 'smirk', message: 'hello world', clear_status_after: nil } + it 'unsets the clear_status_at column' do + set_user_status - expect(response).to have_gitlab_http_status(:success) - expect(user.status.reload.clear_status_at).to be_nil + expect(response).to have_gitlab_http_status(:success) + expect(user_with_status.status.clear_status_at).to be_nil + end end - it 'raises error when unknown status value is given' do - put api('/user/status', user), params: { emoji: 'smirk', message: 'hello world', clear_status_after: 'wrong' } + context 'when clear_status_after is invalid' do + let(:params) { { emoji: 'smirk', message: 'hello world', clear_status_after: 'invalid' } } - expect(response).to have_gitlab_http_status(:bad_request) + it 'raises error when unknown status value is given' do + set_user_status + + expect(response).to have_gitlab_http_status(:bad_request) + end + end + end + + describe 'GET' do + let(:path) { '/user/status' } + + it_behaves_like 'rendering user status' + end + + describe 'PUT' do + subject(:set_user_status) { put api('/user/status', request_user), params: params } + + include_examples '/user/status successful response' + + include_examples '/user/status unsuccessful response' + + include_examples '/user/status passing nil for params' + + include_examples '/user/status clear_status_after field' + + context 'when passing empty params' do + let(:request_user) { user_with_status } + + it 'deletes the status' do + set_user_status + + expect(response).to have_gitlab_http_status(:success) + expect(user_with_status.status).to be_nil + end + end + + context 'when clear_status_after is not given' do + let(:params) { { emoji: 'smirk', message: 'hello world' } } + let(:request_user) { user_with_status } + + it 'unsets clear_status_at column' do + set_user_status + + expect(response).to have_gitlab_http_status(:success) + expect(user_with_status.status.clear_status_at).to be_nil + end + end + end + + describe 'PATCH' do + subject(:set_user_status) { patch api('/user/status', request_user), params: params } + + include_examples '/user/status successful response' + + include_examples '/user/status unsuccessful response' + + include_examples '/user/status passing nil for params' + + include_examples '/user/status clear_status_after field' + + context 'when passing empty params' do + let(:request_user) { user_with_status } + + it 'does not update the status' do + set_user_status + + expect(response).to have_gitlab_http_status(:success) + expect(user_with_status.status).to eq(user_status) + end + end + + context 'when clear_status_after is not given' do + let(:params) { { emoji: 'smirk', message: 'hello world' } } + let(:request_user) { user_with_status } + + it 'does not unset clear_status_at column' do + set_user_status + + expect(response).to have_gitlab_http_status(:success) + expect(user_with_status.status.clear_status_at).not_to be_nil + end end end end -- cgit v1.2.1