09163e423a
Count issues related merge requests based on user access level. And issue can have related MRs from projects where user does not have access so the number of related merge requests should be adjusted based on user's ability to access the related MRs. https://gitlab.com/gitlab-org/gitlab-ce/issues/59581
803 lines
28 KiB
Ruby
803 lines
28 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'spec_helper'
|
|
|
|
describe API::Issues do
|
|
set(:user) { create(:user) }
|
|
set(:project) { create(:project, :public, :repository, creator_id: user.id, namespace: user.namespace) }
|
|
set(:private_mrs_project) do
|
|
create(:project, :public, :repository, creator_id: user.id, namespace: user.namespace, merge_requests_access_level: ProjectFeature::PRIVATE)
|
|
end
|
|
|
|
let(:user2) { create(:user) }
|
|
let(:non_member) { create(:user) }
|
|
set(:guest) { create(:user) }
|
|
set(:author) { create(:author) }
|
|
set(:assignee) { create(:assignee) }
|
|
let(:admin) { create(:user, :admin) }
|
|
let(:issue_title) { 'foo' }
|
|
let(:issue_description) { 'closed' }
|
|
let!(:closed_issue) do
|
|
create :closed_issue,
|
|
author: user,
|
|
assignees: [user],
|
|
project: project,
|
|
state: :closed,
|
|
milestone: milestone,
|
|
created_at: generate(:past_time),
|
|
updated_at: 3.hours.ago,
|
|
closed_at: 1.hour.ago
|
|
end
|
|
let!(:confidential_issue) do
|
|
create :issue,
|
|
:confidential,
|
|
project: project,
|
|
author: author,
|
|
assignees: [assignee],
|
|
created_at: generate(:past_time),
|
|
updated_at: 2.hours.ago
|
|
end
|
|
let!(:issue) do
|
|
create :issue,
|
|
author: user,
|
|
assignees: [user],
|
|
project: project,
|
|
milestone: milestone,
|
|
created_at: generate(:past_time),
|
|
updated_at: 1.hour.ago,
|
|
title: issue_title,
|
|
description: issue_description
|
|
end
|
|
set(:label) do
|
|
create(:label, title: 'label', color: '#FFAABB', project: project)
|
|
end
|
|
let!(:label_link) { create(:label_link, label: label, target: issue) }
|
|
let(:milestone) { create(:milestone, title: '1.0.0', project: project) }
|
|
set(:empty_milestone) do
|
|
create(:milestone, title: '2.0.0', project: project)
|
|
end
|
|
let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) }
|
|
|
|
let(:no_milestone_title) { 'None' }
|
|
let(:any_milestone_title) { 'Any' }
|
|
|
|
before(:all) do
|
|
project.add_reporter(user)
|
|
project.add_guest(guest)
|
|
private_mrs_project.add_reporter(user)
|
|
private_mrs_project.add_guest(guest)
|
|
end
|
|
|
|
before do
|
|
stub_licensed_features(multiple_issue_assignees: false, issue_weights: false)
|
|
end
|
|
|
|
shared_examples 'issues statistics' do
|
|
it 'returns issues statistics' do
|
|
get api("/issues_statistics", user), params: params
|
|
|
|
expect(response).to have_gitlab_http_status(200)
|
|
expect(json_response['statistics']).not_to be_nil
|
|
expect(json_response['statistics']['counts']['all']).to eq counts[:all]
|
|
expect(json_response['statistics']['counts']['closed']).to eq counts[:closed]
|
|
expect(json_response['statistics']['counts']['opened']).to eq counts[:opened]
|
|
end
|
|
end
|
|
|
|
describe 'GET /issues' do
|
|
context 'when unauthenticated' do
|
|
it 'returns an array of all issues' do
|
|
get api('/issues'), params: { scope: 'all' }
|
|
|
|
expect(response).to have_http_status(200)
|
|
expect(json_response).to be_an Array
|
|
end
|
|
|
|
it 'returns authentication error without any scope' do
|
|
get api('/issues')
|
|
|
|
expect(response).to have_http_status(401)
|
|
end
|
|
|
|
it 'returns authentication error when scope is assigned-to-me' do
|
|
get api('/issues'), params: { scope: 'assigned-to-me' }
|
|
|
|
expect(response).to have_http_status(401)
|
|
end
|
|
|
|
it 'returns authentication error when scope is created-by-me' do
|
|
get api('/issues'), params: { scope: 'created-by-me' }
|
|
|
|
expect(response).to have_http_status(401)
|
|
end
|
|
|
|
it 'returns an array of issues matching state in milestone' do
|
|
get api('/issues'), params: { milestone: 'foo', scope: 'all' }
|
|
|
|
expect(response).to have_http_status(200)
|
|
expect_paginated_array_response([])
|
|
end
|
|
|
|
it 'returns an array of issues matching state in milestone' do
|
|
get api('/issues'), params: { milestone: milestone.title, scope: 'all' }
|
|
|
|
expect(response).to have_http_status(200)
|
|
expect_paginated_array_response([issue.id, closed_issue.id])
|
|
end
|
|
|
|
context 'issues_statistics' do
|
|
it 'returns authentication error without any scope' do
|
|
get api('/issues_statistics')
|
|
|
|
expect(response).to have_http_status(401)
|
|
end
|
|
|
|
it 'returns authentication error when scope is assigned_to_me' do
|
|
get api('/issues_statistics'), params: { scope: 'assigned_to_me' }
|
|
|
|
expect(response).to have_http_status(401)
|
|
end
|
|
|
|
it 'returns authentication error when scope is created_by_me' do
|
|
get api('/issues_statistics'), params: { scope: 'created_by_me' }
|
|
|
|
expect(response).to have_http_status(401)
|
|
end
|
|
|
|
context 'no state is treated as all state' do
|
|
let(:params) { {} }
|
|
let(:counts) { { all: 2, closed: 1, opened: 1 } }
|
|
|
|
it_behaves_like 'issues statistics'
|
|
end
|
|
|
|
context 'statistics when all state is passed' do
|
|
let(:params) { { state: :all } }
|
|
let(:counts) { { all: 2, closed: 1, opened: 1 } }
|
|
|
|
it_behaves_like 'issues statistics'
|
|
end
|
|
|
|
context 'closed state is treated as all state' do
|
|
let(:params) { { state: :closed } }
|
|
let(:counts) { { all: 2, closed: 1, opened: 1 } }
|
|
|
|
it_behaves_like 'issues statistics'
|
|
end
|
|
|
|
context 'opened state is treated as all state' do
|
|
let(:params) { { state: :opened } }
|
|
let(:counts) { { all: 2, closed: 1, opened: 1 } }
|
|
|
|
it_behaves_like 'issues statistics'
|
|
end
|
|
|
|
context 'when filtering by milestone and no state treated as all state' do
|
|
let(:params) { { milestone: milestone.title } }
|
|
let(:counts) { { all: 2, closed: 1, opened: 1 } }
|
|
|
|
it_behaves_like 'issues statistics'
|
|
end
|
|
|
|
context 'when filtering by milestone and all state' do
|
|
let(:params) { { milestone: milestone.title, state: :all } }
|
|
let(:counts) { { all: 2, closed: 1, opened: 1 } }
|
|
|
|
it_behaves_like 'issues statistics'
|
|
end
|
|
|
|
context 'when filtering by milestone and closed state treated as all state' do
|
|
let(:params) { { milestone: milestone.title, state: :closed } }
|
|
let(:counts) { { all: 2, closed: 1, opened: 1 } }
|
|
|
|
it_behaves_like 'issues statistics'
|
|
end
|
|
|
|
context 'when filtering by milestone and opened state treated as all state' do
|
|
let(:params) { { milestone: milestone.title, state: :opened } }
|
|
let(:counts) { { all: 2, closed: 1, opened: 1 } }
|
|
|
|
it_behaves_like 'issues statistics'
|
|
end
|
|
|
|
context 'sort does not affect statistics ' do
|
|
let(:params) { { state: :opened, order_by: 'updated_at' } }
|
|
let(:counts) { { all: 2, closed: 1, opened: 1 } }
|
|
|
|
it_behaves_like 'issues statistics'
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when authenticated' do
|
|
it 'returns an array of issues' do
|
|
get api('/issues', user)
|
|
|
|
expect_paginated_array_response([issue.id, closed_issue.id])
|
|
expect(json_response.first['title']).to eq(issue.title)
|
|
expect(json_response.last).to have_key('web_url')
|
|
end
|
|
|
|
it 'returns an array of closed issues' do
|
|
get api('/issues', user), params: { state: :closed }
|
|
|
|
expect_paginated_array_response(closed_issue.id)
|
|
end
|
|
|
|
it 'returns an array of opened issues' do
|
|
get api('/issues', user), params: { state: :opened }
|
|
|
|
expect_paginated_array_response(issue.id)
|
|
end
|
|
|
|
it 'returns an array of all issues' do
|
|
get api('/issues', user), params: { state: :all }
|
|
|
|
expect_paginated_array_response([issue.id, closed_issue.id])
|
|
end
|
|
|
|
it 'returns issues assigned to me' do
|
|
issue2 = create(:issue, assignees: [user2], project: project)
|
|
|
|
get api('/issues', user2), params: { scope: 'assigned_to_me' }
|
|
|
|
expect_paginated_array_response(issue2.id)
|
|
end
|
|
|
|
it 'returns issues assigned to me (kebab-case)' do
|
|
issue2 = create(:issue, assignees: [user2], project: project)
|
|
|
|
get api('/issues', user2), params: { scope: 'assigned-to-me' }
|
|
|
|
expect_paginated_array_response(issue2.id)
|
|
end
|
|
|
|
it 'returns issues authored by the given author id' do
|
|
issue2 = create(:issue, author: user2, project: project)
|
|
|
|
get api('/issues', user), params: { author_id: user2.id, scope: 'all' }
|
|
|
|
expect_paginated_array_response(issue2.id)
|
|
end
|
|
|
|
it 'returns issues assigned to the given assignee id' do
|
|
issue2 = create(:issue, assignees: [user2], project: project)
|
|
|
|
get api('/issues', user), params: { assignee_id: user2.id, scope: 'all' }
|
|
|
|
expect_paginated_array_response(issue2.id)
|
|
end
|
|
|
|
it 'returns issues authored by the given author id and assigned to the given assignee id' do
|
|
issue2 = create(:issue, author: user2, assignees: [user2], project: project)
|
|
|
|
get api('/issues', user), params: { author_id: user2.id, assignee_id: user2.id, scope: 'all' }
|
|
|
|
expect_paginated_array_response(issue2.id)
|
|
end
|
|
|
|
it 'returns issues with no assignee' do
|
|
issue2 = create(:issue, author: user2, project: project)
|
|
|
|
get api('/issues', user), params: { assignee_id: 'None', scope: 'all' }
|
|
|
|
expect_paginated_array_response(issue2.id)
|
|
end
|
|
|
|
it 'returns issues with any assignee' do
|
|
# This issue without assignee should not be returned
|
|
create(:issue, author: user2, project: project)
|
|
|
|
get api('/issues', user), params: { assignee_id: 'Any', scope: 'all' }
|
|
|
|
expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
|
|
end
|
|
|
|
it 'returns only confidential issues' do
|
|
get api('/issues', user), params: { confidential: true, scope: 'all' }
|
|
|
|
expect_paginated_array_response(confidential_issue.id)
|
|
end
|
|
|
|
it 'returns only public issues' do
|
|
get api('/issues', user), params: { confidential: false }
|
|
|
|
expect_paginated_array_response([issue.id, closed_issue.id])
|
|
end
|
|
|
|
it 'returns issues reacted by the authenticated user' do
|
|
issue2 = create(:issue, project: project, author: user, assignees: [user])
|
|
create(:award_emoji, awardable: issue2, user: user2, name: 'star')
|
|
create(:award_emoji, awardable: issue, user: user2, name: 'thumbsup')
|
|
|
|
get api('/issues', user2), params: { my_reaction_emoji: 'Any', scope: 'all' }
|
|
|
|
expect_paginated_array_response([issue2.id, issue.id])
|
|
end
|
|
|
|
it 'returns issues not reacted by the authenticated user' do
|
|
issue2 = create(:issue, project: project, author: user, assignees: [user])
|
|
create(:award_emoji, awardable: issue2, user: user2, name: 'star')
|
|
|
|
get api('/issues', user2), params: { my_reaction_emoji: 'None', scope: 'all' }
|
|
|
|
expect_paginated_array_response([issue.id, closed_issue.id])
|
|
end
|
|
|
|
it 'returns issues matching given search string for title' do
|
|
get api('/issues', user), params: { search: issue.title }
|
|
|
|
expect_paginated_array_response(issue.id)
|
|
end
|
|
|
|
it 'returns issues matching given search string for title and scoped in title' do
|
|
get api('/issues', user), params: { search: issue.title, in: 'title' }
|
|
|
|
expect_paginated_array_response(issue.id)
|
|
end
|
|
|
|
it 'returns an empty array if no issue matches given search string for title and scoped in description' do
|
|
get api('/issues', user), params: { search: issue.title, in: 'description' }
|
|
|
|
expect_paginated_array_response([])
|
|
end
|
|
|
|
it 'returns issues matching given search string for description' do
|
|
get api('/issues', user), params: { search: issue.description }
|
|
|
|
expect_paginated_array_response(issue.id)
|
|
end
|
|
|
|
context 'filtering before a specific date' do
|
|
let!(:issue2) { create(:issue, project: project, author: user, created_at: Date.new(2000, 1, 1), updated_at: Date.new(2000, 1, 1)) }
|
|
|
|
it 'returns issues created before a specific date' do
|
|
get api('/issues?created_before=2000-01-02T00:00:00.060Z', user)
|
|
|
|
expect_paginated_array_response(issue2.id)
|
|
end
|
|
|
|
it 'returns issues updated before a specific date' do
|
|
get api('/issues?updated_before=2000-01-02T00:00:00.060Z', user)
|
|
|
|
expect_paginated_array_response(issue2.id)
|
|
end
|
|
end
|
|
|
|
context 'filtering after a specific date' do
|
|
let!(:issue2) { create(:issue, project: project, author: user, created_at: 1.week.from_now, updated_at: 1.week.from_now) }
|
|
|
|
it 'returns issues created after a specific date' do
|
|
get api("/issues?created_after=#{issue2.created_at}", user)
|
|
|
|
expect_paginated_array_response(issue2.id)
|
|
end
|
|
|
|
it 'returns issues updated after a specific date' do
|
|
get api("/issues?updated_after=#{issue2.updated_at}", user)
|
|
|
|
expect_paginated_array_response(issue2.id)
|
|
end
|
|
end
|
|
|
|
context 'filter by labels or label_name param' do
|
|
context 'N+1' do
|
|
let(:label_b) { create(:label, title: 'foo', project: project) }
|
|
let(:label_c) { create(:label, title: 'bar', project: project) }
|
|
|
|
before do
|
|
create(:label_link, label: label_b, target: issue)
|
|
create(:label_link, label: label_c, target: issue)
|
|
end
|
|
it 'tests N+1' do
|
|
control = ActiveRecord::QueryRecorder.new do
|
|
get api('/issues', user), params: { labels: [label.title, label_b.title, label_c.title] }
|
|
end
|
|
|
|
label_d = create(:label, title: 'dar', project: project)
|
|
label_e = create(:label, title: 'ear', project: project)
|
|
create(:label_link, label: label_d, target: issue)
|
|
create(:label_link, label: label_e, target: issue)
|
|
|
|
expect do
|
|
get api('/issues', user), params: { labels: [label.title, label_b.title, label_c.title] }
|
|
end.not_to exceed_query_limit(control)
|
|
expect(issue.labels.count).to eq(5)
|
|
end
|
|
end
|
|
|
|
it 'returns an array of labeled issues' do
|
|
get api('/issues', user), params: { labels: label.title }
|
|
|
|
expect_paginated_array_response(issue.id)
|
|
expect(json_response.first['labels']).to eq([label.title])
|
|
end
|
|
|
|
it 'returns an array of labeled issues with labels param as array' do
|
|
get api('/issues', user), params: { labels: [label.title] }
|
|
|
|
expect_paginated_array_response(issue.id)
|
|
expect(json_response.first['labels']).to eq([label.title])
|
|
end
|
|
|
|
context 'with labeled issues' do
|
|
let(:label_b) { create(:label, title: 'foo', project: project) }
|
|
let(:label_c) { create(:label, title: 'bar', project: project) }
|
|
|
|
before do
|
|
create(:label_link, label: label_b, target: issue)
|
|
create(:label_link, label: label_c, target: issue)
|
|
|
|
get api('/issues', user), params: params
|
|
end
|
|
|
|
it_behaves_like 'labeled issues with labels and label_name params'
|
|
end
|
|
|
|
it 'returns an empty array if no issue matches labels' do
|
|
get api('/issues', user), params: { labels: 'foo,bar' }
|
|
|
|
expect_paginated_array_response([])
|
|
end
|
|
|
|
it 'returns an empty array if no issue matches labels with labels param as array' do
|
|
get api('/issues', user), params: { labels: %w(foo bar) }
|
|
|
|
expect_paginated_array_response([])
|
|
end
|
|
|
|
it 'returns an array of labeled issues matching given state' do
|
|
get api('/issues', user), params: { labels: label.title, state: :opened }
|
|
|
|
expect_paginated_array_response(issue.id)
|
|
expect(json_response.first['labels']).to eq([label.title])
|
|
expect(json_response.first['state']).to eq('opened')
|
|
end
|
|
|
|
it 'returns an array of labeled issues matching given state with labels param as array' do
|
|
get api('/issues', user), params: { labels: [label.title], state: :opened }
|
|
|
|
expect_paginated_array_response(issue.id)
|
|
expect(json_response.first['labels']).to eq([label.title])
|
|
expect(json_response.first['state']).to eq('opened')
|
|
end
|
|
|
|
it 'returns an empty array if no issue matches labels and state filters' do
|
|
get api('/issues', user), params: { labels: label.title, state: :closed }
|
|
|
|
expect_paginated_array_response([])
|
|
end
|
|
|
|
it 'returns an array of issues with any label' do
|
|
get api('/issues', user), params: { labels: IssuesFinder::FILTER_ANY }
|
|
|
|
expect_paginated_array_response(issue.id)
|
|
end
|
|
|
|
it 'returns an array of issues with any label with labels param as array' do
|
|
get api('/issues', user), params: { labels: [IssuesFinder::FILTER_ANY] }
|
|
|
|
expect_paginated_array_response(issue.id)
|
|
end
|
|
|
|
it 'returns an array of issues with no label' do
|
|
get api('/issues', user), params: { labels: IssuesFinder::FILTER_NONE }
|
|
|
|
expect_paginated_array_response(closed_issue.id)
|
|
end
|
|
|
|
it 'returns an array of issues with no label with labels param as array' do
|
|
get api('/issues', user), params: { labels: [IssuesFinder::FILTER_NONE] }
|
|
|
|
expect_paginated_array_response(closed_issue.id)
|
|
end
|
|
end
|
|
|
|
it 'returns an empty array if no issue matches milestone' do
|
|
get api("/issues?milestone=#{empty_milestone.title}", user)
|
|
|
|
expect_paginated_array_response([])
|
|
end
|
|
|
|
it 'returns an empty array if milestone does not exist' do
|
|
get api('/issues?milestone=foo', user)
|
|
|
|
expect_paginated_array_response([])
|
|
end
|
|
|
|
it 'returns an array of issues in given milestone' do
|
|
get api("/issues?milestone=#{milestone.title}", user)
|
|
|
|
expect_paginated_array_response([issue.id, closed_issue.id])
|
|
end
|
|
|
|
it 'returns an array of issues in given milestone_title param' do
|
|
get api("/issues?milestone_title=#{milestone.title}", user)
|
|
|
|
expect_paginated_array_response([issue.id, closed_issue.id])
|
|
end
|
|
|
|
it 'returns an array of issues matching state in milestone' do
|
|
get api("/issues?milestone=#{milestone.title}&state=closed", user)
|
|
|
|
expect_paginated_array_response(closed_issue.id)
|
|
end
|
|
|
|
it 'returns an array of issues with no milestone' do
|
|
get api("/issues?milestone=#{no_milestone_title}", author)
|
|
|
|
expect_paginated_array_response(confidential_issue.id)
|
|
end
|
|
|
|
it 'returns an array of issues with no milestone using milestone_title param' do
|
|
get api("/issues?milestone_title=#{no_milestone_title}", author)
|
|
|
|
expect_paginated_array_response(confidential_issue.id)
|
|
end
|
|
|
|
it 'returns an array of issues found by iids' do
|
|
get api('/issues', user), params: { iids: [closed_issue.iid] }
|
|
|
|
expect_paginated_array_response(closed_issue.id)
|
|
end
|
|
|
|
it 'returns an empty array if iid does not exist' do
|
|
get api('/issues', user), params: { iids: [0] }
|
|
|
|
expect_paginated_array_response([])
|
|
end
|
|
|
|
context 'without sort params' do
|
|
it 'sorts by created_at descending by default' do
|
|
get api('/issues', user)
|
|
|
|
expect_paginated_array_response([issue.id, closed_issue.id])
|
|
end
|
|
|
|
context 'with 2 issues with same created_at' do
|
|
let!(:closed_issue2) do
|
|
create :closed_issue,
|
|
author: user,
|
|
assignees: [user],
|
|
project: project,
|
|
milestone: milestone,
|
|
created_at: closed_issue.created_at,
|
|
updated_at: 1.hour.ago,
|
|
title: issue_title,
|
|
description: issue_description
|
|
end
|
|
|
|
it 'page breaks first page correctly' do
|
|
get api('/issues?per_page=2', user)
|
|
|
|
expect_paginated_array_response([issue.id, closed_issue2.id])
|
|
end
|
|
|
|
it 'page breaks second page correctly' do
|
|
get api('/issues?per_page=2&page=2', user)
|
|
|
|
expect_paginated_array_response([closed_issue.id])
|
|
end
|
|
end
|
|
end
|
|
|
|
it 'sorts ascending when requested' do
|
|
get api('/issues?sort=asc', user)
|
|
|
|
expect_paginated_array_response([closed_issue.id, issue.id])
|
|
end
|
|
|
|
it 'sorts by updated_at descending when requested' do
|
|
get api('/issues?order_by=updated_at', user)
|
|
|
|
issue.touch(:updated_at)
|
|
|
|
expect_paginated_array_response([issue.id, closed_issue.id])
|
|
end
|
|
|
|
it 'sorts by updated_at ascending when requested' do
|
|
get api('/issues?order_by=updated_at&sort=asc', user)
|
|
|
|
issue.touch(:updated_at)
|
|
|
|
expect_paginated_array_response([closed_issue.id, issue.id])
|
|
end
|
|
|
|
it 'matches V4 response schema' do
|
|
get api('/issues', user)
|
|
|
|
expect(response).to have_gitlab_http_status(200)
|
|
expect(response).to match_response_schema('public_api/v4/issues')
|
|
end
|
|
|
|
it 'returns a related merge request count of 0 if there are no related merge requests' do
|
|
get api('/issues', user)
|
|
|
|
expect(response).to have_gitlab_http_status(200)
|
|
expect(response).to match_response_schema('public_api/v4/issues')
|
|
expect(json_response.first).to include('merge_requests_count' => 0)
|
|
end
|
|
|
|
it 'returns a related merge request count > 0 if there are related merge requests' do
|
|
create(:merge_requests_closing_issues, issue: issue)
|
|
|
|
get api('/issues', user)
|
|
|
|
expect(response).to have_gitlab_http_status(200)
|
|
expect(response).to match_response_schema('public_api/v4/issues')
|
|
expect(json_response.first).to include('merge_requests_count' => 1)
|
|
end
|
|
|
|
context 'issues_statistics' do
|
|
context 'no state is treated as all state' do
|
|
let(:params) { {} }
|
|
let(:counts) { { all: 2, closed: 1, opened: 1 } }
|
|
|
|
it_behaves_like 'issues statistics'
|
|
end
|
|
|
|
context 'statistics when all state is passed' do
|
|
let(:params) { { state: :all } }
|
|
let(:counts) { { all: 2, closed: 1, opened: 1 } }
|
|
|
|
it_behaves_like 'issues statistics'
|
|
end
|
|
|
|
context 'closed state is treated as all state' do
|
|
let(:params) { { state: :closed } }
|
|
let(:counts) { { all: 2, closed: 1, opened: 1 } }
|
|
|
|
it_behaves_like 'issues statistics'
|
|
end
|
|
|
|
context 'opened state is treated as all state' do
|
|
let(:params) { { state: :opened } }
|
|
let(:counts) { { all: 2, closed: 1, opened: 1 } }
|
|
|
|
it_behaves_like 'issues statistics'
|
|
end
|
|
|
|
context 'when filtering by milestone and no state treated as all state' do
|
|
let(:params) { { milestone: milestone.title } }
|
|
let(:counts) { { all: 2, closed: 1, opened: 1 } }
|
|
|
|
it_behaves_like 'issues statistics'
|
|
end
|
|
|
|
context 'when filtering by milestone and all state' do
|
|
let(:params) { { milestone: milestone.title, state: :all } }
|
|
let(:counts) { { all: 2, closed: 1, opened: 1 } }
|
|
|
|
it_behaves_like 'issues statistics'
|
|
end
|
|
|
|
context 'when filtering by milestone and closed state treated as all state' do
|
|
let(:params) { { milestone: milestone.title, state: :closed } }
|
|
let(:counts) { { all: 2, closed: 1, opened: 1 } }
|
|
|
|
it_behaves_like 'issues statistics'
|
|
end
|
|
|
|
context 'when filtering by milestone and opened state treated as all state' do
|
|
let(:params) { { milestone: milestone.title, state: :opened } }
|
|
let(:counts) { { all: 2, closed: 1, opened: 1 } }
|
|
|
|
it_behaves_like 'issues statistics'
|
|
end
|
|
|
|
context 'sort does not affect statistics ' do
|
|
let(:params) { { state: :opened, order_by: 'updated_at' } }
|
|
let(:counts) { { all: 2, closed: 1, opened: 1 } }
|
|
|
|
it_behaves_like 'issues statistics'
|
|
end
|
|
end
|
|
|
|
context 'filtering by assignee_username' do
|
|
let(:another_assignee) { create(:assignee) }
|
|
let!(:issue1) { create(:issue, author: user2, project: project, created_at: 3.days.ago) }
|
|
let!(:issue2) { create(:issue, author: user2, project: project, created_at: 2.days.ago) }
|
|
let!(:issue3) { create(:issue, author: user2, assignees: [assignee, another_assignee], project: project, created_at: 1.day.ago) }
|
|
|
|
it 'returns issues with by assignee_username' do
|
|
get api("/issues", user), params: { assignee_username: [assignee.username], scope: 'all' }
|
|
|
|
expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id])
|
|
expect_paginated_array_response([confidential_issue.id, issue3.id])
|
|
end
|
|
|
|
it 'returns issues by assignee_username as string' do
|
|
get api("/issues", user), params: { assignee_username: assignee.username, scope: 'all' }
|
|
|
|
expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id])
|
|
expect_paginated_array_response([confidential_issue.id, issue3.id])
|
|
end
|
|
|
|
it 'returns error when multiple assignees are passed' do
|
|
get api("/issues", user), params: { assignee_username: [assignee.username, another_assignee.username], scope: 'all' }
|
|
|
|
expect(response).to have_gitlab_http_status(400)
|
|
expect(json_response["error"]).to include("allows one value, but found 2")
|
|
end
|
|
|
|
it 'returns error when assignee_username and assignee_id are passed together' do
|
|
get api("/issues", user), params: { assignee_username: [assignee.username], assignee_id: another_assignee.id, scope: 'all' }
|
|
|
|
expect(response).to have_gitlab_http_status(400)
|
|
expect(json_response["error"]).to include("mutually exclusive")
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when returns issue merge_requests_count for different access levels" do
|
|
let!(:merge_request1) do
|
|
create(:merge_request,
|
|
:simple,
|
|
author: user,
|
|
source_project: private_mrs_project,
|
|
target_project: private_mrs_project,
|
|
description: "closes #{issue.to_reference(private_mrs_project)}")
|
|
end
|
|
let!(:merge_request2) do
|
|
create(:merge_request,
|
|
:simple,
|
|
author: user,
|
|
source_project: project,
|
|
target_project: project,
|
|
description: "closes #{issue.to_reference}")
|
|
end
|
|
|
|
it_behaves_like 'accessible merge requests count' do
|
|
let(:api_url) { "/issues" }
|
|
let(:target_issue) { issue }
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'DELETE /projects/:id/issues/:issue_iid' do
|
|
it 'rejects a non member from deleting an issue' do
|
|
delete api("/projects/#{project.id}/issues/#{issue.iid}", non_member)
|
|
expect(response).to have_gitlab_http_status(403)
|
|
end
|
|
|
|
it 'rejects a developer from deleting an issue' do
|
|
delete api("/projects/#{project.id}/issues/#{issue.iid}", author)
|
|
expect(response).to have_gitlab_http_status(403)
|
|
end
|
|
|
|
context 'when the user is project owner' do
|
|
let(:owner) { create(:user) }
|
|
let(:project) { create(:project, namespace: owner.namespace) }
|
|
|
|
it 'deletes the issue if an admin requests it' do
|
|
delete api("/projects/#{project.id}/issues/#{issue.iid}", owner)
|
|
|
|
expect(response).to have_gitlab_http_status(204)
|
|
end
|
|
|
|
it_behaves_like '412 response' do
|
|
let(:request) { api("/projects/#{project.id}/issues/#{issue.iid}", owner) }
|
|
end
|
|
end
|
|
|
|
context 'when issue does not exist' do
|
|
it 'returns 404 when trying to move an issue' do
|
|
delete api("/projects/#{project.id}/issues/123", user)
|
|
|
|
expect(response).to have_gitlab_http_status(404)
|
|
end
|
|
end
|
|
|
|
it 'returns 404 when using the issue ID instead of IID' do
|
|
delete api("/projects/#{project.id}/issues/#{issue.id}", user)
|
|
|
|
expect(response).to have_gitlab_http_status(404)
|
|
end
|
|
end
|
|
|
|
describe 'time tracking endpoints' do
|
|
let(:issuable) { issue }
|
|
|
|
include_examples 'time tracking endpoints', 'issue'
|
|
end
|
|
end
|