2019-10-28 20:06:10 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2016-03-11 14:04:42 -05:00
|
|
|
require 'spec_helper'
|
|
|
|
|
2020-06-24 02:09:01 -04:00
|
|
|
RSpec.describe API::Todos do
|
2020-03-30 14:08:07 -04:00
|
|
|
let_it_be(:group) { create(:group) }
|
|
|
|
let_it_be(:project_1) { create(:project, :repository, group: group) }
|
|
|
|
let_it_be(:project_2) { create(:project) }
|
|
|
|
let_it_be(:author_1) { create(:user) }
|
|
|
|
let_it_be(:author_2) { create(:user) }
|
|
|
|
let_it_be(:john_doe) { create(:user, username: 'john_doe') }
|
|
|
|
let_it_be(:merge_request) { create(:merge_request, source_project: project_1) }
|
|
|
|
let_it_be(:merge_request_todo) { create(:todo, project: project_1, author: author_2, user: john_doe, target: merge_request) }
|
|
|
|
let_it_be(:pending_1) { create(:todo, :mentioned, project: project_1, author: author_1, user: john_doe) }
|
|
|
|
let_it_be(:pending_2) { create(:todo, project: project_2, author: author_2, user: john_doe) }
|
|
|
|
let_it_be(:pending_3) { create(:on_commit_todo, project: project_1, author: author_2, user: john_doe) }
|
|
|
|
let_it_be(:done) { create(:todo, :done, project: project_1, author: author_1, user: john_doe) }
|
|
|
|
let_it_be(:award_emoji_1) { create(:award_emoji, awardable: merge_request, user: author_1, name: 'thumbsup') }
|
|
|
|
let_it_be(:award_emoji_2) { create(:award_emoji, awardable: pending_1.target, user: author_1, name: 'thumbsup') }
|
|
|
|
let_it_be(:award_emoji_3) { create(:award_emoji, awardable: pending_2.target, user: author_2, name: 'thumbsdown') }
|
|
|
|
|
|
|
|
before_all do
|
2017-12-22 03:18:28 -05:00
|
|
|
project_1.add_developer(john_doe)
|
|
|
|
project_2.add_developer(john_doe)
|
2016-06-10 06:24:38 -04:00
|
|
|
end
|
|
|
|
|
2016-03-11 14:04:42 -05:00
|
|
|
describe 'GET /todos' do
|
|
|
|
context 'when unauthenticated' do
|
2016-05-20 17:17:13 -04:00
|
|
|
it 'returns authentication error' do
|
2016-03-11 14:04:42 -05:00
|
|
|
get api('/todos')
|
2016-05-20 17:17:13 -04:00
|
|
|
|
2020-03-30 14:08:07 -04:00
|
|
|
expect(response).to have_gitlab_http_status(:unauthorized)
|
2016-03-11 14:04:42 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when authenticated' do
|
2020-09-08 14:08:48 -04:00
|
|
|
context 'when invalid params' do
|
|
|
|
context "invalid action" do
|
|
|
|
it 'returns 400' do
|
|
|
|
get api('/todos', john_doe), params: { action: 'InvalidAction' }
|
|
|
|
expect(response).to have_gitlab_http_status(:bad_request)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context "invalid state" do
|
|
|
|
it 'returns 400' do
|
|
|
|
get api('/todos', john_doe), params: { state: 'InvalidState' }
|
|
|
|
expect(response).to have_gitlab_http_status(:bad_request)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context "invalid type" do
|
|
|
|
it 'returns 400' do
|
|
|
|
get api('/todos', john_doe), params: { type: 'InvalidType' }
|
|
|
|
expect(response).to have_gitlab_http_status(:bad_request)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-05-20 17:17:13 -04:00
|
|
|
it 'returns an array of pending todos for current user' do
|
2016-03-11 14:04:42 -05:00
|
|
|
get api('/todos', john_doe)
|
2016-05-20 17:17:13 -04:00
|
|
|
|
2020-03-30 14:08:07 -04:00
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
2017-01-24 15:49:10 -05:00
|
|
|
expect(response).to include_pagination_headers
|
2016-03-11 14:04:42 -05:00
|
|
|
expect(json_response).to be_an Array
|
2019-03-02 12:31:36 -05:00
|
|
|
expect(json_response.length).to eq(4)
|
2016-06-10 07:02:41 -04:00
|
|
|
expect(json_response[0]['id']).to eq(pending_3.id)
|
|
|
|
expect(json_response[0]['project']).to be_a Hash
|
|
|
|
expect(json_response[0]['author']).to be_a Hash
|
|
|
|
expect(json_response[0]['target_type']).to be_present
|
2016-06-22 13:15:09 -04:00
|
|
|
expect(json_response[0]['target']).to be_a Hash
|
2016-06-10 07:02:41 -04:00
|
|
|
expect(json_response[0]['target_url']).to be_present
|
|
|
|
expect(json_response[0]['body']).to be_present
|
|
|
|
expect(json_response[0]['state']).to eq('pending')
|
2016-06-15 07:20:30 -04:00
|
|
|
expect(json_response[0]['action_name']).to eq('assigned')
|
2016-06-10 07:02:41 -04:00
|
|
|
expect(json_response[0]['created_at']).to be_present
|
2019-03-02 12:31:36 -05:00
|
|
|
expect(json_response[0]['target_type']).to eq('Commit')
|
|
|
|
|
|
|
|
expect(json_response[1]['target_type']).to eq('Issue')
|
|
|
|
expect(json_response[1]['target']['upvotes']).to eq(0)
|
|
|
|
expect(json_response[1]['target']['downvotes']).to eq(1)
|
|
|
|
expect(json_response[1]['target']['merge_requests_count']).to eq(0)
|
|
|
|
|
|
|
|
expect(json_response[2]['target_type']).to eq('Issue')
|
|
|
|
expect(json_response[2]['target']['upvotes']).to eq(1)
|
|
|
|
expect(json_response[2]['target']['downvotes']).to eq(0)
|
|
|
|
expect(json_response[2]['target']['merge_requests_count']).to eq(0)
|
|
|
|
|
|
|
|
expect(json_response[3]['target_type']).to eq('MergeRequest')
|
|
|
|
# Only issues get a merge request count at the moment
|
|
|
|
expect(json_response[3]['target']['merge_requests_count']).to be_nil
|
|
|
|
expect(json_response[3]['target']['upvotes']).to eq(1)
|
|
|
|
expect(json_response[3]['target']['downvotes']).to eq(0)
|
2016-03-11 14:04:42 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
context 'and using the author filter' do
|
2016-05-20 17:17:13 -04:00
|
|
|
it 'filters based on author_id param' do
|
2018-12-17 17:52:17 -05:00
|
|
|
get api('/todos', john_doe), params: { author_id: author_2.id }
|
2016-05-20 17:17:13 -04:00
|
|
|
|
2020-03-30 14:08:07 -04:00
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
2017-01-24 15:49:10 -05:00
|
|
|
expect(response).to include_pagination_headers
|
2016-03-11 14:04:42 -05:00
|
|
|
expect(json_response).to be_an Array
|
2019-03-02 12:31:36 -05:00
|
|
|
expect(json_response.length).to eq(3)
|
2016-03-11 14:04:42 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'and using the type filter' do
|
2016-05-20 17:17:13 -04:00
|
|
|
it 'filters based on type param' do
|
2016-06-28 12:04:44 -04:00
|
|
|
create(:todo, project: project_1, author: author_2, user: john_doe, target: merge_request)
|
|
|
|
|
2018-12-17 17:52:17 -05:00
|
|
|
get api('/todos', john_doe), params: { type: 'MergeRequest' }
|
2016-05-20 17:17:13 -04:00
|
|
|
|
2020-03-30 14:08:07 -04:00
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
2017-01-24 15:49:10 -05:00
|
|
|
expect(response).to include_pagination_headers
|
2016-03-11 14:04:42 -05:00
|
|
|
expect(json_response).to be_an Array
|
2019-03-02 12:31:36 -05:00
|
|
|
expect(json_response.length).to eq(2)
|
2016-03-11 14:04:42 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'and using the state filter' do
|
2016-05-20 17:17:13 -04:00
|
|
|
it 'filters based on state param' do
|
2018-12-17 17:52:17 -05:00
|
|
|
get api('/todos', john_doe), params: { state: 'done' }
|
2016-05-20 17:17:13 -04:00
|
|
|
|
2020-03-30 14:08:07 -04:00
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
2017-01-24 15:49:10 -05:00
|
|
|
expect(response).to include_pagination_headers
|
2016-03-11 14:04:42 -05:00
|
|
|
expect(json_response).to be_an Array
|
|
|
|
expect(json_response.length).to eq(1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'and using the project filter' do
|
2016-05-20 17:17:13 -04:00
|
|
|
it 'filters based on project_id param' do
|
2018-12-17 17:52:17 -05:00
|
|
|
get api('/todos', john_doe), params: { project_id: project_2.id }
|
2016-05-20 17:17:13 -04:00
|
|
|
|
2020-03-30 14:08:07 -04:00
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
2017-01-24 15:49:10 -05:00
|
|
|
expect(response).to include_pagination_headers
|
2016-03-11 14:04:42 -05:00
|
|
|
expect(json_response).to be_an Array
|
|
|
|
expect(json_response.length).to eq(1)
|
|
|
|
end
|
|
|
|
end
|
2016-06-22 13:15:09 -04:00
|
|
|
|
2018-08-06 05:05:44 -04:00
|
|
|
context 'and using the group filter' do
|
|
|
|
it 'filters based on project_id param' do
|
2018-12-17 17:52:17 -05:00
|
|
|
get api('/todos', john_doe), params: { group_id: group.id, sort: :target_id }
|
2018-08-06 05:05:44 -04:00
|
|
|
|
2020-03-30 14:08:07 -04:00
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
2018-08-06 05:05:44 -04:00
|
|
|
expect(response).to include_pagination_headers
|
|
|
|
expect(json_response).to be_an Array
|
2019-03-02 12:31:36 -05:00
|
|
|
expect(json_response.length).to eq(3)
|
2018-08-06 05:05:44 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-06-22 13:15:09 -04:00
|
|
|
context 'and using the action filter' do
|
|
|
|
it 'filters based on action param' do
|
2018-12-17 17:52:17 -05:00
|
|
|
get api('/todos', john_doe), params: { action: 'mentioned' }
|
2016-06-22 13:15:09 -04:00
|
|
|
|
2020-03-30 14:08:07 -04:00
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
2017-01-24 15:49:10 -05:00
|
|
|
expect(response).to include_pagination_headers
|
2016-06-22 13:15:09 -04:00
|
|
|
expect(json_response).to be_an Array
|
|
|
|
expect(json_response.length).to eq(1)
|
|
|
|
end
|
|
|
|
end
|
2016-03-11 14:04:42 -05:00
|
|
|
end
|
2019-03-02 12:31:36 -05:00
|
|
|
|
|
|
|
it 'avoids N+1 queries', :request_store do
|
|
|
|
create(:todo, project: project_1, author: author_2, user: john_doe, target: merge_request)
|
|
|
|
|
|
|
|
get api('/todos', john_doe)
|
|
|
|
|
|
|
|
control = ActiveRecord::QueryRecorder.new { get api('/todos', john_doe) }
|
|
|
|
|
|
|
|
merge_request_2 = create(:merge_request, source_project: project_2)
|
|
|
|
create(:todo, project: project_2, author: author_2, user: john_doe, target: merge_request_2)
|
|
|
|
|
|
|
|
project_3 = create(:project, :repository)
|
|
|
|
project_3.add_developer(john_doe)
|
|
|
|
merge_request_3 = create(:merge_request, source_project: project_3)
|
|
|
|
create(:todo, project: project_3, author: author_2, user: john_doe, target: merge_request_3)
|
|
|
|
create(:todo, :mentioned, project: project_1, author: author_1, user: john_doe)
|
|
|
|
create(:on_commit_todo, project: project_3, author: author_1, user: john_doe)
|
|
|
|
|
|
|
|
expect { get api('/todos', john_doe) }.not_to exceed_query_limit(control)
|
2020-03-30 14:08:07 -04:00
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
2019-03-02 12:31:36 -05:00
|
|
|
end
|
2020-05-11 20:10:11 -04:00
|
|
|
|
|
|
|
context 'when there is a Design Todo' do
|
|
|
|
let!(:design_todo) { create_todo_for_mentioned_in_design }
|
|
|
|
|
|
|
|
def create_todo_for_mentioned_in_design
|
|
|
|
issue = create(:issue, project: project_1)
|
|
|
|
create(:todo, :mentioned,
|
|
|
|
user: john_doe,
|
|
|
|
project: project_1,
|
|
|
|
target: create(:design, issue: issue),
|
|
|
|
author: create(:user),
|
|
|
|
note: create(:note, project: project_1, note: "I am note, hear me roar"))
|
|
|
|
end
|
|
|
|
|
|
|
|
def api_request
|
|
|
|
get api('/todos', john_doe)
|
|
|
|
end
|
|
|
|
|
|
|
|
before do
|
|
|
|
api_request
|
|
|
|
end
|
|
|
|
|
|
|
|
specify do
|
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'avoids N+1 queries', :request_store do
|
|
|
|
control = ActiveRecord::QueryRecorder.new { api_request }
|
|
|
|
|
|
|
|
create_todo_for_mentioned_in_design
|
|
|
|
|
|
|
|
expect { api_request }.not_to exceed_query_limit(control)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'includes the Design Todo in the response' do
|
|
|
|
expect(json_response).to include(
|
|
|
|
a_hash_including('id' => design_todo.id)
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
2016-03-11 14:04:42 -05:00
|
|
|
end
|
|
|
|
|
2017-02-21 04:00:33 -05:00
|
|
|
describe 'POST /todos/:id/mark_as_done' do
|
2016-03-11 14:04:42 -05:00
|
|
|
context 'when unauthenticated' do
|
2016-05-20 17:17:13 -04:00
|
|
|
it 'returns authentication error' do
|
2017-02-21 04:00:33 -05:00
|
|
|
post api("/todos/#{pending_1.id}/mark_as_done")
|
2016-05-20 17:17:13 -04:00
|
|
|
|
2020-03-03 16:08:37 -05:00
|
|
|
expect(response).to have_gitlab_http_status(:unauthorized)
|
2016-03-11 14:04:42 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when authenticated' do
|
2016-05-20 17:17:13 -04:00
|
|
|
it 'marks a todo as done' do
|
2017-02-21 04:00:33 -05:00
|
|
|
post api("/todos/#{pending_1.id}/mark_as_done", john_doe)
|
2016-05-20 17:17:13 -04:00
|
|
|
|
2020-03-03 16:08:37 -05:00
|
|
|
expect(response).to have_gitlab_http_status(:created)
|
2017-02-21 04:00:33 -05:00
|
|
|
expect(json_response['id']).to eq(pending_1.id)
|
|
|
|
expect(json_response['state']).to eq('done')
|
2016-03-11 14:04:42 -05:00
|
|
|
expect(pending_1.reload).to be_done
|
|
|
|
end
|
2016-07-08 12:42:47 -04:00
|
|
|
|
|
|
|
it 'updates todos cache' do
|
|
|
|
expect_any_instance_of(User).to receive(:update_todos_count_cache).and_call_original
|
|
|
|
|
2017-02-21 04:00:33 -05:00
|
|
|
post api("/todos/#{pending_1.id}/mark_as_done", john_doe)
|
2016-07-08 12:42:47 -04:00
|
|
|
end
|
2018-01-18 18:10:19 -05:00
|
|
|
|
|
|
|
it 'returns 404 if the todo does not belong to the current user' do
|
|
|
|
post api("/todos/#{pending_1.id}/mark_as_done", author_1)
|
|
|
|
|
2020-03-30 14:08:07 -04:00
|
|
|
expect(response).to have_gitlab_http_status(:not_found)
|
2018-01-18 18:10:19 -05:00
|
|
|
end
|
2016-03-11 14:04:42 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-02-21 04:00:33 -05:00
|
|
|
describe 'POST /mark_as_done' do
|
2016-03-11 14:04:42 -05:00
|
|
|
context 'when unauthenticated' do
|
2016-05-20 17:17:13 -04:00
|
|
|
it 'returns authentication error' do
|
2017-02-21 04:00:33 -05:00
|
|
|
post api('/todos/mark_as_done')
|
2016-05-20 17:17:13 -04:00
|
|
|
|
2020-03-03 16:08:37 -05:00
|
|
|
expect(response).to have_gitlab_http_status(:unauthorized)
|
2016-03-11 14:04:42 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when authenticated' do
|
2016-05-20 17:17:13 -04:00
|
|
|
it 'marks all todos as done' do
|
2017-02-21 04:00:33 -05:00
|
|
|
post api('/todos/mark_as_done', john_doe)
|
2016-05-20 17:17:13 -04:00
|
|
|
|
2020-03-03 16:08:37 -05:00
|
|
|
expect(response).to have_gitlab_http_status(:no_content)
|
2016-03-11 14:04:42 -05:00
|
|
|
expect(pending_1.reload).to be_done
|
|
|
|
expect(pending_2.reload).to be_done
|
|
|
|
expect(pending_3.reload).to be_done
|
|
|
|
end
|
2016-07-08 12:42:47 -04:00
|
|
|
|
|
|
|
it 'updates todos cache' do
|
|
|
|
expect_any_instance_of(User).to receive(:update_todos_count_cache).and_call_original
|
|
|
|
|
2017-02-21 04:00:33 -05:00
|
|
|
post api("/todos/mark_as_done", john_doe)
|
2016-07-08 12:42:47 -04:00
|
|
|
end
|
2016-03-11 14:04:42 -05:00
|
|
|
end
|
|
|
|
end
|
2016-06-28 12:04:44 -04:00
|
|
|
|
|
|
|
shared_examples 'an issuable' do |issuable_type|
|
|
|
|
it 'creates a todo on an issuable' do
|
2017-02-28 03:29:14 -05:00
|
|
|
post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.iid}/todo", john_doe)
|
2016-06-28 12:04:44 -04:00
|
|
|
|
2020-03-30 14:08:07 -04:00
|
|
|
expect(response).to have_gitlab_http_status(:created)
|
2016-06-28 12:04:44 -04:00
|
|
|
expect(json_response['project']).to be_a Hash
|
|
|
|
expect(json_response['author']).to be_a Hash
|
|
|
|
expect(json_response['target_type']).to eq(issuable.class.name)
|
|
|
|
expect(json_response['target']).to be_a Hash
|
|
|
|
expect(json_response['target_url']).to be_present
|
|
|
|
expect(json_response['body']).to be_present
|
|
|
|
expect(json_response['state']).to eq('pending')
|
|
|
|
expect(json_response['action_name']).to eq('marked')
|
|
|
|
expect(json_response['created_at']).to be_present
|
2020-04-23 05:09:46 -04:00
|
|
|
expect(json_response['updated_at']).to be_present
|
2016-06-28 12:04:44 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns 304 there already exist a todo on that issuable' do
|
2020-12-22 22:10:22 -05:00
|
|
|
stub_feature_flags(multiple_todos: false)
|
|
|
|
|
2016-06-28 12:04:44 -04:00
|
|
|
create(:todo, project: project_1, author: author_1, user: john_doe, target: issuable)
|
|
|
|
|
2017-02-28 03:29:14 -05:00
|
|
|
post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.iid}/todo", john_doe)
|
2016-06-28 12:04:44 -04:00
|
|
|
|
2020-03-30 14:08:07 -04:00
|
|
|
expect(response).to have_gitlab_http_status(:not_modified)
|
2016-06-28 12:04:44 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns 404 if the issuable is not found' do
|
2020-03-30 14:08:07 -04:00
|
|
|
unknown_id = 0
|
|
|
|
|
|
|
|
post api("/projects/#{project_1.id}/#{issuable_type}/#{unknown_id}/todo", john_doe)
|
2016-06-28 12:04:44 -04:00
|
|
|
|
2020-03-30 14:08:07 -04:00
|
|
|
expect(response).to have_gitlab_http_status(:not_found)
|
2016-06-28 12:04:44 -04:00
|
|
|
end
|
2017-01-03 13:03:13 -05:00
|
|
|
|
|
|
|
it 'returns an error if the issuable is not accessible' do
|
|
|
|
guest = create(:user)
|
2017-12-22 03:18:28 -05:00
|
|
|
project_1.add_guest(guest)
|
2017-01-03 13:03:13 -05:00
|
|
|
|
2017-02-28 03:29:14 -05:00
|
|
|
post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.iid}/todo", guest)
|
2017-01-03 13:03:13 -05:00
|
|
|
|
|
|
|
if issuable_type == 'merge_requests'
|
2020-03-03 16:08:37 -05:00
|
|
|
expect(response).to have_gitlab_http_status(:forbidden)
|
2017-01-03 13:03:13 -05:00
|
|
|
else
|
2020-03-03 16:08:37 -05:00
|
|
|
expect(response).to have_gitlab_http_status(:not_found)
|
2017-01-03 13:03:13 -05:00
|
|
|
end
|
|
|
|
end
|
2021-02-11 10:09:11 -05:00
|
|
|
|
|
|
|
it 'returns an error if the issuable author does not have access' do
|
|
|
|
project_1.add_guest(issuable.author)
|
|
|
|
|
|
|
|
post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.iid}/todo", issuable.author)
|
|
|
|
|
|
|
|
expect(response).to have_gitlab_http_status(:not_found)
|
|
|
|
end
|
2016-06-28 12:04:44 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
describe 'POST :id/issuable_type/:issueable_id/todo' do
|
|
|
|
context 'for an issue' do
|
|
|
|
it_behaves_like 'an issuable', 'issues' do
|
2020-03-30 14:08:07 -04:00
|
|
|
let_it_be(:issuable) do
|
|
|
|
create(:issue, :confidential, author: author_1, project: project_1)
|
|
|
|
end
|
2016-06-28 12:04:44 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'for a merge request' do
|
|
|
|
it_behaves_like 'an issuable', 'merge_requests' do
|
2020-03-30 14:08:07 -04:00
|
|
|
let_it_be(:issuable) do
|
|
|
|
create(:merge_request, :simple, source_project: project_1)
|
|
|
|
end
|
2016-06-28 12:04:44 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2016-03-11 14:04:42 -05:00
|
|
|
end
|