From f791b9447bae1176dfe80d133295bb44db91889d Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Sat, 31 Oct 2020 06:09:06 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .rubocop_todo.yml | 6 - app/models/environment.rb | 2 +- .../unreleased/270200-downtier-pat-apis.yml | 5 + ...02-full-project-issue-boards-api-on-ce.yml | 5 + ...hing-limit-environment-default-to-true.yml | 6 + .../reactive_caching_limit_environment.yml | 2 +- .../development/suggest_pipeline.yml | 2 +- doc/api/personal_access_tokens.md | 5 +- lib/api/api.rb | 1 + lib/api/boards.rb | 37 ++++++ lib/api/boards_responses.rb | 35 ++++++ lib/api/entities/board.rb | 1 + lib/api/personal_access_tokens.rb | 59 +++++++++ .../registry/repositories_controller_spec.rb | 5 +- .../legacy_upload_mover_spec.rb | 102 +++++----------- spec/requests/api/boards_spec.rb | 43 ++++++- .../api/personal_access_tokens_spec.rb | 112 ++++++++++++++++++ .../api/discussions_shared_examples.rb | 11 +- ...and_scoped_issue_boards_shared_examples.rb | 94 +++++++++++++++ 19 files changed, 437 insertions(+), 96 deletions(-) create mode 100644 changelogs/unreleased/270200-downtier-pat-apis.yml create mode 100644 changelogs/unreleased/273002-full-project-issue-boards-api-on-ce.yml create mode 100644 changelogs/unreleased/feature-change-reactive-caching-limit-environment-default-to-true.yml create mode 100644 lib/api/personal_access_tokens.rb create mode 100644 spec/requests/api/personal_access_tokens_spec.rb create mode 100644 spec/support/shared_examples/requests/api/multiple_and_scoped_issue_boards_shared_examples.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 6b91fc8c428..0890fe473c6 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -186,18 +186,12 @@ RSpec/ExpectChange: # Offense count: 47 RSpec/ExpectGitlabTracking: Exclude: - - 'ee/spec/controllers/projects/settings/operations_controller_spec.rb' - - 'ee/spec/requests/api/visual_review_discussions_spec.rb' - - 'ee/spec/services/epics/issue_promote_service_spec.rb' - - 'spec/controllers/groups/registry/repositories_controller_spec.rb' - 'spec/controllers/projects/registry/repositories_controller_spec.rb' - 'spec/controllers/projects/registry/tags_controller_spec.rb' - 'spec/controllers/projects/settings/operations_controller_spec.rb' - - 'spec/controllers/registrations_controller_spec.rb' - 'spec/lib/api/helpers_spec.rb' - 'spec/requests/api/project_container_repositories_spec.rb' - 'spec/support/shared_examples/controllers/trackable_shared_examples.rb' - - 'spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb' - 'spec/support/shared_examples/requests/api/discussions_shared_examples.rb' - 'spec/support/shared_examples/requests/api/packages_shared_examples.rb' - 'spec/support/shared_examples/requests/api/tracking_shared_examples.rb' diff --git a/app/models/environment.rb b/app/models/environment.rb index 66613869915..15c62cfae97 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -395,7 +395,7 @@ class Environment < ApplicationRecord # Overrides ReactiveCaching default to activate limit checking behind a FF def reactive_cache_limit_enabled? - Feature.enabled?(:reactive_caching_limit_environment, project) + Feature.enabled?(:reactive_caching_limit_environment, project, default_enabled: true) end end diff --git a/changelogs/unreleased/270200-downtier-pat-apis.yml b/changelogs/unreleased/270200-downtier-pat-apis.yml new file mode 100644 index 00000000000..48a37c8e19e --- /dev/null +++ b/changelogs/unreleased/270200-downtier-pat-apis.yml @@ -0,0 +1,5 @@ +--- +title: Move Personal Access Token API to Core +merge_request: 46145 +author: +type: changed diff --git a/changelogs/unreleased/273002-full-project-issue-boards-api-on-ce.yml b/changelogs/unreleased/273002-full-project-issue-boards-api-on-ce.yml new file mode 100644 index 00000000000..82856ee39c1 --- /dev/null +++ b/changelogs/unreleased/273002-full-project-issue-boards-api-on-ce.yml @@ -0,0 +1,5 @@ +--- +title: Make all Project Issue Boards API available even in CE +merge_request: 46137 +author: Takuya Noguchi +type: changed diff --git a/changelogs/unreleased/feature-change-reactive-caching-limit-environment-default-to-true.yml b/changelogs/unreleased/feature-change-reactive-caching-limit-environment-default-to-true.yml new file mode 100644 index 00000000000..4ec0062f6ec --- /dev/null +++ b/changelogs/unreleased/feature-change-reactive-caching-limit-environment-default-to-true.yml @@ -0,0 +1,6 @@ +--- +title: Limits the Deploy Boards data to 10 MB. This change is enabled by default behind + a feature flag +merge_request: 46043 +author: +type: changed diff --git a/config/feature_flags/development/reactive_caching_limit_environment.yml b/config/feature_flags/development/reactive_caching_limit_environment.yml index 8aa66c9d293..0020c57e1b8 100644 --- a/config/feature_flags/development/reactive_caching_limit_environment.yml +++ b/config/feature_flags/development/reactive_caching_limit_environment.yml @@ -4,4 +4,4 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/34202 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/202633 group: group::configure type: development -default_enabled: false +default_enabled: true diff --git a/config/feature_flags/development/suggest_pipeline.yml b/config/feature_flags/development/suggest_pipeline.yml index e39b2632c17..afc2f2b7452 100644 --- a/config/feature_flags/development/suggest_pipeline.yml +++ b/config/feature_flags/development/suggest_pipeline.yml @@ -3,5 +3,5 @@ name: suggest_pipeline introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45926 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/267492 type: development -group: group::growth +group: group::expansion default_enabled: true diff --git a/doc/api/personal_access_tokens.md b/doc/api/personal_access_tokens.md index 76c9338e4a0..5bd804b8042 100644 --- a/doc/api/personal_access_tokens.md +++ b/doc/api/personal_access_tokens.md @@ -4,13 +4,14 @@ group: unassigned info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers --- -# Personal access tokens API **(ULTIMATE)** +# Personal access tokens API You can read more about [personal access tokens](../user/profile/personal_access_tokens.md#personal-access-tokens). ## List personal access tokens -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/227264) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.3. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/227264) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.3. +> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/270200) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.6. Get a list of personal access tokens. diff --git a/lib/api/api.rb b/lib/api/api.rb index c28a6608dc5..5e8fdda5a52 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -237,6 +237,7 @@ module API mount ::API::ProjectTemplates mount ::API::Terraform::State mount ::API::Terraform::StateVersion + mount ::API::PersonalAccessTokens mount ::API::ProtectedBranches mount ::API::ProtectedTags mount ::API::Releases diff --git a/lib/api/boards.rb b/lib/api/boards.rb index dbf0cef0484..f4b23c507f4 100644 --- a/lib/api/boards.rb +++ b/lib/api/boards.rb @@ -42,6 +42,43 @@ module API authorize!(:read_board, user_project) present board, with: Entities::Board end + + desc 'Create a project board' do + detail 'This feature was introduced in 10.4' + success Entities::Board + end + params do + requires :name, type: String, desc: 'The board name' + end + post '/' do + authorize!(:admin_board, board_parent) + + create_board + end + + desc 'Update a project board' do + detail 'This feature was introduced in 11.0' + success Entities::Board + end + params do + use :update_params + end + put '/:board_id' do + authorize!(:admin_board, board_parent) + + update_board + end + + desc 'Delete a project board' do + detail 'This feature was introduced in 10.4' + success Entities::Board + end + + delete '/:board_id' do + authorize!(:admin_board, board_parent) + + delete_board + end end params do diff --git a/lib/api/boards_responses.rb b/lib/api/boards_responses.rb index 6a86c02bf4a..2ae82f78e01 100644 --- a/lib/api/boards_responses.rb +++ b/lib/api/boards_responses.rb @@ -10,6 +10,35 @@ module API board_parent.boards.find(params[:board_id]) end + def create_board + forbidden! unless board_parent.multiple_issue_boards_available? + + response = + ::Boards::CreateService.new(board_parent, current_user, { name: params[:name] }).execute + + present response.payload, with: Entities::Board + end + + def update_board + service = ::Boards::UpdateService.new(board_parent, current_user, declared_params(include_missing: false)) + service.execute(board) + + if board.valid? + present board, with: Entities::Board + else + bad_request!("Failed to save board #{board.errors.messages}") + end + end + + def delete_board + forbidden! unless board_parent.multiple_issue_boards_available? + + destroy_conditionally!(board) do |board| + service = ::Boards::DestroyService.new(board_parent, current_user) + service.execute(board) + end + end + def board_lists board.destroyable_lists end @@ -62,6 +91,12 @@ module API params :list_creation_params do requires :label_id, type: Integer, desc: 'The ID of an existing label' end + + params :update_params do + # Configurable issue boards are not available in CE/EE Core. + # https://docs.gitlab.com/ee/user/project/issue_board.html#configurable-issue-boards + optional :name, type: String, desc: 'The board name' + end end end end diff --git a/lib/api/entities/board.rb b/lib/api/entities/board.rb index 5bb1cde0fa9..b7a50408313 100644 --- a/lib/api/entities/board.rb +++ b/lib/api/entities/board.rb @@ -4,6 +4,7 @@ module API module Entities class Board < Grape::Entity expose :id + expose :name expose :project, using: Entities::BasicProjectDetails expose :lists, using: Entities::List do |board| diff --git a/lib/api/personal_access_tokens.rb b/lib/api/personal_access_tokens.rb new file mode 100644 index 00000000000..774bc935284 --- /dev/null +++ b/lib/api/personal_access_tokens.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module API + class PersonalAccessTokens < ::API::Base + include ::API::PaginationParams + + desc 'Get all Personal Access Tokens' do + detail 'This feature was added in GitLab 13.3' + success Entities::PersonalAccessToken + end + params do + optional :user_id, type: Integer, desc: 'User ID' + + use :pagination + end + + before do + authenticate! + restrict_non_admins! unless current_user.admin? + end + + helpers do + def finder_params(current_user) + current_user.admin? ? { user: user(params[:user_id]) } : { user: current_user } + end + + def user(user_id) + UserFinder.new(user_id).find_by_id + end + + def restrict_non_admins! + return if params[:user_id].blank? + + unauthorized! unless Ability.allowed?(current_user, :read_user_personal_access_tokens, user(params[:user_id])) + end + + def find_token(id) + PersonalAccessToken.find(id) || not_found! + end + end + + resources :personal_access_tokens do + get do + tokens = PersonalAccessTokensFinder.new(finder_params(current_user), current_user).execute + + present paginate(tokens), with: Entities::PersonalAccessToken + end + + delete ':id' do + service = ::PersonalAccessTokens::RevokeService.new( + current_user, + { token: find_token(params[:id]) } + ).execute + + service.success? ? no_content! : bad_request!(nil) + end + end + end +end diff --git a/spec/controllers/groups/registry/repositories_controller_spec.rb b/spec/controllers/groups/registry/repositories_controller_spec.rb index ae982b02a4f..70125087f30 100644 --- a/spec/controllers/groups/registry/repositories_controller_spec.rb +++ b/spec/controllers/groups/registry/repositories_controller_spec.rb @@ -64,12 +64,11 @@ RSpec.describe Groups::Registry::RepositoriesController do context 'html format' do let(:format) { :html } - it 'show index page' do - expect(Gitlab::Tracking).not_to receive(:event) - + it 'show index page', :snowplow do subject expect(response).to have_gitlab_http_status(:ok) + expect_no_snowplow_event end end diff --git a/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb b/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb index 1637589d272..934ab7e37f8 100644 --- a/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb +++ b/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' # rubocop: disable RSpec/FactoriesInMigrationSpecs -RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover do +RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover, :aggregate_failures do let(:test_dir) { FileUploader.options['storage_path'] } let(:filename) { 'image.png' } @@ -67,27 +67,35 @@ RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover do end end - shared_examples 'migrates the file correctly' do - before do - described_class.new(legacy_upload).execute - end + shared_examples 'migrates the file correctly' do |remote| + it 'creates a new upload record correctly, updates the legacy upload note so that it references the file in the markdown, removes the attachment from the note model, removes the file, moves legacy uploads to the correct location, removes the upload record' do + expect(File.exist?(legacy_upload.absolute_path)).to be_truthy unless remote + + described_class.new(legacy_upload).execute - it 'creates a new uplaod record correctly' do expect(new_upload.secret).not_to be_nil - expect(new_upload.path).to end_with("#{new_upload.secret}/image.png") + expect(new_upload.path).to end_with("#{new_upload.secret}/#{filename}") expect(new_upload.model_id).to eq(project.id) expect(new_upload.model_type).to eq('Project') expect(new_upload.uploader).to eq('FileUploader') - end - it 'updates the legacy upload note so that it references the file in the markdown' do - expected_path = File.join('/uploads', new_upload.secret, 'image.png') + expected_path = File.join('/uploads', new_upload.secret, filename) expected_markdown = "some note \n ![image](#{expected_path})" - expect(note.reload.note).to eq(expected_markdown) - end - it 'removes the attachment from the note model' do - expect(note.reload.attachment.file).to be_nil + expect(note.reload.note).to eq(expected_markdown) + expect(note.attachment.file).to be_nil + + if remote + expect(bucket.files.get(remote_file[:key])).to be_nil + connection = ::Fog::Storage.new(FileUploader.object_store_credentials) + expect(connection.get_object('uploads', new_upload.path)[:status]).to eq(200) + else + expect(File.exist?(legacy_upload.absolute_path)).to be_falsey + expected_path = File.join(test_dir, 'uploads', project.disk_path, new_upload.secret, filename) + expect(File.exist?(expected_path)).to be_truthy + end + + expect { legacy_upload.reload }.to raise_error(ActiveRecord::RecordNotFound) end end @@ -120,23 +128,6 @@ RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover do end context 'when the upload is in local storage' do - shared_examples 'legacy local file' do - it 'removes the file correctly' do - expect(File.exist?(legacy_upload.absolute_path)).to be_truthy - - described_class.new(legacy_upload).execute - - expect(File.exist?(legacy_upload.absolute_path)).to be_falsey - end - - it 'moves legacy uploads to the correct location' do - described_class.new(legacy_upload).execute - - expected_path = File.join(test_dir, 'uploads', project.disk_path, new_upload.secret, filename) - expect(File.exist?(expected_path)).to be_truthy - end - end - context 'when the upload file does not exist on the filesystem' do let(:legacy_upload) { create_upload(note, filename, false) } @@ -201,15 +192,11 @@ RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover do path: "uploads/-/system/note/attachment/#{note.id}/#{filename}", model: note, mount_point: nil) end - it_behaves_like 'migrates the file correctly' - it_behaves_like 'legacy local file' - it_behaves_like 'legacy upload deletion' + it_behaves_like 'migrates the file correctly', false end context 'when the file can be handled correctly' do - it_behaves_like 'migrates the file correctly' - it_behaves_like 'legacy local file' - it_behaves_like 'legacy upload deletion' + it_behaves_like 'migrates the file correctly', false end end @@ -217,17 +204,13 @@ RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover do context 'when the file belongs to a legacy project' do let(:project) { legacy_project } - it_behaves_like 'migrates the file correctly' - it_behaves_like 'legacy local file' - it_behaves_like 'legacy upload deletion' + it_behaves_like 'migrates the file correctly', false end context 'when the file belongs to a hashed project' do let(:project) { hashed_project } - it_behaves_like 'migrates the file correctly' - it_behaves_like 'legacy local file' - it_behaves_like 'legacy upload deletion' + it_behaves_like 'migrates the file correctly', false end end @@ -244,17 +227,13 @@ RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover do context 'when the file belongs to a legacy project' do let(:project) { legacy_project } - it_behaves_like 'migrates the file correctly' - it_behaves_like 'legacy local file' - it_behaves_like 'legacy upload deletion' + it_behaves_like 'migrates the file correctly', false end context 'when the file belongs to a hashed project' do let(:project) { hashed_project } - it_behaves_like 'migrates the file correctly' - it_behaves_like 'legacy local file' - it_behaves_like 'legacy upload deletion' + it_behaves_like 'migrates the file correctly', false end end end @@ -272,23 +251,6 @@ RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover do stub_uploads_object_storage(FileUploader) end - shared_examples 'legacy remote file' do - it 'removes the file correctly' do - # expect(bucket.files.get(remote_file[:key])).to be_nil - - described_class.new(legacy_upload).execute - - expect(bucket.files.get(remote_file[:key])).to be_nil - end - - it 'moves legacy uploads to the correct remote location' do - described_class.new(legacy_upload).execute - - connection = ::Fog::Storage.new(FileUploader.object_store_credentials) - expect(connection.get_object('uploads', new_upload.path)[:status]).to eq(200) - end - end - context 'when the upload file does not exist on the filesystem' do it_behaves_like 'legacy upload deletion' end @@ -300,9 +262,7 @@ RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover do let(:project) { legacy_project } - it_behaves_like 'migrates the file correctly' - it_behaves_like 'legacy remote file' - it_behaves_like 'legacy upload deletion' + it_behaves_like 'migrates the file correctly', true end context 'when the file belongs to a hashed project' do @@ -312,9 +272,7 @@ RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover do let(:project) { hashed_project } - it_behaves_like 'migrates the file correctly' - it_behaves_like 'legacy remote file' - it_behaves_like 'legacy upload deletion' + it_behaves_like 'migrates the file correctly', true end end end diff --git a/spec/requests/api/boards_spec.rb b/spec/requests/api/boards_spec.rb index a63198c5407..36fc6101b84 100644 --- a/spec/requests/api/boards_spec.rb +++ b/spec/requests/api/boards_spec.rb @@ -35,7 +35,46 @@ RSpec.describe API::Boards do it_behaves_like 'group and project boards', "/projects/:id/boards" - describe "POST /projects/:id/boards/lists" do + describe "POST /projects/:id/boards" do + let(:url) { "/projects/#{board_parent.id}/boards" } + + it 'creates a new issue board' do + post api(url, user), params: { name: 'foo' } + + expect(response).to have_gitlab_http_status(:created) + expect(json_response['name']).to eq('foo') + end + + it 'fails to create a new board' do + post api(url, user), params: { some_name: 'foo' } + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['error']).to eq('name is missing') + end + end + + describe "PUT /projects/:id/boards/:board_id" do + let(:url) { "/projects/#{board_parent.id}/boards/#{board.id}" } + + it 'updates the issue board' do + put api(url, user), params: { name: 'changed board name' } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['name']).to eq('changed board name') + end + end + + describe "DELETE /projects/:id/boards/:board_id" do + let(:url) { "/projects/#{board_parent.id}/boards/#{board.id}" } + + it 'delete the issue board' do + delete api(url, user) + + expect(response).to have_gitlab_http_status(:no_content) + end + end + + describe "POST /projects/:id/boards/:board_id/lists" do let(:url) { "/projects/#{board_parent.id}/boards/#{board.id}/lists" } it 'creates a new issue board list for group labels' do @@ -65,7 +104,7 @@ RSpec.describe API::Boards do end end - describe "POST /groups/:id/boards/lists" do + describe "POST /groups/:id/boards/:board_id/lists" do let_it_be(:group) { create(:group) } let_it_be(:board_parent) { create(:group, parent: group ) } let(:url) { "/groups/#{board_parent.id}/boards/#{board.id}/lists" } diff --git a/spec/requests/api/personal_access_tokens_spec.rb b/spec/requests/api/personal_access_tokens_spec.rb new file mode 100644 index 00000000000..ccc5f322ff9 --- /dev/null +++ b/spec/requests/api/personal_access_tokens_spec.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe API::PersonalAccessTokens do + let_it_be(:path) { '/personal_access_tokens' } + let_it_be(:token1) { create(:personal_access_token) } + let_it_be(:token2) { create(:personal_access_token) } + let_it_be(:current_user) { create(:user) } + + describe 'GET /personal_access_tokens' do + context 'logged in as an Administrator' do + let_it_be(:current_user) { create(:admin) } + + it 'returns all PATs by default' do + get api(path, current_user) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response.count).to eq(PersonalAccessToken.all.count) + end + + context 'filtered with user_id parameter' do + it 'returns only PATs belonging to that user' do + get api(path, current_user), params: { user_id: token1.user.id } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response.count).to eq(1) + expect(json_response.first['user_id']).to eq(token1.user.id) + end + end + + context 'logged in as a non-Administrator' do + let_it_be(:current_user) { create(:user) } + let_it_be(:user) { create(:user) } + let_it_be(:token) { create(:personal_access_token, user: current_user)} + let_it_be(:other_token) { create(:personal_access_token, user: user) } + + it 'returns all PATs belonging to the signed-in user' do + get api(path, current_user, personal_access_token: token) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response.count).to eq(1) + expect(json_response.map { |r| r['user_id'] }.uniq).to contain_exactly(current_user.id) + end + + context 'filtered with user_id parameter' do + it 'returns PATs belonging to the specific user' do + get api(path, current_user, personal_access_token: token), params: { user_id: current_user.id } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response.count).to eq(1) + expect(json_response.map { |r| r['user_id'] }.uniq).to contain_exactly(current_user.id) + end + + it 'is unauthorized if filtered by a user other than current_user' do + get api(path, current_user, personal_access_token: token), params: { user_id: user.id } + + expect(response).to have_gitlab_http_status(:unauthorized) + end + end + end + + context 'not authenticated' do + it 'is forbidden' do + get api(path) + + expect(response).to have_gitlab_http_status(:unauthorized) + end + end + end + end + + describe 'DELETE /personal_access_tokens/:id' do + let(:path) { "/personal_access_tokens/#{token1.id}" } + + context 'when current_user is an administrator', :enable_admin_mode do + let_it_be(:admin_user) { create(:admin) } + let_it_be(:admin_token) { create(:personal_access_token, user: admin_user) } + let_it_be(:admin_path) { "/personal_access_tokens/#{admin_token.id}" } + + it 'revokes a different users token' do + delete api(path, admin_user) + + expect(response).to have_gitlab_http_status(:no_content) + expect(token1.reload.revoked?).to be true + end + + it 'revokes their own token' do + delete api(admin_path, admin_user) + + expect(response).to have_gitlab_http_status(:no_content) + end + end + + context 'when current_user is not an administrator' do + let_it_be(:user_token) { create(:personal_access_token, user: current_user) } + let_it_be(:user_token_path) { "/personal_access_tokens/#{user_token.id}" } + + it 'fails revokes a different users token' do + delete api(path, current_user) + + expect(response).to have_gitlab_http_status(:bad_request) + end + + it 'revokes their own token' do + delete api(user_token_path, current_user) + + expect(response).to have_gitlab_http_status(:no_content) + end + end + end +end diff --git a/spec/support/shared_examples/requests/api/discussions_shared_examples.rb b/spec/support/shared_examples/requests/api/discussions_shared_examples.rb index 6315c10b0c4..d8fd1dec06c 100644 --- a/spec/support/shared_examples/requests/api/discussions_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/discussions_shared_examples.rb @@ -117,15 +117,10 @@ RSpec.shared_examples 'discussions API' do |parent_type, noteable_type, id_name, expect(response).to have_gitlab_http_status(:unauthorized) end - it 'tracks a Notes::CreateService event' do - expect(Gitlab::Tracking).to receive(:event) do |category, action, data| - expect(category).to eq('Notes::CreateService') - expect(action).to eq('execute') - expect(data[:label]).to eq('note') - expect(data[:value]).to be_an(Integer) - end - + it 'tracks a Notes::CreateService event', :snowplow do post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user), params: { body: 'hi!' } + + expect_snowplow_event(category: 'Notes::CreateService', action: 'execute', label: 'note', value: anything) end context 'with notes_create_service_tracking feature flag disabled' do diff --git a/spec/support/shared_examples/requests/api/multiple_and_scoped_issue_boards_shared_examples.rb b/spec/support/shared_examples/requests/api/multiple_and_scoped_issue_boards_shared_examples.rb new file mode 100644 index 00000000000..54aa9d47dd8 --- /dev/null +++ b/spec/support/shared_examples/requests/api/multiple_and_scoped_issue_boards_shared_examples.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'multiple and scoped issue boards' do |route_definition| + let(:root_url) { route_definition.gsub(":id", board_parent.id.to_s) } + + context 'multiple issue boards' do + before do + board_parent.add_reporter(user) + stub_licensed_features(multiple_group_issue_boards: true) + end + + describe "POST #{route_definition}" do + it 'creates a board' do + post api(root_url, user), params: { name: "new board" } + + expect(response).to have_gitlab_http_status(:created) + + expect(response).to match_response_schema('public_api/v4/board', dir: "ee") + end + end + + describe "PUT #{route_definition}/:board_id" do + let(:url) { "#{root_url}/#{board.id}" } + + it 'updates a board' do + put api(url, user), params: { name: 'new name', weight: 4, labels: 'foo, bar' } + + expect(response).to have_gitlab_http_status(:ok) + + expect(response).to match_response_schema('public_api/v4/board', dir: "ee") + + board.reload + + expect(board.name).to eq('new name') + expect(board.weight).to eq(4) + expect(board.labels.map(&:title)).to contain_exactly('foo', 'bar') + end + + it 'does not remove missing attributes from the board' do + expect { put api(url, user), params: { name: 'new name' } } + .to not_change { board.reload.assignee } + .and not_change { board.reload.milestone } + .and not_change { board.reload.weight } + .and not_change { board.reload.labels.map(&:title).sort } + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('public_api/v4/board', dir: "ee") + end + + it 'allows removing optional attributes' do + put api(url, user), params: { name: 'new name', assignee_id: nil, milestone_id: nil, weight: nil, labels: nil } + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('public_api/v4/board', dir: "ee") + + board.reload + + expect(board.name).to eq('new name') + expect(board.assignee).to be_nil + expect(board.milestone).to be_nil + expect(board.weight).to be_nil + expect(board.labels).to be_empty + end + end + + describe "DELETE #{route_definition}/:board_id" do + let(:url) { "#{root_url}/#{board.id}" } + + it 'deletes a board' do + delete api(url, user) + + expect(response).to have_gitlab_http_status(:no_content) + end + end + end + + context 'with the scoped_issue_board-feature available' do + it 'returns the milestone when the `scoped_issue_board` feature is enabled' do + stub_licensed_features(scoped_issue_board: true) + + get api(root_url, user) + + expect(json_response.first["milestone"]).not_to be_nil + end + + it 'hides the milestone when the `scoped_issue_board` feature is disabled' do + stub_licensed_features(scoped_issue_board: false) + + get api(root_url, user) + + expect(json_response.first["milestone"]).to be_nil + end + end +end