diff options
Diffstat (limited to 'spec/requests')
51 files changed, 1436 insertions, 112 deletions
diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb index 80040cddd4d..19a34314bb8 100644 --- a/spec/requests/api/award_emoji_spec.rb +++ b/spec/requests/api/award_emoji_spec.rb @@ -9,6 +9,7 @@ describe API::AwardEmoji do set(:award_emoji) { create(:award_emoji, awardable: issue, user: user) } let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) } let!(:downvote) { create(:award_emoji, :downvote, awardable: merge_request, user: user) } + set(:note) { create(:note, project: project, noteable: issue) } before do diff --git a/spec/requests/api/badges_spec.rb b/spec/requests/api/badges_spec.rb index ea0a7d4c9b7..d931dea01e7 100644 --- a/spec/requests/api/badges_spec.rb +++ b/spec/requests/api/badges_spec.rb @@ -81,6 +81,7 @@ describe API::Badges do get api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", user) expect(response).to have_gitlab_http_status(200) + expect(json_response['name']).to eq(badge.name) expect(json_response['id']).to eq(badge.id) expect(json_response['link_url']).to eq(badge.link_url) expect(json_response['rendered_link_url']).to eq(badge.rendered_link_url) @@ -98,6 +99,7 @@ describe API::Badges do include_context 'source helpers' let(:source) { get_source(source_type) } + let(:example_name) { 'BadgeName' } let(:example_url) { 'http://www.example.com' } let(:example_url2) { 'http://www.example1.com' } @@ -105,7 +107,7 @@ describe API::Badges do it_behaves_like 'a 404 response when source is private' do let(:route) do post api("/#{source_type.pluralize}/#{source.id}/badges", stranger), - params: { link_url: example_url, image_url: example_url2 } + params: { name: example_name, link_url: example_url, image_url: example_url2 } end end @@ -128,11 +130,12 @@ describe API::Badges do it 'creates a new badge' do expect do post api("/#{source_type.pluralize}/#{source.id}/badges", maintainer), - params: { link_url: example_url, image_url: example_url2 } + params: { name: example_name, link_url: example_url, image_url: example_url2 } expect(response).to have_gitlab_http_status(201) end.to change { source.badges.count }.by(1) + expect(json_response['name']).to eq(example_name) expect(json_response['link_url']).to eq(example_url) expect(json_response['image_url']).to eq(example_url2) expect(json_response['kind']).to eq source_type @@ -169,6 +172,7 @@ describe API::Badges do context "with :sources == #{source_type.pluralize}" do let(:badge) { source.badges.first } + let(:example_name) { 'BadgeName' } let(:example_url) { 'http://www.example.com' } let(:example_url2) { 'http://www.example1.com' } @@ -197,9 +201,10 @@ describe API::Badges do context 'when authenticated as a maintainer/owner' do it 'updates the member', :quarantine do put api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", maintainer), - params: { link_url: example_url, image_url: example_url2 } + params: { name: example_name, link_url: example_url, image_url: example_url2 } expect(response).to have_gitlab_http_status(200) + expect(json_response['name']).to eq(example_name) expect(json_response['link_url']).to eq(example_url) expect(json_response['image_url']).to eq(example_url2) expect(json_response['kind']).to eq source_type @@ -297,7 +302,7 @@ describe API::Badges do expect(response).to have_gitlab_http_status(200) - expect(json_response.keys).to contain_exactly('link_url', 'rendered_link_url', 'image_url', 'rendered_image_url') + expect(json_response.keys).to contain_exactly('name', 'link_url', 'rendered_link_url', 'image_url', 'rendered_image_url') expect(json_response['link_url']).to eq(example_url) expect(json_response['image_url']).to eq(example_url2) expect(json_response['rendered_link_url']).to eq(example_url) @@ -351,9 +356,9 @@ describe API::Badges do project.add_developer(developer) project.add_maintainer(maintainer) project.request_access(access_requester) - project.project_badges << build(:project_badge, project: project) - project.project_badges << build(:project_badge, project: project) - project_group.badges << build(:group_badge, group: group) + project.project_badges << build(:project_badge, project: project, name: 'ExampleBadge1') + project.project_badges << build(:project_badge, project: project, name: 'ExampleBadge2') + project_group.badges << build(:group_badge, group: group, name: 'ExampleBadge3') end end @@ -362,8 +367,8 @@ describe API::Badges do group.add_developer(developer) group.add_owner(maintainer) group.request_access(access_requester) - group.badges << build(:group_badge, group: group) - group.badges << build(:group_badge, group: group) + group.badges << build(:group_badge, group: group, name: 'ExampleBadge4') + group.badges << build(:group_badge, group: group, name: 'ExampleBadge5') end end end diff --git a/spec/requests/api/boards_spec.rb b/spec/requests/api/boards_spec.rb index 8a67e956165..510ef9d7d0a 100644 --- a/spec/requests/api/boards_spec.rb +++ b/spec/requests/api/boards_spec.rb @@ -69,6 +69,7 @@ describe API::Boards do set(:group) { create(:group) } set(:board_parent) { create(:group, parent: group ) } let(:url) { "/groups/#{board_parent.id}/boards/#{board.id}/lists" } + set(:board) { create(:board, group: board_parent) } it 'creates a new board list for ancestor group labels' do diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index 675b06b057c..99374d28324 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -131,7 +131,7 @@ describe API::Branches do end new_branch_name = 'protected-branch' - CreateBranchService.new(project, current_user).execute(new_branch_name, 'master') + ::Branches::CreateService.new(project, current_user).execute(new_branch_name, 'master') create(:protected_branch, name: new_branch_name, project: project) expect do @@ -629,7 +629,9 @@ describe API::Branches do describe 'DELETE /projects/:id/repository/branches/:branch' do before do - allow_any_instance_of(Repository).to receive(:rm_branch).and_return(true) + allow_next_instance_of(Repository) do |instance| + allow(instance).to receive(:rm_branch).and_return(true) + end end it 'removes branch' do @@ -666,7 +668,9 @@ describe API::Branches do describe 'DELETE /projects/:id/repository/merged_branches' do before do - allow_any_instance_of(Repository).to receive(:rm_branch).and_return(true) + allow_next_instance_of(Repository) do |instance| + allow(instance).to receive(:rm_branch).and_return(true) + end end it 'returns 202 with json body' do diff --git a/spec/requests/api/broadcast_messages_spec.rb b/spec/requests/api/broadcast_messages_spec.rb index 541acb29857..9dc639a25a2 100644 --- a/spec/requests/api/broadcast_messages_spec.rb +++ b/spec/requests/api/broadcast_messages_spec.rb @@ -29,7 +29,7 @@ describe API::BroadcastMessages do expect(response).to include_pagination_headers expect(json_response).to be_kind_of(Array) expect(json_response.first.keys) - .to match_array(%w(id message starts_at ends_at color font active)) + .to match_array(%w(id message starts_at ends_at color font active target_path)) end end @@ -52,7 +52,7 @@ describe API::BroadcastMessages do expect(response).to have_gitlab_http_status(200) expect(json_response['id']).to eq message.id expect(json_response.keys) - .to match_array(%w(id message starts_at ends_at color font active)) + .to match_array(%w(id message starts_at ends_at color font active target_path)) end end @@ -100,6 +100,15 @@ describe API::BroadcastMessages do expect(json_response['color']).to eq attrs[:color] expect(json_response['font']).to eq attrs[:font] end + + it 'accepts a target path' do + attrs = attributes_for(:broadcast_message, target_path: "*/welcome") + + post api('/broadcast_messages', admin), params: attrs + + expect(response).to have_gitlab_http_status(201) + expect(json_response['target_path']).to eq attrs[:target_path] + end end end @@ -150,6 +159,15 @@ describe API::BroadcastMessages do expect(response).to have_gitlab_http_status(200) expect { message.reload }.to change { message.message }.to('new message') end + + it 'accepts a new target_path' do + attrs = { target_path: '*/welcome' } + + put api("/broadcast_messages/#{message.id}", admin), params: attrs + + expect(response).to have_gitlab_http_status(200) + expect(json_response['target_path']).to eq attrs[:target_path] + end end end diff --git a/spec/requests/api/deployments_spec.rb b/spec/requests/api/deployments_spec.rb index 26849c0991d..3dc8e5749d4 100644 --- a/spec/requests/api/deployments_spec.rb +++ b/spec/requests/api/deployments_spec.rb @@ -30,40 +30,47 @@ describe API::Deployments do expect(json_response.last['iid']).to eq(deployment_3.iid) end - describe 'ordering' do - using RSpec::Parameterized::TableSyntax + context 'with updated_at filters specified' do + it 'returns projects deployments with last update in specified datetime range' do + get api("/projects/#{project.id}/deployments", user), params: { updated_before: 30.minutes.ago, updated_after: 90.minutes.ago } - let(:order_by) { nil } - let(:sort) { nil } + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response.first['id']).to eq(deployment_3.id) + end + end + + describe 'ordering' do + let(:order_by) { 'iid' } + let(:sort) { 'desc' } subject { get api("/projects/#{project.id}/deployments?order_by=#{order_by}&sort=#{sort}", user) } + before do + subject + end + def expect_deployments(ordered_deployments) - json_response.each_with_index do |deployment_json, index| - expect(deployment_json['id']).to eq(public_send(ordered_deployments[index]).id) - end + expect(json_response.map { |d| d['id'] }).to eq(ordered_deployments.map(&:id)) end - before do - subject + it 'returns ordered deployments' do + expect(json_response.map { |i| i['id'] }).to eq([deployment_2.id, deployment_1.id, deployment_3.id]) end - where(:order_by, :sort, :ordered_deployments) do - 'created_at' | 'asc' | [:deployment_3, :deployment_2, :deployment_1] - 'created_at' | 'desc' | [:deployment_1, :deployment_2, :deployment_3] - 'id' | 'asc' | [:deployment_1, :deployment_2, :deployment_3] - 'id' | 'desc' | [:deployment_3, :deployment_2, :deployment_1] - 'iid' | 'asc' | [:deployment_3, :deployment_1, :deployment_2] - 'iid' | 'desc' | [:deployment_2, :deployment_1, :deployment_3] - 'ref' | 'asc' | [:deployment_2, :deployment_1, :deployment_3] - 'ref' | 'desc' | [:deployment_3, :deployment_1, :deployment_2] - 'updated_at' | 'asc' | [:deployment_2, :deployment_3, :deployment_1] - 'updated_at' | 'desc' | [:deployment_1, :deployment_3, :deployment_2] + context 'with invalid order_by' do + let(:order_by) { 'wrong_sorting_value' } + + it 'returns error' do + expect(response).to have_gitlab_http_status(400) + end end - with_them do - it 'returns the deployments ordered' do - expect_deployments(ordered_deployments) + context 'with invalid sorting' do + let(:sort) { 'wrong_sorting_direction' } + + it 'returns error' do + expect(response).to have_gitlab_http_status(400) end end end @@ -140,7 +147,7 @@ describe API::Deployments do expect(response).to have_gitlab_http_status(500) end - it 'links any merged merge requests to the deployment' do + it 'links any merged merge requests to the deployment', :sidekiq_inline do mr = create( :merge_request, :merged, @@ -192,7 +199,7 @@ describe API::Deployments do expect(json_response['ref']).to eq('master') end - it 'links any merged merge requests to the deployment' do + it 'links any merged merge requests to the deployment', :sidekiq_inline do mr = create( :merge_request, :merged, @@ -335,4 +342,40 @@ describe API::Deployments do end end end + + context 'prevent N + 1 queries' do + context 'when the endpoint returns multiple records' do + let(:project) { create(:project) } + + def create_record + create(:deployment, :success, project: project) + end + + def request_with_query_count + ActiveRecord::QueryRecorder.new { trigger_request }.count + end + + def trigger_request + get api("/projects/#{project.id}/deployments?order_by=updated_at&sort=asc", user) + end + + before do + create_record + end + + it 'succeeds' do + trigger_request + + expect(response).to have_gitlab_http_status(200) + + expect(json_response.size).to eq(1) + end + + it 'does not increase the query count' do + expect { create_record }.not_to change { request_with_query_count } + + expect(json_response.size).to eq(2) + end + end + end end diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index ec18156f49f..ab915af8ab0 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -315,11 +315,11 @@ describe API::Files do expect(range['commit']['message']) .to eq("Files, encoding and much more\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n") - expect(range['commit']['authored_date']).to eq('2014-02-27T08:14:56.000Z') + expect(range['commit']['authored_date']).to eq('2014-02-27T10:14:56.000+02:00') expect(range['commit']['author_name']).to eq('Dmitriy Zaporozhets') expect(range['commit']['author_email']).to eq('dmitriy.zaporozhets@gmail.com') - expect(range['commit']['committed_date']).to eq('2014-02-27T08:14:56.000Z') + expect(range['commit']['committed_date']).to eq('2014-02-27T10:14:56.000+02:00') expect(range['commit']['committer_name']).to eq('Dmitriy Zaporozhets') expect(range['commit']['committer_email']).to eq('dmitriy.zaporozhets@gmail.com') end @@ -548,8 +548,9 @@ describe API::Files do end it "returns a 400 if editor fails to create file" do - allow_any_instance_of(Repository).to receive(:create_file) - .and_raise(Gitlab::Git::CommitError, 'Cannot create file') + allow_next_instance_of(Repository) do |instance| + allow(instance).to receive(:create_file).and_raise(Gitlab::Git::CommitError, 'Cannot create file') + end post api(route("any%2Etxt"), user), params: params @@ -636,7 +637,7 @@ describe API::Files do put api(route(file_path), user), params: params_with_stale_id expect(response).to have_gitlab_http_status(400) - expect(json_response['message']).to eq('You are attempting to update a file that has changed since you started editing it.') + expect(json_response['message']).to eq(_('You are attempting to update a file that has changed since you started editing it.')) end it "updates existing file in project repo with accepts correct last commit id" do @@ -698,7 +699,9 @@ describe API::Files do end it "returns a 400 if fails to delete file" do - allow_any_instance_of(Repository).to receive(:delete_file).and_raise(Gitlab::Git::CommitError, 'Cannot delete file') + allow_next_instance_of(Repository) do |instance| + allow(instance).to receive(:delete_file).and_raise(Gitlab::Git::CommitError, 'Cannot delete file') + end delete api(route(file_path), user), params: params diff --git a/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb index 5b910d5bfe0..b24981873c8 100644 --- a/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb +++ b/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb @@ -75,6 +75,7 @@ describe 'Adding an AwardEmoji' do describe 'marking Todos as done' do let(:user) { current_user} + subject { post_graphql_mutation(mutation, current_user: user) } include_examples 'creating award emojis marks Todos as done' diff --git a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb index ae628d3e56c..5e2c0e668a5 100644 --- a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb +++ b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb @@ -83,6 +83,7 @@ describe 'Toggling an AwardEmoji' do describe 'marking Todos as done' do let(:user) { current_user} + subject { post_graphql_mutation(mutation, current_user: user) } include_examples 'creating award emojis marks Todos as done' diff --git a/spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb b/spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb new file mode 100644 index 00000000000..4d0bb59b030 --- /dev/null +++ b/spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Setting an issue as confidential' do + include GraphqlHelpers + + let(:current_user) { create(:user) } + let(:issue) { create(:issue) } + let(:project) { issue.project } + let(:input) { { confidential: true } } + + let(:mutation) do + variables = { + project_path: project.full_path, + iid: issue.iid.to_s + } + graphql_mutation(:issue_set_confidential, variables.merge(input), + <<-QL.strip_heredoc + clientMutationId + errors + issue { + iid + confidential + } + QL + ) + end + + def mutation_response + graphql_mutation_response(:issue_set_confidential) + end + + before do + project.add_developer(current_user) + end + + it 'returns an error if the user is not allowed to update the issue' do + error = "The resource that you are attempting to access does not exist or you don't have permission to perform this action" + post_graphql_mutation(mutation, current_user: create(:user)) + + expect(graphql_errors).to include(a_hash_including('message' => error)) + end + + it 'updates the issue confidentiality' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['issue']['confidential']).to be_truthy + end +end diff --git a/spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb b/spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb new file mode 100644 index 00000000000..1efa9e16233 --- /dev/null +++ b/spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Setting Due Date of an issue' do + include GraphqlHelpers + + let(:current_user) { create(:user) } + let(:issue) { create(:issue) } + let(:project) { issue.project } + let(:input) { { due_date: 2.days.since } } + + let(:mutation) do + variables = { + project_path: project.full_path, + iid: issue.iid.to_s + } + graphql_mutation(:issue_set_due_date, variables.merge(input), + <<-QL.strip_heredoc + clientMutationId + errors + issue { + iid + dueDate + } + QL + ) + end + + def mutation_response + graphql_mutation_response(:issue_set_due_date) + end + + before do + project.add_developer(current_user) + end + + it 'returns an error if the user is not allowed to update the issue' do + error = "The resource that you are attempting to access does not exist or you don't have permission to perform this action" + post_graphql_mutation(mutation, current_user: create(:user)) + + expect(graphql_errors).to include(a_hash_including('message' => error)) + end + + it 'updates the issue due date' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['issue']['dueDate']).to eq(2.days.since.to_date.to_s) + end + + context 'when passing due date without a date value' do + let(:input) { { due_date: 'test' } } + + it 'returns internal server error' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(graphql_errors).to include(a_hash_including('message' => 'Internal server error')) + end + end +end diff --git a/spec/requests/api/graphql/mutations/snippets/create_spec.rb b/spec/requests/api/graphql/mutations/snippets/create_spec.rb new file mode 100644 index 00000000000..9ef45c0f6bc --- /dev/null +++ b/spec/requests/api/graphql/mutations/snippets/create_spec.rb @@ -0,0 +1,144 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Creating a Snippet' do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project) } + let(:content) { 'Initial content' } + let(:description) { 'Initial description' } + let(:title) { 'Initial title' } + let(:file_name) { 'Initial file_name' } + let(:visibility_level) { 'public' } + let(:project_path) { nil } + + let(:mutation) do + variables = { + content: content, + description: description, + visibility_level: visibility_level, + file_name: file_name, + title: title, + project_path: project_path + } + + graphql_mutation(:create_snippet, variables) + end + + def mutation_response + graphql_mutation_response(:create_snippet) + end + + context 'when the user does not have permission' do + let(:current_user) { nil } + + it_behaves_like 'a mutation that returns top-level errors', + errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] + + it 'does not create the Snippet' do + expect do + post_graphql_mutation(mutation, current_user: current_user) + end.not_to change { Snippet.count } + end + + context 'when user is not authorized in the project' do + let(:project_path) { project.full_path } + + it 'does not create the snippet when the user is not authorized' do + expect do + post_graphql_mutation(mutation, current_user: current_user) + end.not_to change { Snippet.count } + end + end + end + + context 'when the user has permission' do + let(:current_user) { user } + + context 'with PersonalSnippet' do + it 'creates the Snippet' do + expect do + post_graphql_mutation(mutation, current_user: current_user) + end.to change { Snippet.count }.by(1) + end + + it 'returns the created Snippet' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(mutation_response['snippet']['content']).to eq(content) + expect(mutation_response['snippet']['title']).to eq(title) + expect(mutation_response['snippet']['description']).to eq(description) + expect(mutation_response['snippet']['fileName']).to eq(file_name) + expect(mutation_response['snippet']['visibilityLevel']).to eq(visibility_level) + expect(mutation_response['snippet']['project']).to be_nil + end + end + + context 'with ProjectSnippet' do + let(:project_path) { project.full_path } + + before do + project.add_developer(current_user) + end + + it 'creates the Snippet' do + expect do + post_graphql_mutation(mutation, current_user: current_user) + end.to change { Snippet.count }.by(1) + end + + it 'returns the created Snippet' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(mutation_response['snippet']['content']).to eq(content) + expect(mutation_response['snippet']['title']).to eq(title) + expect(mutation_response['snippet']['description']).to eq(description) + expect(mutation_response['snippet']['fileName']).to eq(file_name) + expect(mutation_response['snippet']['visibilityLevel']).to eq(visibility_level) + expect(mutation_response['snippet']['project']['fullPath']).to eq(project_path) + end + + context 'when the project path is invalid' do + let(:project_path) { 'foobar' } + + it 'returns an an error' do + post_graphql_mutation(mutation, current_user: current_user) + errors = json_response['errors'] + + expect(errors.first['message']).to eq(Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR) + end + end + + context 'when the feature is disabled' do + it 'returns an an error' do + project.project_feature.update_attribute(:snippets_access_level, ProjectFeature::DISABLED) + + post_graphql_mutation(mutation, current_user: current_user) + errors = json_response['errors'] + + expect(errors.first['message']).to eq(Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR) + end + end + end + + context 'when there are ActiveRecord validation errors' do + let(:title) { '' } + + it_behaves_like 'a mutation that returns errors in the response', errors: ["Title can't be blank"] + + it 'does not create the Snippet' do + expect do + post_graphql_mutation(mutation, current_user: current_user) + end.not_to change { Snippet.count } + end + + it 'does not return Snippet' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(mutation_response['snippet']).to be_nil + end + end + end +end diff --git a/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb b/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb new file mode 100644 index 00000000000..351d2db8973 --- /dev/null +++ b/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Destroying a Snippet' do + include GraphqlHelpers + + let(:current_user) { snippet.author } + let(:mutation) do + variables = { + id: snippet.to_global_id.to_s + } + + graphql_mutation(:destroy_snippet, variables) + end + + def mutation_response + graphql_mutation_response(:destroy_snippet) + end + + shared_examples 'graphql delete actions' do + context 'when the user does not have permission' do + let(:current_user) { create(:user) } + + it_behaves_like 'a mutation that returns top-level errors', + errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] + + it 'does not destroy the Snippet' do + expect do + post_graphql_mutation(mutation, current_user: current_user) + end.not_to change { Snippet.count } + end + end + + context 'when the user has permission' do + it 'destroys the Snippet' do + expect do + post_graphql_mutation(mutation, current_user: current_user) + end.to change { Snippet.count }.by(-1) + end + + it 'returns an empty Snippet' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(mutation_response).to have_key('snippet') + expect(mutation_response['snippet']).to be_nil + end + end + end + + describe 'PersonalSnippet' do + it_behaves_like 'graphql delete actions' do + let_it_be(:snippet) { create(:personal_snippet) } + end + end + + describe 'ProjectSnippet' do + let_it_be(:project) { create(:project, :private) } + let_it_be(:snippet) { create(:project_snippet, :private, project: project, author: create(:user)) } + + context 'when the author is not a member of the project' do + it 'returns an an error' do + post_graphql_mutation(mutation, current_user: current_user) + errors = json_response['errors'] + + expect(errors.first['message']).to eq(Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR) + end + end + + context 'when the author is a member of the project' do + before do + project.add_developer(current_user) + end + + it_behaves_like 'graphql delete actions' + + context 'when the snippet project feature is disabled' do + it 'returns an an error' do + project.project_feature.update_attribute(:snippets_access_level, ProjectFeature::DISABLED) + + post_graphql_mutation(mutation, current_user: current_user) + errors = json_response['errors'] + + expect(errors.first['message']).to eq(Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR) + end + end + end + end +end diff --git a/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb b/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb new file mode 100644 index 00000000000..0e8fe4987b9 --- /dev/null +++ b/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Mark snippet as spam' do + include GraphqlHelpers + + let_it_be(:admin) { create(:admin) } + let_it_be(:other_user) { create(:user) } + let_it_be(:snippet) { create(:personal_snippet) } + let_it_be(:user_agent_detail) { create(:user_agent_detail, subject: snippet) } + let(:current_user) { snippet.author } + let(:mutation) do + variables = { + id: snippet.to_global_id.to_s + } + + graphql_mutation(:mark_as_spam_snippet, variables) + end + + def mutation_response + graphql_mutation_response(:mark_as_spam_snippet) + end + + shared_examples 'does not mark the snippet as spam' do + it do + expect do + post_graphql_mutation(mutation, current_user: current_user) + end.not_to change { snippet.reload.user_agent_detail.submitted } + end + end + + context 'when the user does not have permission' do + let(:current_user) { other_user } + + it_behaves_like 'a mutation that returns top-level errors', + errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] + + it_behaves_like 'does not mark the snippet as spam' + end + + context 'when the user has permission' do + context 'when user can not mark snippet as spam' do + it_behaves_like 'does not mark the snippet as spam' + end + + context 'when user can mark snippet as spam' do + let(:current_user) { admin } + + before do + stub_application_setting(akismet_enabled: true) + end + + it 'marks snippet as spam' do + expect_next_instance_of(SpamService) do |instance| + expect(instance).to receive(:mark_as_spam!) + end + + post_graphql_mutation(mutation, current_user: current_user) + end + end + end +end diff --git a/spec/requests/api/graphql/mutations/snippets/update_spec.rb b/spec/requests/api/graphql/mutations/snippets/update_spec.rb new file mode 100644 index 00000000000..deaa9e8a237 --- /dev/null +++ b/spec/requests/api/graphql/mutations/snippets/update_spec.rb @@ -0,0 +1,144 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Updating a Snippet' do + include GraphqlHelpers + + let_it_be(:original_content) { 'Initial content' } + let_it_be(:original_description) { 'Initial description' } + let_it_be(:original_title) { 'Initial title' } + let_it_be(:original_file_name) { 'Initial file_name' } + let(:updated_content) { 'Updated content' } + let(:updated_description) { 'Updated description' } + let(:updated_title) { 'Updated_title' } + let(:updated_file_name) { 'Updated file_name' } + let(:current_user) { snippet.author } + + let(:mutation) do + variables = { + id: GitlabSchema.id_from_object(snippet).to_s, + content: updated_content, + description: updated_description, + visibility_level: 'public', + file_name: updated_file_name, + title: updated_title + } + + graphql_mutation(:update_snippet, variables) + end + + def mutation_response + graphql_mutation_response(:update_snippet) + end + + shared_examples 'graphql update actions' do + context 'when the user does not have permission' do + let(:current_user) { create(:user) } + + it_behaves_like 'a mutation that returns top-level errors', + errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] + + it 'does not update the Snippet' do + expect do + post_graphql_mutation(mutation, current_user: current_user) + end.not_to change { snippet.reload } + end + end + + context 'when the user has permission' do + it 'updates the Snippet' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(snippet.reload.title).to eq(updated_title) + end + + it 'returns the updated Snippet' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(mutation_response['snippet']['content']).to eq(updated_content) + expect(mutation_response['snippet']['title']).to eq(updated_title) + expect(mutation_response['snippet']['description']).to eq(updated_description) + expect(mutation_response['snippet']['fileName']).to eq(updated_file_name) + expect(mutation_response['snippet']['visibilityLevel']).to eq('public') + end + + context 'when there are ActiveRecord validation errors' do + let(:updated_title) { '' } + + it_behaves_like 'a mutation that returns errors in the response', errors: ["Title can't be blank"] + + it 'does not update the Snippet' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(snippet.reload.title).to eq(original_title) + end + + it 'returns the Snippet with its original values' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(mutation_response['snippet']['content']).to eq(original_content) + expect(mutation_response['snippet']['title']).to eq(original_title) + expect(mutation_response['snippet']['description']).to eq(original_description) + expect(mutation_response['snippet']['fileName']).to eq(original_file_name) + expect(mutation_response['snippet']['visibilityLevel']).to eq('private') + end + end + end + end + + describe 'PersonalSnippet' do + it_behaves_like 'graphql update actions' do + let_it_be(:snippet) do + create(:personal_snippet, + :private, + file_name: original_file_name, + title: original_title, + content: original_content, + description: original_description) + end + end + end + + describe 'ProjectSnippet' do + let_it_be(:project) { create(:project, :private) } + let_it_be(:snippet) do + create(:project_snippet, + :private, + project: project, + author: create(:user), + file_name: original_file_name, + title: original_title, + content: original_content, + description: original_description) + end + + context 'when the author is not a member of the project' do + it 'returns an an error' do + post_graphql_mutation(mutation, current_user: current_user) + errors = json_response['errors'] + + expect(errors.first['message']).to eq(Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR) + end + end + + context 'when the author is a member of the project' do + before do + project.add_developer(current_user) + end + + it_behaves_like 'graphql update actions' + + context 'when the snippet project feature is disabled' do + it 'returns an an error' do + project.project_feature.update_attribute(:snippets_access_level, ProjectFeature::DISABLED) + + post_graphql_mutation(mutation, current_user: current_user) + errors = json_response['errors'] + + expect(errors.first['message']).to eq(Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR) + end + end + end + end +end diff --git a/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb b/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb new file mode 100644 index 00000000000..40e085027d7 --- /dev/null +++ b/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Marking all todos done' do + include GraphqlHelpers + + let_it_be(:current_user) { create(:user) } + let_it_be(:author) { create(:user) } + let_it_be(:other_user) { create(:user) } + let_it_be(:other_user2) { create(:user) } + + let_it_be(:todo1) { create(:todo, user: current_user, author: author, state: :pending) } + let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :done) } + let_it_be(:todo3) { create(:todo, user: current_user, author: author, state: :pending) } + + let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :pending) } + + let(:input) { {} } + + let(:mutation) do + graphql_mutation(:todos_mark_all_done, input, + <<-QL.strip_heredoc + clientMutationId + errors + updatedIds + QL + ) + end + + def mutation_response + graphql_mutation_response(:todos_mark_all_done) + end + + it 'marks all pending todos as done' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(todo1.reload.state).to eq('done') + expect(todo2.reload.state).to eq('done') + expect(todo3.reload.state).to eq('done') + expect(other_user_todo.reload.state).to eq('pending') + + updated_todo_ids = mutation_response['updatedIds'] + expect(updated_todo_ids).to contain_exactly(global_id_of(todo1), global_id_of(todo3)) + end + + it 'behaves as expected if there are no todos for the requesting user' do + post_graphql_mutation(mutation, current_user: other_user2) + + expect(todo1.reload.state).to eq('pending') + expect(todo2.reload.state).to eq('done') + expect(todo3.reload.state).to eq('pending') + expect(other_user_todo.reload.state).to eq('pending') + + updated_todo_ids = mutation_response['updatedIds'] + expect(updated_todo_ids).to be_empty + end + + context 'when user is not logged in' do + let(:current_user) { nil } + + it_behaves_like 'a mutation that returns top-level errors', + errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action'] + end +end diff --git a/spec/requests/api/graphql/mutations/todos/restore_spec.rb b/spec/requests/api/graphql/mutations/todos/restore_spec.rb new file mode 100644 index 00000000000..faa36c8273a --- /dev/null +++ b/spec/requests/api/graphql/mutations/todos/restore_spec.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Restoring Todos' do + include GraphqlHelpers + + let_it_be(:current_user) { create(:user) } + let_it_be(:author) { create(:user) } + let_it_be(:other_user) { create(:user) } + + let_it_be(:todo1) { create(:todo, user: current_user, author: author, state: :done) } + let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :pending) } + + let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :done) } + + let(:input) { { id: todo1.to_global_id.to_s } } + + let(:mutation) do + graphql_mutation(:todo_restore, input, + <<-QL.strip_heredoc + clientMutationId + errors + todo { + id + state + } + QL + ) + end + + def mutation_response + graphql_mutation_response(:todo_restore) + end + + it 'restores a single todo' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(todo1.reload.state).to eq('pending') + expect(todo2.reload.state).to eq('pending') + expect(other_user_todo.reload.state).to eq('done') + + todo = mutation_response['todo'] + expect(todo['id']).to eq(todo1.to_global_id.to_s) + expect(todo['state']).to eq('pending') + end + + context 'when todo is already marked pending' do + let(:input) { { id: todo2.to_global_id.to_s } } + + it 'has the expected response' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(todo1.reload.state).to eq('done') + expect(todo2.reload.state).to eq('pending') + expect(other_user_todo.reload.state).to eq('done') + + todo = mutation_response['todo'] + expect(todo['id']).to eq(todo2.to_global_id.to_s) + expect(todo['state']).to eq('pending') + end + end + + context 'when todo does not belong to requesting user' do + let(:input) { { id: other_user_todo.to_global_id.to_s } } + let(:access_error) { 'The resource that you are attempting to access does not exist or you don\'t have permission to perform this action' } + + it 'contains the expected error' do + post_graphql_mutation(mutation, current_user: current_user) + + errors = json_response['errors'] + expect(errors).not_to be_blank + expect(errors.first['message']).to eq(access_error) + + expect(todo1.reload.state).to eq('done') + expect(todo2.reload.state).to eq('pending') + expect(other_user_todo.reload.state).to eq('done') + end + end + + context 'when using an invalid gid' do + let(:input) { { id: 'invalid_gid' } } + let(:invalid_gid_error) { 'invalid_gid is not a valid GitLab id.' } + + it 'contains the expected error' do + post_graphql_mutation(mutation, current_user: current_user) + + errors = json_response['errors'] + expect(errors).not_to be_blank + expect(errors.first['message']).to eq(invalid_gid_error) + + expect(todo1.reload.state).to eq('done') + expect(todo2.reload.state).to eq('pending') + expect(other_user_todo.reload.state).to eq('done') + end + end +end diff --git a/spec/requests/api/graphql/project/error_tracking/sentry_detailed_error_request_spec.rb b/spec/requests/api/graphql/project/error_tracking/sentry_detailed_error_request_spec.rb new file mode 100644 index 00000000000..d10380dab3a --- /dev/null +++ b/spec/requests/api/graphql/project/error_tracking/sentry_detailed_error_request_spec.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe 'getting a detailed sentry error' do + include GraphqlHelpers + + let_it_be(:project) { create(:project, :repository) } + let_it_be(:project_setting) { create(:project_error_tracking_setting, project: project) } + let_it_be(:current_user) { project.owner } + let_it_be(:sentry_detailed_error) { build(:detailed_error_tracking_error) } + let(:sentry_gid) { sentry_detailed_error.to_global_id.to_s } + let(:fields) do + <<~QUERY + #{all_graphql_fields_for('SentryDetailedError'.classify)} + QUERY + end + + let(:query) do + graphql_query_for( + 'project', + { 'fullPath' => project.full_path }, + query_graphql_field('sentryDetailedError', { id: sentry_gid }, fields) + ) + end + + let(:error_data) { graphql_data['project']['sentryDetailedError'] } + + it_behaves_like 'a working graphql query' do + before do + post_graphql(query, current_user: current_user) + end + end + + context 'when data is loading via reactive cache' do + before do + post_graphql(query, current_user: current_user) + end + + it "is expected to return an empty error" do + expect(error_data).to eq nil + end + end + + context 'reactive cache returns data' do + before do + expect_any_instance_of(ErrorTracking::ProjectErrorTrackingSetting) + .to receive(:issue_details) + .and_return({ issue: sentry_detailed_error }) + + post_graphql(query, current_user: current_user) + end + + it "is expected to return a valid error" do + expect(error_data['id']).to eql sentry_gid + expect(error_data['sentryId']).to eql sentry_detailed_error.id.to_s + expect(error_data['status']).to eql sentry_detailed_error.status.upcase + expect(error_data['firstSeen']).to eql sentry_detailed_error.first_seen + expect(error_data['lastSeen']).to eql sentry_detailed_error.last_seen + end + + it 'is expected to return the frequency correctly' do + expect(error_data['frequency'].count).to eql sentry_detailed_error.frequency.count + + first_frequency = error_data['frequency'].first + expect(Time.parse(first_frequency['time'])).to eql Time.at(sentry_detailed_error.frequency[0][0], in: 0) + expect(first_frequency['count']).to eql sentry_detailed_error.frequency[0][1] + end + end +end diff --git a/spec/requests/api/graphql_spec.rb b/spec/requests/api/graphql_spec.rb index 54401ec4085..d0378278600 100644 --- a/spec/requests/api/graphql_spec.rb +++ b/spec/requests/api/graphql_spec.rb @@ -46,7 +46,7 @@ describe 'GraphQL' do end it 'logs the exception in Sentry and continues with the request' do - expect(Gitlab::Sentry).to receive(:track_exception).at_least(1).times + expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).at_least(1).times expect(Gitlab::GraphqlLogger).to receive(:info) post_graphql(query, variables: {}) @@ -146,7 +146,7 @@ describe 'GraphQL' do end it "logs a warning that the 'calls_gitaly' field declaration is missing" do - expect(Gitlab::Sentry).to receive(:track_exception).once + expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).once post_graphql(query, current_user: user) end diff --git a/spec/requests/api/group_clusters_spec.rb b/spec/requests/api/group_clusters_spec.rb index 97465647a87..14027db01c4 100644 --- a/spec/requests/api/group_clusters_spec.rb +++ b/spec/requests/api/group_clusters_spec.rb @@ -261,7 +261,7 @@ describe API::GroupClusters do it 'responds with 400' do expect(response).to have_gitlab_http_status(400) - expect(json_response['message']['base'].first).to include('Instance does not support multiple Kubernetes clusters') + expect(json_response['message']['base'].first).to eq(_('Instance does not support multiple Kubernetes clusters')) end end @@ -372,7 +372,7 @@ describe API::GroupClusters do end it 'returns validation error' do - expect(json_response['message']['platform_kubernetes.base'].first).to eq('Cannot modify managed Kubernetes cluster') + expect(json_response['message']['platform_kubernetes.base'].first).to eq(_('Cannot modify managed Kubernetes cluster')) end end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index cb97398805a..a4f68df928f 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -488,6 +488,51 @@ describe API::Groups do expect(response).to have_gitlab_http_status(404) end end + + context 'limiting the number of projects and shared_projects in the response' do + let(:limit) { 1 } + + before do + stub_const("GroupProjectsFinder::DEFAULT_PROJECTS_LIMIT", limit) + + # creates 3 public projects + create_list(:project, 3, :public, namespace: group1) + + # creates 3 shared projects + public_group = create(:group, :public) + projects_to_be_shared = create_list(:project, 3, :public, namespace: public_group) + + projects_to_be_shared.each do |project| + create(:project_group_link, project: project, group: group1) + end + end + + context 'when limiting feature is enabled' do + before do + stub_feature_flags(limit_projects_in_groups_api: true) + end + + it 'limits projects and shared_projects' do + get api("/groups/#{group1.id}") + + expect(json_response['projects'].count).to eq(limit) + expect(json_response['shared_projects'].count).to eq(limit) + end + end + + context 'when limiting feature is not enabled' do + before do + stub_feature_flags(limit_projects_in_groups_api: false) + end + + it 'does not limit projects and shared_projects' do + get api("/groups/#{group1.id}") + + expect(json_response['projects'].count).to eq(3) + expect(json_response['shared_projects'].count).to eq(3) + end + end + end end describe 'PUT /groups/:id' do @@ -1030,8 +1075,9 @@ describe API::Groups do let(:project_path) { CGI.escape(project.full_path) } before do - allow_any_instance_of(Projects::TransferService) - .to receive(:execute).and_return(true) + allow_next_instance_of(Projects::TransferService) do |instance| + allow(instance).to receive(:execute).and_return(true) + end end context "when authenticated as user" do diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb index bbfe40041a1..26174611c58 100644 --- a/spec/requests/api/helpers_spec.rb +++ b/spec/requests/api/helpers_spec.rb @@ -146,13 +146,13 @@ describe API::Helpers do let(:personal_access_token) { create(:personal_access_token, user: user) } it "returns a 401 response for an invalid token" do - env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = 'invalid token' + env[Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER] = 'invalid token' expect { current_user }.to raise_error /401/ end it "returns a 403 response for a user without access" do - env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token + env[Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(false) expect { current_user }.to raise_error /403/ @@ -160,7 +160,7 @@ describe API::Helpers do it 'returns a 403 response for a user who is blocked' do user.block! - env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token + env[Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token expect { current_user }.to raise_error /403/ end @@ -168,7 +168,7 @@ describe API::Helpers do context 'when terms are enforced' do before do enforce_terms - env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token + env[Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token end it 'returns a 403 when a user has not accepted the terms' do @@ -183,27 +183,27 @@ describe API::Helpers do end it "sets current_user" do - env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token + env[Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token expect(current_user).to eq(user) end it "does not allow tokens without the appropriate scope" do personal_access_token = create(:personal_access_token, user: user, scopes: ['read_user']) - env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token + env[Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token expect { current_user }.to raise_error Gitlab::Auth::InsufficientScopeError end it 'does not allow revoked tokens' do personal_access_token.revoke! - env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token + env[Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token expect { current_user }.to raise_error Gitlab::Auth::RevokedError end it 'does not allow expired tokens' do personal_access_token.update!(expires_at: 1.day.ago) - env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token + env[Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token expect { current_user }.to raise_error Gitlab::Auth::ExpiredError end @@ -213,7 +213,7 @@ describe API::Helpers do before do stub_config_setting(impersonation_enabled: false) - env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token + env[Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token end it 'does not allow impersonation tokens' do @@ -226,11 +226,11 @@ describe API::Helpers do describe '.handle_api_exception' do before do allow_any_instance_of(self.class).to receive(:rack_response) - allow(Gitlab::Sentry).to receive(:enabled?).and_return(true) stub_sentry_settings - configure_sentry + expect(Gitlab::ErrorTracking).to receive(:sentry_dsn).and_return(Gitlab.config.sentry.dsn) + Gitlab::ErrorTracking.configure Raven.client.configuration.encoding = 'json' end @@ -478,7 +478,7 @@ describe API::Helpers do context 'passed as param' do before do - set_param(Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_PARAM, token.token) + set_param(Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_PARAM, token.token) end it_behaves_like 'sudo' @@ -486,7 +486,7 @@ describe API::Helpers do context 'passed as header' do before do - env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = token.token + env[Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER] = token.token end it_behaves_like 'sudo' diff --git a/spec/requests/api/internal/base_spec.rb b/spec/requests/api/internal/base_spec.rb index fcff2cde730..ecbb81294a0 100644 --- a/spec/requests/api/internal/base_spec.rb +++ b/spec/requests/api/internal/base_spec.rb @@ -193,7 +193,15 @@ describe API::Internal::Base do end it 'responds successfully when a user is not found' do - get(api("/internal/discover"), params: { username: 'noone', secret_token: secret_token }) + get(api('/internal/discover'), params: { username: 'noone', secret_token: secret_token }) + + expect(response).to have_gitlab_http_status(200) + + expect(response.body).to eq('null') + end + + it 'response successfully when passing invalid params' do + get(api('/internal/discover'), params: { nothing: 'to find a user', secret_token: secret_token }) expect(response).to have_gitlab_http_status(200) @@ -318,7 +326,7 @@ describe API::Internal::Base do expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path) expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage)) expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage)) - expect(json_response["gitaly"]["features"]).to eq('gitaly-feature-get-all-lfs-pointers-go' => 'true', 'gitaly-feature-inforef-uploadpack-cache' => 'true') + expect(json_response["gitaly"]["features"]).to eq('gitaly-feature-inforef-uploadpack-cache' => 'true', 'gitaly-feature-get-tag-messages-go' => 'true', 'gitaly-feature-filter-shas-with-signatures-go' => 'true') expect(user.reload.last_activity_on).to eql(Date.today) end end @@ -338,7 +346,7 @@ describe API::Internal::Base do expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path) expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage)) expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage)) - expect(json_response["gitaly"]["features"]).to eq('gitaly-feature-get-all-lfs-pointers-go' => 'true', 'gitaly-feature-inforef-uploadpack-cache' => 'true') + expect(json_response["gitaly"]["features"]).to eq('gitaly-feature-inforef-uploadpack-cache' => 'true', 'gitaly-feature-get-tag-messages-go' => 'true', 'gitaly-feature-filter-shas-with-signatures-go' => 'true') expect(user.reload.last_activity_on).to be_nil end end @@ -580,7 +588,7 @@ describe API::Internal::Base do expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path) expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage)) expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage)) - expect(json_response["gitaly"]["features"]).to eq('gitaly-feature-get-all-lfs-pointers-go' => 'true', 'gitaly-feature-inforef-uploadpack-cache' => 'true') + expect(json_response["gitaly"]["features"]).to eq('gitaly-feature-inforef-uploadpack-cache' => 'true', 'gitaly-feature-get-tag-messages-go' => 'true', 'gitaly-feature-filter-shas-with-signatures-go' => 'true') end end @@ -819,7 +827,6 @@ describe API::Internal::Base do before do project.add_developer(user) - allow(described_class).to receive(:identify).and_return(user) allow_any_instance_of(Gitlab::Identifier).to receive(:identify).and_return(user) end diff --git a/spec/requests/api/internal/pages_spec.rb b/spec/requests/api/internal/pages_spec.rb index 03bf748b471..2887163fe58 100644 --- a/spec/requests/api/internal/pages_spec.rb +++ b/spec/requests/api/internal/pages_spec.rb @@ -4,10 +4,10 @@ require 'spec_helper' describe API::Internal::Pages do describe "GET /internal/pages" do - let(:pages_shared_secret) { SecureRandom.random_bytes(Gitlab::Pages::SECRET_LENGTH) } + let(:pages_secret) { SecureRandom.random_bytes(Gitlab::Pages::SECRET_LENGTH) } before do - allow(Gitlab::Pages).to receive(:secret).and_return(pages_shared_secret) + allow(Gitlab::Pages).to receive(:secret).and_return(pages_secret) end def query_host(host, headers = {}) @@ -47,11 +47,12 @@ describe API::Internal::Pages do project.mark_pages_as_deployed end - context 'not existing host' do - it 'responds with 404 Not Found' do + context 'domain does not exist' do + it 'responds with 204 no content' do query_host('pages.gitlab.io') - expect(response).to have_gitlab_http_status(404) + expect(response).to have_gitlab_http_status(204) + expect(response.body).to be_empty end end diff --git a/spec/requests/api/issues/get_project_issues_spec.rb b/spec/requests/api/issues/get_project_issues_spec.rb index 06a43ea6b02..59aeb91edd2 100644 --- a/spec/requests/api/issues/get_project_issues_spec.rb +++ b/spec/requests/api/issues/get_project_issues_spec.rb @@ -589,6 +589,24 @@ describe API::Issues do expect(json_response['subscribed']).to be_truthy end + context "moved_to_id" do + let(:moved_issue) do + create(:closed_issue, project: project, moved_to: issue) + end + + it 'returns null when not moved' do + get api("/projects/#{project.id}/issues/#{issue.iid}", user) + + expect(json_response['moved_to_id']).to be_nil + end + + it 'returns issue id when moved' do + get api("/projects/#{project.id}/issues/#{moved_issue.iid}", user) + + expect(json_response['moved_to_id']).to eq(issue.id) + end + end + it 'exposes the closed_at attribute' do get api("/projects/#{project.id}/issues/#{closed_issue.iid}", user) diff --git a/spec/requests/api/issues/issues_spec.rb b/spec/requests/api/issues/issues_spec.rb index 61a94b682be..50a0a80b542 100644 --- a/spec/requests/api/issues/issues_spec.rb +++ b/spec/requests/api/issues/issues_spec.rb @@ -832,7 +832,7 @@ describe API::Issues do end context 'when issue does not exist' do - it 'returns 404 when trying to move an issue' do + it 'returns 404 when trying to delete an issue' do delete api("/projects/#{project.id}/issues/123", user) expect(response).to have_gitlab_http_status(404) diff --git a/spec/requests/api/issues/post_projects_issues_spec.rb b/spec/requests/api/issues/post_projects_issues_spec.rb index 3a55b437ead..e9f678d164e 100644 --- a/spec/requests/api/issues/post_projects_issues_spec.rb +++ b/spec/requests/api/issues/post_projects_issues_spec.rb @@ -453,7 +453,7 @@ describe API::Issues do params: { to_project_id: project.id } expect(response).to have_gitlab_http_status(400) - expect(json_response['message']).to eq('Cannot move issue to project it originates from!') + expect(json_response['message']).to eq(s_('MoveIssue|Cannot move issue to project it originates from!')) end end @@ -463,7 +463,7 @@ describe API::Issues do params: { to_project_id: target_project2.id } expect(response).to have_gitlab_http_status(400) - expect(json_response['message']).to eq('Cannot move issue due to insufficient permissions!') + expect(json_response['message']).to eq(s_('MoveIssue|Cannot move issue due to insufficient permissions!')) end end diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb index 020e7659a4c..82bf607b911 100644 --- a/spec/requests/api/jobs_spec.rb +++ b/spec/requests/api/jobs_spec.rb @@ -795,9 +795,11 @@ describe API::Jobs do before do stub_remote_url_206(url, file_path) - allow_any_instance_of(JobArtifactUploader).to receive(:file_storage?) { false } - allow_any_instance_of(JobArtifactUploader).to receive(:url) { url } - allow_any_instance_of(JobArtifactUploader).to receive(:size) { File.size(file_path) } + allow_next_instance_of(JobArtifactUploader) do |instance| + allow(instance).to receive(:file_storage?) { false } + allow(instance).to receive(:url) { url } + allow(instance).to receive(:size) { File.size(file_path) } + end end it 'returns specific job trace' do diff --git a/spec/requests/api/keys_spec.rb b/spec/requests/api/keys_spec.rb index 6802a0cfdab..f7da1abcfdf 100644 --- a/spec/requests/api/keys_spec.rb +++ b/spec/requests/api/keys_spec.rb @@ -25,7 +25,6 @@ describe API::Keys do it 'returns single ssh key with user information' do user.keys << key - user.save get api("/keys/#{key.id}", admin) expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq(key.title) @@ -40,4 +39,73 @@ describe API::Keys do end end end + + describe 'GET /keys?fingerprint=' do + it 'returns authentication error' do + get api("/keys?fingerprint=#{key.fingerprint}") + + expect(response).to have_gitlab_http_status(401) + end + + it 'returns authentication error when authenticated as user' do + get api("/keys?fingerprint=#{key.fingerprint}", user) + + expect(response).to have_gitlab_http_status(403) + end + + context 'when authenticated as admin' do + it 'returns 404 for non-existing SSH md5 fingerprint' do + get api("/keys?fingerprint=11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11", admin) + + expect(response).to have_gitlab_http_status(404) + expect(json_response['message']).to eq('404 Key Not Found') + end + + it 'returns 404 for non-existing SSH sha256 fingerprint' do + get api("/keys?fingerprint=#{URI.encode_www_form_component("SHA256:nUhzNyftwADy8AH3wFY31tAKs7HufskYTte2aXo1lCg")}", admin) + + expect(response).to have_gitlab_http_status(404) + expect(json_response['message']).to eq('404 Key Not Found') + end + + it 'returns user if SSH md5 fingerprint found' do + user.keys << key + + get api("/keys?fingerprint=#{key.fingerprint}", admin) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['title']).to eq(key.title) + expect(json_response['user']['id']).to eq(user.id) + expect(json_response['user']['username']).to eq(user.username) + end + + it 'returns user if SSH sha256 fingerprint found' do + user.keys << key + + get api("/keys?fingerprint=#{URI.encode_www_form_component("SHA256:" + key.fingerprint_sha256)}", admin) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['title']).to eq(key.title) + expect(json_response['user']['id']).to eq(user.id) + expect(json_response['user']['username']).to eq(user.username) + end + + it 'returns user if SSH sha256 fingerprint found' do + user.keys << key + + get api("/keys?fingerprint=#{URI.encode_www_form_component("sha256:" + key.fingerprint_sha256)}", admin) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['title']).to eq(key.title) + expect(json_response['user']['id']).to eq(user.id) + expect(json_response['user']['username']).to eq(user.username) + end + + it "does not include the user's `is_admin` flag" do + get api("/keys?fingerprint=#{key.fingerprint}", admin) + + expect(json_response['user']['is_admin']).to be_nil + end + end + end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index c96c80b6998..e5ad1a6378e 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -1567,6 +1567,18 @@ describe API::MergeRequests do expect(response).to have_gitlab_http_status(200) end + it 'does not merge if merge_when_pipeline_succeeds is passed and the pipeline has failed' do + create(:ci_pipeline, + :failed, + sha: merge_request.diff_head_sha, + merge_requests_as_head_pipeline: [merge_request]) + + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), params: { merge_when_pipeline_succeeds: true } + + expect(response).to have_gitlab_http_status(405) + expect(merge_request.reload.state).to eq('opened') + end + it "enables merge when pipeline succeeds if the pipeline is active" do allow_any_instance_of(MergeRequest).to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline) allow(pipeline).to receive(:active?).and_return(true) diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index e57d7699892..cc2038a7245 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -92,7 +92,7 @@ describe API::Notes do end context "current user can view the note" do - it "returns an empty array" do + it "returns a non-empty array" do get api("/projects/#{ext_proj.id}/issues/#{ext_issue.iid}/notes", private_user) expect(response).to have_gitlab_http_status(200) diff --git a/spec/requests/api/pages/pages_spec.rb b/spec/requests/api/pages/pages_spec.rb new file mode 100644 index 00000000000..2085c509eff --- /dev/null +++ b/spec/requests/api/pages/pages_spec.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe API::Pages do + let_it_be(:project) { create(:project, path: 'my.project', pages_https_only: false) } + let_it_be(:admin) { create(:admin) } + let_it_be(:user) { create(:user) } + + before do + project.add_maintainer(user) + project.mark_pages_as_deployed + end + + describe 'DELETE /projects/:id/pages' do + context 'when Pages is disabled' do + before do + allow(Gitlab.config.pages).to receive(:enabled).and_return(false) + end + + it_behaves_like '404 response' do + let(:request) { delete api("/projects/#{project.id}/pages", admin)} + end + end + + context 'when Pages is enabled' do + before do + allow(Gitlab.config.pages).to receive(:enabled).and_return(true) + end + + context 'when Pages are deployed' do + it 'returns 204' do + delete api("/projects/#{project.id}/pages", admin) + + expect(response).to have_gitlab_http_status(204) + end + + it 'removes the pages' do + expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project).and_return true + expect(PagesWorker).to receive(:perform_in).with(5.minutes, :remove, project.namespace.full_path, anything) + + delete api("/projects/#{project.id}/pages", admin ) + + expect(project.reload.pages_metadatum.deployed?).to be(false) + end + end + + context 'when pages are not deployed' do + before do + project.mark_pages_as_not_deployed + end + + it 'returns 204' do + delete api("/projects/#{project.id}/pages", admin) + + expect(response).to have_gitlab_http_status(204) + end + end + + context 'when there is no project' do + it 'returns 404' do + id = -1 + + delete api("/projects/#{id}/pages", admin) + + expect(response).to have_gitlab_http_status(404) + end + end + end + end +end diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb index cce52cfc1ca..a9d570b5696 100644 --- a/spec/requests/api/pipelines_spec.rb +++ b/spec/requests/api/pipelines_spec.rb @@ -237,6 +237,20 @@ describe API::Pipelines do end end + context 'when updated_at filters are specified' do + let!(:pipeline1) { create(:ci_pipeline, project: project, updated_at: 2.days.ago) } + let!(:pipeline2) { create(:ci_pipeline, project: project, updated_at: 4.days.ago) } + let!(:pipeline3) { create(:ci_pipeline, project: project, updated_at: 1.hour.ago) } + + it 'returns pipelines with last update date in specified datetime range' do + get api("/projects/#{project.id}/pipelines", user), params: { updated_before: 1.day.ago, updated_after: 3.days.ago } + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response.first['id']).to eq(pipeline1.id) + end + end + context 'when order_by and sort are specified' do context 'when order_by user_id' do before do @@ -384,7 +398,7 @@ describe API::Pipelines do post api("/projects/#{project.id}/pipeline", user), params: { ref: project.default_branch } expect(response).to have_gitlab_http_status(400) - expect(json_response['message']['base'].first).to eq 'Missing .gitlab-ci.yml file' + expect(json_response['message']['base'].first).to eq 'Missing CI config file' expect(json_response).not_to be_an Array end end diff --git a/spec/requests/api/project_clusters_spec.rb b/spec/requests/api/project_clusters_spec.rb index 04e59238877..f3d005322f2 100644 --- a/spec/requests/api/project_clusters_spec.rb +++ b/spec/requests/api/project_clusters_spec.rb @@ -260,7 +260,7 @@ describe API::ProjectClusters do it 'responds with 400' do expect(response).to have_gitlab_http_status(400) - expect(json_response['message']['base'].first).to eq('Instance does not support multiple Kubernetes clusters') + expect(json_response['message']['base'].first).to eq(_('Instance does not support multiple Kubernetes clusters')) end end @@ -376,7 +376,7 @@ describe API::ProjectClusters do end it 'returns validation error' do - expect(json_response['message']['platform_kubernetes.base'].first).to eq('Cannot modify managed Kubernetes cluster') + expect(json_response['message']['platform_kubernetes.base'].first).to eq(_('Cannot modify managed Kubernetes cluster')) end end diff --git a/spec/requests/api/project_export_spec.rb b/spec/requests/api/project_export_spec.rb index 605ff888234..37f2cc85a50 100644 --- a/spec/requests/api/project_export_spec.rb +++ b/spec/requests/api/project_export_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe API::ProjectExport do +describe API::ProjectExport, :clean_gitlab_redis_cache do set(:project) { create(:project) } set(:project_none) { create(:project) } set(:project_started) { create(:project) } @@ -47,6 +47,19 @@ describe API::ProjectExport do it_behaves_like '404 response' end + shared_examples_for 'when rate limit is exceeded' do + before do + allow(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).and_return(true) + end + + it 'prevents requesting project export' do + request + + expect(response).to have_gitlab_http_status(429) + expect(json_response['message']['error']).to eq('This endpoint has been requested too many times. Try again later.') + end + end + describe 'GET /projects/:project_id/export' do shared_examples_for 'get project export status not found' do it_behaves_like '404 response' do @@ -219,6 +232,12 @@ describe API::ProjectExport do let(:user) { admin } it_behaves_like 'get project download by strategy' + + context 'when rate limit is exceeded' do + let(:request) { get api(download_path, admin) } + + include_examples 'when rate limit is exceeded' + end end context 'when user is a maintainer' do @@ -329,6 +348,12 @@ describe API::ProjectExport do let(:user) { admin } it_behaves_like 'post project export start' + + context 'when rate limit is exceeded' do + let(:request) { post api(path, admin) } + + include_examples 'when rate limit is exceeded' + end end context 'when user is a maintainer' do diff --git a/spec/requests/api/project_import_spec.rb b/spec/requests/api/project_import_spec.rb index 866adbd424e..186f0f52a46 100644 --- a/spec/requests/api/project_import_spec.rb +++ b/spec/requests/api/project_import_spec.rb @@ -7,6 +7,7 @@ describe API::ProjectImport do let(:user) { create(:user) } let(:file) { File.join('spec', 'features', 'projects', 'import_export', 'test_project_export.tar.gz') } let(:namespace) { create(:group) } + before do allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) stub_uploads_object_storage(FileUploader) diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb index cac3f07d0d0..bfb6f10efa3 100644 --- a/spec/requests/api/project_snippets_spec.rb +++ b/spec/requests/api/project_snippets_spec.rb @@ -179,7 +179,9 @@ describe API::ProjectSnippets do end before do - allow_any_instance_of(AkismetService).to receive(:spam?).and_return(true) + allow_next_instance_of(AkismetService) do |instance| + allow(instance).to receive(:spam?).and_return(true) + end end context 'when the snippet is private' do @@ -269,7 +271,9 @@ describe API::ProjectSnippets do end before do - allow_any_instance_of(AkismetService).to receive(:spam?).and_return(true) + allow_next_instance_of(AkismetService) do |instance| + allow(instance).to receive(:spam?).and_return(true) + end end context 'when the snippet is private' do diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index cda2dd7d2f4..9af4f484f99 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -1882,6 +1882,7 @@ describe API::Projects do describe "POST /projects/:id/share" do let(:group) { create(:group) } + before do group.add_developer(user) end diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb index bf05587fe03..da04e852795 100644 --- a/spec/requests/api/releases_spec.rb +++ b/spec/requests/api/releases_spec.rb @@ -558,6 +558,43 @@ describe API::Releases do end end + context 'when using JOB-TOKEN auth' do + let(:job) { create(:ci_build, user: maintainer) } + let(:params) do + { + name: 'Another release', + tag_name: 'v0.2', + description: 'Another nice release', + released_at: '2019-04-25T10:00:00+09:00' + } + end + + context 'when no token is provided' do + it 'returns a :not_found error' do + post api("/projects/#{project.id}/releases"), params: params + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when an invalid token is provided' do + it 'returns an :unauthorized error' do + post api("/projects/#{project.id}/releases"), params: params.merge(job_token: 'yadayadayada') + + expect(response).to have_gitlab_http_status(:unauthorized) + end + end + + context 'when a valid token is provided' do + it 'creates the release' do + post api("/projects/#{project.id}/releases"), params: params.merge(job_token: job.token) + + expect(response).to have_gitlab_http_status(:created) + expect(project.releases.last.description).to eq('Another nice release') + end + end + end + context 'when tag does not exist in git repository' do let(:params) do { diff --git a/spec/requests/api/remote_mirrors_spec.rb b/spec/requests/api/remote_mirrors_spec.rb new file mode 100644 index 00000000000..c5ba9bd223e --- /dev/null +++ b/spec/requests/api/remote_mirrors_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe API::RemoteMirrors do + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :repository, :remote_mirror) } + + describe 'GET /projects/:id/remote_mirrors' do + let(:route) { "/projects/#{project.id}/remote_mirrors" } + + it 'requires `admin_remote_mirror` permission' do + project.add_developer(user) + + get api(route, user) + + expect(response).to have_gitlab_http_status(:unauthorized) + end + + it 'returns a list of remote mirrors' do + project.add_maintainer(user) + + get api(route, user) + + expect(response).to have_gitlab_http_status(:success) + expect(response).to match_response_schema('remote_mirrors') + end + + context 'with the `remote_mirrors_api` feature disabled' do + before do + stub_feature_flags(remote_mirrors_api: false) + end + + it 'responds with `not_found`' do + get api(route, user) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end +end diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 6138036b0af..cc6cadb190a 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -513,6 +513,16 @@ describe API::Runner, :clean_gitlab_redis_shared_state do expect(json_response['features']).to eq(expected_features) end + it 'creates persistent ref' do + expect_any_instance_of(Ci::PersistentRef).to receive(:create_ref) + .with(job.sha, "refs/#{Repository::REF_PIPELINES}/#{job.commit_id}") + + request_job info: { platform: :darwin } + + expect(response).to have_gitlab_http_status(201) + expect(json_response['id']).to eq(job.id) + end + context 'when job is made for tag' do let!(:job) { create(:ci_build, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) } diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index a080b59173f..7c7620389b4 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -4,7 +4,6 @@ require "spec_helper" describe API::Services do set(:user) { create(:user) } - set(:admin) { create(:admin) } set(:user2) { create(:user) } set(:project) do @@ -88,14 +87,7 @@ describe API::Services do expect(response).to have_gitlab_http_status(401) end - it "returns all properties of service #{service} when authenticated as admin" do - get api("/projects/#{project.id}/services/#{dashed_service}", admin) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['properties'].keys).to match_array(service_instance.api_field_names) - end - - it "returns properties of service #{service} other than passwords when authenticated as project owner" do + it "returns all properties of service #{service}" do get api("/projects/#{project.id}/services/#{dashed_service}", user) expect(response).to have_gitlab_http_status(200) diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index b7586307929..af86ba86303 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe API::Settings, 'Settings' do let(:user) { create(:user) } + set(:admin) { create(:admin) } describe "GET /application/settings" do @@ -36,6 +37,7 @@ describe API::Settings, 'Settings' do expect(json_response['allow_local_requests_from_system_hooks']).to be(true) expect(json_response).not_to have_key('performance_bar_allowed_group_path') expect(json_response).not_to have_key('performance_bar_enabled') + expect(json_response['snippet_size_limit']).to eq(50.megabytes) end end @@ -85,7 +87,8 @@ describe API::Settings, 'Settings' do allow_local_requests_from_web_hooks_and_services: true, allow_local_requests_from_system_hooks: false, push_event_hooks_limit: 2, - push_event_activities_limit: 2 + push_event_activities_limit: 2, + snippet_size_limit: 5 } expect(response).to have_gitlab_http_status(200) @@ -121,6 +124,7 @@ describe API::Settings, 'Settings' do expect(json_response['allow_local_requests_from_system_hooks']).to eq(false) expect(json_response['push_event_hooks_limit']).to eq(2) expect(json_response['push_event_activities_limit']).to eq(2) + expect(json_response['snippet_size_limit']).to eq(5) end end diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb index 36d2a0d7ea7..f32be7a8765 100644 --- a/spec/requests/api/snippets_spec.rb +++ b/spec/requests/api/snippets_spec.rb @@ -66,6 +66,9 @@ describe API::Snippets do let!(:public_snippet_other) { create(:personal_snippet, :public, author: other_user) } let!(:private_snippet_other) { create(:personal_snippet, :private, author: other_user) } let!(:internal_snippet_other) { create(:personal_snippet, :internal, author: other_user) } + let!(:public_snippet_project) { create(:project_snippet, :public, author: user) } + let!(:private_snippet_project) { create(:project_snippet, :private, author: user) } + let!(:internal_snippet_project) { create(:project_snippet, :internal, author: user) } it 'returns all snippets with public visibility from all users' do get api("/snippets/public", user) @@ -76,10 +79,10 @@ describe API::Snippets do expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly( public_snippet.id, public_snippet_other.id) - expect(json_response.map { |snippet| snippet['web_url']} ).to include( + expect(json_response.map { |snippet| snippet['web_url']} ).to contain_exactly( "http://localhost/snippets/#{public_snippet.id}", "http://localhost/snippets/#{public_snippet_other.id}") - expect(json_response.map { |snippet| snippet['raw_url']} ).to include( + expect(json_response.map { |snippet| snippet['raw_url']} ).to contain_exactly( "http://localhost/snippets/#{public_snippet.id}/raw", "http://localhost/snippets/#{public_snippet_other.id}/raw") end @@ -235,7 +238,9 @@ describe API::Snippets do end before do - allow_any_instance_of(AkismetService).to receive(:spam?).and_return(true) + allow_next_instance_of(AkismetService) do |instance| + allow(instance).to receive(:spam?).and_return(true) + end end context 'when the snippet is private' do @@ -322,7 +327,9 @@ describe API::Snippets do end before do - allow_any_instance_of(AkismetService).to receive(:spam?).and_return(true) + allow_next_instance_of(AkismetService) do |instance| + allow(instance).to receive(:spam?).and_return(true) + end end context 'when the snippet is private' do @@ -368,6 +375,7 @@ describe API::Snippets do describe 'DELETE /snippets/:id' do let!(:public_snippet) { create(:personal_snippet, :public, author: user) } + it 'deletes snippet' do expect do delete api("/snippets/#{public_snippet.id}", user) diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index dca87d5e4ce..09e63b86cfc 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -328,7 +328,9 @@ describe API::Tags do let(:route) { "/projects/#{project_id}/repository/tags/#{tag_name}" } before do - allow_any_instance_of(Repository).to receive(:rm_tag).and_return(true) + allow_next_instance_of(Repository) do |instance| + allow(instance).to receive(:rm_tag).and_return(true) + end end shared_examples_for 'repository delete tag' do diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 1a1e80f1ce3..0a22a09b8a6 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -1261,6 +1261,25 @@ describe API::Users do expect { Namespace.find(namespace.id) }.to raise_error ActiveRecord::RecordNotFound end + context "sole owner of a group" do + let!(:group) { create(:group).tap { |group| group.add_owner(user) } } + + context "hard delete disabled" do + it "does not delete user" do + perform_enqueued_jobs { delete api("/users/#{user.id}", admin)} + expect(response).to have_gitlab_http_status(409) + end + end + + context "hard delete enabled" do + it "delete user and group", :sidekiq_might_not_need_inline do + perform_enqueued_jobs { delete api("/users/#{user.id}?hard_delete=true", admin)} + expect(response).to have_gitlab_http_status(204) + expect(Group.exists?(group.id)).to be_falsy + end + end + end + it_behaves_like '412 response' do let(:request) { api("/users/#{user.id}", admin) } end @@ -2105,6 +2124,7 @@ describe API::Users do describe 'GET /user/status' do let(:path) { '/user/status' } + it_behaves_like 'rendering user status' end diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 1b17d492b0c..42b4bd71b88 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -456,7 +456,7 @@ describe 'Git HTTP requests' do end it "responds with status 403" do - expect(Rack::Attack::Allow2Ban).to receive(:filter).and_return(true) + expect(Rack::Attack::Allow2Ban).to receive(:banned?).and_return(true) expect(Gitlab::AuthLogger).to receive(:error).with({ message: 'Rack_Attack', env: :blocklist, diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb index c1f99115612..199c2dbe9ca 100644 --- a/spec/requests/jwt_controller_spec.rb +++ b/spec/requests/jwt_controller_spec.rb @@ -134,7 +134,9 @@ describe JwtController do context 'when internal auth is disabled' do it 'rejects the authorization attempt with personal access token message' do - allow_any_instance_of(ApplicationSetting).to receive(:password_authentication_enabled_for_git?) { false } + allow_next_instance_of(ApplicationSetting) do |instance| + allow(instance).to receive(:password_authentication_enabled_for_git?) { false } + end get '/jwt/auth', params: parameters, headers: headers expect(response).to have_gitlab_http_status(401) diff --git a/spec/requests/projects/merge_requests/creations_spec.rb b/spec/requests/projects/merge_requests/creations_spec.rb new file mode 100644 index 00000000000..d192e1bca7f --- /dev/null +++ b/spec/requests/projects/merge_requests/creations_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'merge requests creations' do + describe 'GET /:namespace/:project/merge_requests/new' do + include ProjectForksHelper + + let(:project) { create(:project, :repository) } + let(:user) { project.owner } + + before do + login_as(user) + end + + def get_new + get namespace_project_new_merge_request_path(namespace_id: project.namespace, project_id: project) + end + + it 'avoids N+1 DB queries even with forked projects' do + control = ActiveRecord::QueryRecorder.new(skip_cached: false) { get_new } + + 5.times { fork_project(project, user) } + + expect { get_new }.not_to exceed_query_limit(control) + end + end +end diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb index 4d5055a7e27..9968b2e4aba 100644 --- a/spec/requests/rack_attack_global_spec.rb +++ b/spec/requests/rack_attack_global_spec.rb @@ -84,7 +84,9 @@ describe 'Rack Attack global throttles' do expect(response).to have_http_status 200 end - expect_any_instance_of(Rack::Attack::Request).to receive(:ip).at_least(:once).and_return('1.2.3.4') + expect_next_instance_of(Rack::Attack::Request) do |instance| + expect(instance).to receive(:ip).at_least(:once).and_return('1.2.3.4') + end # would be over limit for the same IP get url_that_does_not_require_authentication @@ -100,6 +102,18 @@ describe 'Rack Attack global throttles' do end end + context 'when the request is authenticated by a runner token' do + let(:request_jobs_url) { '/api/v4/jobs/request' } + let(:runner) { create(:ci_runner) } + + it 'does not cont as unauthenticated' do + (1 + requests_per_period).times do + post request_jobs_url, params: { token: runner.token } + expect(response).to have_http_status 204 + end + end + end + it 'logs RackAttack info into structured logs' do requests_per_period.times do get url_that_does_not_require_authentication @@ -249,10 +263,10 @@ describe 'Rack Attack global throttles' do expect_rejection { post protected_path_that_does_not_require_authentication, params: post_params } end - context 'when Omnibus throttle is present' do + context 'when Omnibus throttle should be used' do before do allow(Gitlab::Throttle) - .to receive(:omnibus_protected_paths_present?).and_return(true) + .to receive(:should_use_omnibus_protected_paths?).and_return(true) end it 'allows requests over the rate limit' do @@ -298,7 +312,7 @@ describe 'Rack Attack global throttles' do it_behaves_like 'rate-limited token-authenticated requests' end - context 'when Omnibus throttle is present' do + context 'when Omnibus throttle should be used' do let(:request_args) { [api(api_partial_url, personal_access_token: token)] } let(:other_user_request_args) { [api(api_partial_url, personal_access_token: other_user_token)] } @@ -309,7 +323,7 @@ describe 'Rack Attack global throttles' do stub_application_setting(settings_to_set) allow(Gitlab::Throttle) - .to receive(:omnibus_protected_paths_present?).and_return(true) + .to receive(:should_use_omnibus_protected_paths?).and_return(true) end it 'allows requests over the rate limit' do @@ -339,7 +353,7 @@ describe 'Rack Attack global throttles' do it_behaves_like 'rate-limited web authenticated requests' - context 'when Omnibus throttle is present' do + context 'when Omnibus throttle should be used' do before do settings_to_set[:"#{throttle_setting_prefix}_requests_per_period"] = requests_per_period settings_to_set[:"#{throttle_setting_prefix}_period_in_seconds"] = period_in_seconds @@ -347,7 +361,7 @@ describe 'Rack Attack global throttles' do stub_application_setting(settings_to_set) allow(Gitlab::Throttle) - .to receive(:omnibus_protected_paths_present?).and_return(true) + .to receive(:should_use_omnibus_protected_paths?).and_return(true) login_as(user) end diff --git a/spec/requests/user_avatar_spec.rb b/spec/requests/user_avatar_spec.rb new file mode 100644 index 00000000000..9451674161c --- /dev/null +++ b/spec/requests/user_avatar_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Loading a user avatar' do + let(:user) { create(:user, :with_avatar) } + + context 'when logged in' do + # The exact query count will vary depending on the 2FA settings of the + # instance, group, and user. Removing those extra 2FA queries in this case + # may not be a good idea, so we just set up the ideal case. + before do + stub_application_setting(require_two_factor_authentication: true) + + login_as(create(:user, :two_factor)) + end + + # One each for: current user, avatar user, and upload record + it 'only performs three SQL queries' do + get user.avatar_url # Skip queries on first application load + + expect(response).to have_gitlab_http_status(200) + expect { get user.avatar_url }.not_to exceed_query_limit(3) + end + end + + context 'when logged out' do + # One each for avatar user and upload record + it 'only performs two SQL queries' do + get user.avatar_url # Skip queries on first application load + + expect(response).to have_gitlab_http_status(200) + expect { get user.avatar_url }.not_to exceed_query_limit(2) + end + end +end |