Commit 11f023f2 authored by GitLab Release Tools Bot's avatar GitLab Release Tools Bot

Merge branch 'security-282445-read-todo-target' into 'master'

Filter todos whose target users no longer have access to [RUN AS-IF-FOSS]

See merge request gitlab-org/security/gitlab!1494
parents a6ccbb8d 16516b2a
...@@ -16,6 +16,8 @@ class Dashboard::TodosController < Dashboard::ApplicationController ...@@ -16,6 +16,8 @@ class Dashboard::TodosController < Dashboard::ApplicationController
@todos = @todos.with_entity_associations @todos = @todos.with_entity_associations
return if redirect_out_of_range(@todos, todos_page_count(@todos)) return if redirect_out_of_range(@todos, todos_page_count(@todos))
@allowed_todos = ::Todos::AllowedTargetFilterService.new(@todos, current_user).execute
end end
def destroy def destroy
......
...@@ -34,7 +34,7 @@ module Resolvers ...@@ -34,7 +34,7 @@ module Resolvers
return Todo.none unless current_user.present? && target.present? return Todo.none unless current_user.present? && target.present?
return Todo.none if target.is_a?(User) && target != current_user return Todo.none if target.is_a?(User) && target != current_user
TodosFinder.new(current_user, todo_finder_params(args)).execute TodosFinder.new(current_user, todo_finder_params(args)).execute.with_entity_associations
end end
private private
......
...@@ -115,11 +115,9 @@ module Types ...@@ -115,11 +115,9 @@ module Types
null: true, null: true,
description: 'Runbook for the alert as defined in alert details.' description: 'Runbook for the alert as defined in alert details.'
field :todos, field :todos, description: 'To-do items of the current user for the alert.', resolver: Resolvers::TodoResolver do
Types::TodoType.connection_type, extension(::Gitlab::Graphql::TodosProjectPermissionPreloader::FieldExtension)
null: true, end
description: 'To-do items of the current user for the alert.',
resolver: Resolvers::TodoResolver
field :details_url, field :details_url,
GraphQL::Types::String, GraphQL::Types::String,
......
...@@ -55,9 +55,6 @@ module Types ...@@ -55,9 +55,6 @@ module Types
type: GraphQL::Types::String, type: GraphQL::Types::String,
null: false, null: false,
description: 'Web path of the user.' description: 'Web path of the user.'
field :todos,
resolver: Resolvers::TodoResolver,
description: 'To-do items of the user.'
field :group_memberships, field :group_memberships,
type: Types::GroupMemberType.connection_type, type: Types::GroupMemberType.connection_type,
null: true, null: true,
...@@ -81,6 +78,10 @@ module Types ...@@ -81,6 +78,10 @@ module Types
description: 'Projects starred by the user.', description: 'Projects starred by the user.',
resolver: Resolvers::UserStarredProjectsResolver resolver: Resolvers::UserStarredProjectsResolver
field :todos, resolver: Resolvers::TodoResolver, description: 'To-do items of the user.' do
extension(::Gitlab::Graphql::TodosProjectPermissionPreloader::FieldExtension)
end
# Merge request field: MRs can be authored, assigned, or assigned-for-review: # Merge request field: MRs can be authored, assigned, or assigned-for-review:
field :authored_merge_requests, field :authored_merge_requests,
resolver: Resolvers::AuthoredMergeRequestsResolver, resolver: Resolvers::AuthoredMergeRequestsResolver,
......
...@@ -266,6 +266,10 @@ module AlertManagement ...@@ -266,6 +266,10 @@ module AlertManagement
end end
end end
def to_ability_name
'alert_management_alert'
end
private private
def hook_data def hook_data
......
...@@ -103,4 +103,12 @@ class ApplicationRecord < ActiveRecord::Base ...@@ -103,4 +103,12 @@ class ApplicationRecord < ActiveRecord::Base
def self.cached_column_list def self.cached_column_list
self.column_names.map { |column_name| self.arel_table[column_name] } self.column_names.map { |column_name| self.arel_table[column_name] }
end end
def readable_by?(user)
Ability.allowed?(user, "read_#{to_ability_name}".to_sym, self)
end
def to_ability_name
model_name.element
end
end end
...@@ -550,6 +550,10 @@ class Commit ...@@ -550,6 +550,10 @@ class Commit
expire_note_etag_cache_for_related_mrs expire_note_etag_cache_for_related_mrs
end end
def readable_by?(user)
Ability.allowed?(user, :read_commit, self)
end
private private
def expire_note_etag_cache_for_related_mrs def expire_note_etag_cache_for_related_mrs
......
...@@ -182,10 +182,6 @@ module DesignManagement ...@@ -182,10 +182,6 @@ module DesignManagement
File.join(DesignManagement.designs_directory, "issue-#{issue.iid}", design.filename) File.join(DesignManagement.designs_directory, "issue-#{issue.iid}", design.filename)
end end
def to_ability_name
'design'
end
def description def description
'' ''
end end
......
...@@ -714,10 +714,6 @@ class Group < Namespace ...@@ -714,10 +714,6 @@ class Group < Namespace
Gitlab::ServiceDesk.supported? && all_projects.service_desk_enabled.exists? Gitlab::ServiceDesk.supported? && all_projects.service_desk_enabled.exists?
end end
def to_ability_name
model_name.singular
end
def activity_path def activity_path
Gitlab::Routing.url_helpers.activity_group_path(self) Gitlab::Routing.url_helpers.activity_group_path(self)
end end
......
...@@ -543,6 +543,25 @@ class Issue < ApplicationRecord ...@@ -543,6 +543,25 @@ class Issue < ApplicationRecord
self.update_column(:upvotes_count, self.upvotes) self.update_column(:upvotes_count, self.upvotes)
end end
# Returns `true` if the given User can read the current Issue.
#
# This method duplicates the same check of issue_policy.rb
# for performance reasons, check commit: 002ad215818450d2cbbc5fa065850a953dc7ada8
# Make sure to sync this method with issue_policy.rb
def readable_by?(user)
if user.can_read_all_resources?
true
elsif project.owner == user
true
elsif confidential? && !assignee_or_author?(user)
project.team.member?(user, Gitlab::Access::REPORTER)
else
project.public? ||
project.internal? && !user.external? ||
project.team.member?(user)
end
end
private private
def spammable_attribute_changed? def spammable_attribute_changed?
...@@ -568,25 +587,6 @@ class Issue < ApplicationRecord ...@@ -568,25 +587,6 @@ class Issue < ApplicationRecord
Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_created_action(author: author) Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_created_action(author: author)
end end
# Returns `true` if the given User can read the current Issue.
#
# This method duplicates the same check of issue_policy.rb
# for performance reasons, check commit: 002ad215818450d2cbbc5fa065850a953dc7ada8
# Make sure to sync this method with issue_policy.rb
def readable_by?(user)
if user.can_read_all_resources?
true
elsif project.owner == user
true
elsif confidential? && !assignee_or_author?(user)
project.team.member?(user, Gitlab::Access::REPORTER)
else
project.public? ||
project.internal? && !user.external? ||
project.team.member?(user)
end
end
# Returns `true` if this Issue is visible to everybody. # Returns `true` if this Issue is visible to everybody.
def publicly_visible? def publicly_visible?
project.public? && !confidential? && !::Gitlab::ExternalAuthorization.enabled? project.public? && !confidential? && !::Gitlab::ExternalAuthorization.enabled?
......
...@@ -384,12 +384,6 @@ class Note < ApplicationRecord ...@@ -384,12 +384,6 @@ class Note < ApplicationRecord
super super
end end
# This method is to be used for checking read permissions on a note instead of `system_note_with_references_visible_for?`
def readable_by?(user)
# note_policy accounts for #system_note_with_references_visible_for?(user) check when granting read access
Ability.allowed?(user, :read_note, self)
end
def award_emoji? def award_emoji?
can_be_award_emoji? && contains_emoji_only? can_be_award_emoji? && contains_emoji_only?
end end
...@@ -406,10 +400,6 @@ class Note < ApplicationRecord ...@@ -406,10 +400,6 @@ class Note < ApplicationRecord
note =~ /\A#{Banzai::Filter::EmojiFilter.emoji_pattern}\s?\Z/ note =~ /\A#{Banzai::Filter::EmojiFilter.emoji_pattern}\s?\Z/
end end
def to_ability_name
model_name.singular
end
def noteable_ability_name def noteable_ability_name
if for_snippet? if for_snippet?
'snippet' 'snippet'
......
...@@ -1494,10 +1494,6 @@ class Project < ApplicationRecord ...@@ -1494,10 +1494,6 @@ class Project < ApplicationRecord
end end
end end
def to_ability_name
model_name.singular
end
# rubocop: disable CodeReuse/ServiceClass # rubocop: disable CodeReuse/ServiceClass
def execute_hooks(data, hooks_scope = :push_hooks) def execute_hooks(data, hooks_scope = :push_hooks)
run_after_commit_or_now do run_after_commit_or_now do
......
...@@ -5,7 +5,10 @@ class TodoPolicy < BasePolicy ...@@ -5,7 +5,10 @@ class TodoPolicy < BasePolicy
condition(:own_todo) do condition(:own_todo) do
@user && @subject.user_id == @user.id @user && @subject.user_id == @user.id
end end
condition(:can_read_target) do
@user && @subject.target&.readable_by?(@user)
end
rule { own_todo }.enable :read_todo rule { own_todo & can_read_target }.enable :read_todo
rule { own_todo }.enable :update_todo rule { own_todo & can_read_target }.enable :update_todo
end end
# frozen_string_literal: true
module Todos
class AllowedTargetFilterService
include Gitlab::Allowable
def initialize(todos, current_user)
@todos = todos
@current_user = current_user
end
def execute
Preloaders::UserMaxAccessLevelInProjectsPreloader.new(@todos.map(&:project).compact, @current_user).execute
@todos.select { |todo| can?(@current_user, :read_todo, todo) }
end
end
end
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
= number_with_delimiter(todos_done_count) = number_with_delimiter(todos_done_count)
.nav-controls .nav-controls
- if @todos.any?(&:pending?) - if @allowed_todos.any?(&:pending?)
.gl-mr-3 .gl-mr-3
= link_to destroy_all_dashboard_todos_path(todos_filter_params), class: 'gl-button btn btn-default btn-loading align-items-center js-todos-mark-all', method: :delete, data: { href: destroy_all_dashboard_todos_path(todos_filter_params) } do = link_to destroy_all_dashboard_todos_path(todos_filter_params), class: 'gl-button btn btn-default btn-loading align-items-center js-todos-mark-all', method: :delete, data: { href: destroy_all_dashboard_todos_path(todos_filter_params) } do
Mark all as done Mark all as done
...@@ -82,11 +82,11 @@ ...@@ -82,11 +82,11 @@
= sort_title_oldest_created = sort_title_oldest_created
.row.js-todos-all .row.js-todos-all
- if @todos.any? - if @allowed_todos.any?
.col.js-todos-list-container{ data: { qa_selector: "todos_list_container" } } .col.js-todos-list-container{ data: { qa_selector: "todos_list_container" } }
.js-todos-options{ data: { per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages } } .js-todos-options{ data: { per_page: @allowed_todos.count, current_page: @todos.current_page, total_pages: @todos.total_pages } }
%ul.content-list.todos-list %ul.content-list.todos-list
= render @todos = render @allowed_todos
= paginate @todos, theme: "gitlab" = paginate @todos, theme: "gitlab"
.js-nothing-here-container.empty-state.hidden .js-nothing-here-container.empty-state.hidden
.svg-content .svg-content
......
...@@ -6,7 +6,9 @@ RSpec.describe 'getting project information' do ...@@ -6,7 +6,9 @@ RSpec.describe 'getting project information' do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let_it_be(:epic_todo) { create(:todo, user: current_user, target: create(:epic)) } let_it_be(:group) { create(:group) }
let_it_be(:epic) { create(:epic, group: group) }
let_it_be(:epic_todo) { create(:todo, user: current_user, target: epic) }
let(:fields) do let(:fields) do
<<~QUERY <<~QUERY
...@@ -22,7 +24,13 @@ RSpec.describe 'getting project information' do ...@@ -22,7 +24,13 @@ RSpec.describe 'getting project information' do
subject { graphql_data.dig('currentUser', 'todos', 'nodes') } subject { graphql_data.dig('currentUser', 'todos', 'nodes') }
before_all do
group.add_developer(current_user)
end
before do before do
stub_licensed_features(epics: true)
post_graphql(query, current_user: current_user) post_graphql(query, current_user: current_user)
end end
......
...@@ -31,6 +31,8 @@ RSpec.describe API::Todos do ...@@ -31,6 +31,8 @@ RSpec.describe API::Todos do
let!(:epic_todo) { create_todo_for_new_epic } let!(:epic_todo) { create_todo_for_new_epic }
before do before do
stub_licensed_features(epics: true)
get api('/todos', personal_access_token: pat) get api('/todos', personal_access_token: pat)
end end
...@@ -45,7 +47,8 @@ RSpec.describe API::Todos do ...@@ -45,7 +47,8 @@ RSpec.describe API::Todos do
create_todo_for_new_epic create_todo_for_new_epic
expect { get api('/todos', personal_access_token: pat) }.not_to exceed_query_limit(control) # Additional query due to authorization check on new group
expect { get api('/todos', personal_access_token: pat) }.not_to exceed_query_limit(control).with_threshold(1)
end end
it 'includes the Epic Todo in the response' do it 'includes the Epic Todo in the response' do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Todos::AllowedTargetFilterService do
let_it_be(:authorized_group) { create(:group, :private) }
let_it_be(:authorized_project) { create(:project, group: authorized_group) }
let_it_be(:unauthorized_group) { create(:group, :private) }
let_it_be(:unauthorized_project) { create(:project, group: unauthorized_group) }
let_it_be(:user) { create(:user) }
let_it_be(:authorized_epic) { create(:epic, group: authorized_group) }
let_it_be(:authorized_epic_todo) { create(:todo, group: authorized_group, target: authorized_epic, user: user) }
let_it_be(:unauthorized_epic) { create(:epic, group: unauthorized_group) }
let_it_be(:unauthorized_epic_todo) { create(:todo, group: unauthorized_group, target: unauthorized_epic, user: user) }
before_all do
authorized_group.add_developer(user)
end
describe '#execute' do
subject(:execute_service) { described_class.new(all_todos, user).execute }
let!(:all_todos) { authorized_todos + unauthorized_todos }
let(:authorized_todos) do
[
authorized_epic_todo
]
end
let(:unauthorized_todos) do
[
unauthorized_epic_todo
]
end
before do
stub_licensed_features(epics: true)
end
it { is_expected.to match_array(authorized_todos) }
end
end
...@@ -92,6 +92,7 @@ module API ...@@ -92,6 +92,7 @@ module API
end end
get do get do
todos = paginate(find_todos.with_entity_associations) todos = paginate(find_todos.with_entity_associations)
todos = ::Todos::AllowedTargetFilterService.new(todos, current_user).execute
options = { with: Entities::Todo, current_user: current_user } options = { with: Entities::Todo, current_user: current_user }
batch_load_issuable_metadata(todos, options) batch_load_issuable_metadata(todos, options)
......
# frozen_string_literal: true
module Gitlab
module Graphql
module TodosProjectPermissionPreloader
class FieldExtension < ::GraphQL::Schema::FieldExtension
def after_resolve(value:, memo:, **rest)
todos = value.to_a
Preloaders::UserMaxAccessLevelInProjectsPreloader.new(
todos.map(&:project).compact,
current_user(rest)
).execute
value
end
private
def current_user(options)
options.dig(:context, :current_user)
end
end
end
end
end
...@@ -3,16 +3,20 @@ ...@@ -3,16 +3,20 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'Dashboard > Todo target states' do RSpec.describe 'Dashboard > Todo target states' do
let(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:author) { create(:user) } let_it_be(:author) { create(:user) }
let(:project) { create(:project, :public) } let_it_be(:project) { create(:project, :public) }
before_all do
project.add_developer(user)
end
before do before do
sign_in(user) sign_in(user)
end end
it 'on a closed issue todo has closed label' do it 'on a closed issue todo has closed label' do
issue_closed = create(:issue, state: 'closed') issue_closed = create(:issue, state: 'closed', project: project)
create_todo issue_closed create_todo issue_closed
visit dashboard_todos_path visit dashboard_todos_path
...@@ -22,7 +26,7 @@ RSpec.describe 'Dashboard > Todo target states' do ...@@ -22,7 +26,7 @@ RSpec.describe 'Dashboard > Todo target states' do
end end
it 'on an open issue todo does not have an open label' do it 'on an open issue todo does not have an open label' do
issue_open = create(:issue) issue_open = create(:issue, project: project)
create_todo issue_open create_todo issue_open
visit dashboard_todos_path visit dashboard_todos_path
...@@ -32,7 +36,7 @@ RSpec.describe 'Dashboard > Todo target states' do ...@@ -32,7 +36,7 @@ RSpec.describe 'Dashboard > Todo target states' do
end end
it 'on a merged merge request todo has merged label' do it 'on a merged merge request todo has merged label' do
mr_merged = create(:merge_request, :simple, :merged, author: user) mr_merged = create(:merge_request, :simple, :merged, author: user, source_project: project)
create_todo mr_merged create_todo mr_merged
visit dashboard_todos_path visit dashboard_todos_path
...@@ -42,7 +46,7 @@ RSpec.describe 'Dashboard > Todo target states' do ...@@ -42,7 +46,7 @@ RSpec.describe 'Dashboard > Todo target states' do
end end
it 'on a closed merge request todo has closed label' do it 'on a closed merge request todo has closed label' do
mr_closed = create(:merge_request, :simple, :closed, author: user) mr_closed = create(:merge_request, :simple, :closed, author: user, source_project: project)
create_todo mr_closed create_todo mr_closed
visit dashboard_todos_path visit dashboard_todos_path
...@@ -52,7 +56,7 @@ RSpec.describe 'Dashboard > Todo target states' do ...@@ -52,7 +56,7 @@ RSpec.describe 'Dashboard > Todo target states' do
end end
it 'on an open merge request todo does not have an open label' do it 'on an open merge request todo does not have an open label' do
mr_open = create(:merge_request, :simple, author: user) mr_open = create(:merge_request, :simple, author: user, source_project: project)
create_todo mr_open create_todo mr_open
visit dashboard_todos_path visit dashboard_todos_path
......
...@@ -128,7 +128,7 @@ RSpec.describe 'Dashboard > User filters todos', :js do ...@@ -128,7 +128,7 @@ RSpec.describe 'Dashboard > User filters todos', :js do
describe 'filter by action' do describe 'filter by action' do
before do before do
create(:todo, :build_failed, user: user_1, author: user_2, project: project_1) create(:todo, :build_failed, user: user_1, author: user_2, project: project_1, target: merge_request)
create(:todo, :marked, user: user_1, author: user_2, project: project_1, target: issue1) create(:todo, :marked, user: user_1, author: user_2, project: project_1, target: issue1)
create(:todo, :review_requested, user: user_1, author: user_2, project: project_1, target: issue1) create(:todo, :review_requested, user: user_1, author: user_2, project: project_1, target: issue1)
end end
......
...@@ -3,10 +3,16 @@ ...@@ -3,10 +3,16 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'Dashboard Todos' do RSpec.describe 'Dashboard Todos' do
include DesignManagementTestHelpers
let_it_be(:user) { create(:user, username: 'john') } let_it_be(:user) { create(:user, username: 'john') }
let_it_be(:author) { create(:user) } let_it_be(:author) { create(:user) }
let_it_be(:project) { create(:project, :public) } let_it_be(:project) { create(:project, :public) }
let_it_be(:issue) { create(:issue, due_date: Date.today, title: "Fix bug") } let_it_be(:issue) { create(:issue, project: project, due_date: Date.today, title: "Fix bug") }
before_all do
project.add_developer(user)
end
context 'User does not have todos' do context 'User does not have todos' do
before do before do
...@@ -21,8 +27,8 @@ RSpec.describe 'Dashboard Todos' do ...@@ -21,8 +27,8 @@ RSpec.describe 'Dashboard Todos' do
context 'when the todo references a merge request' do context 'when the todo references a merge request' do
let(:referenced_mr) { create(:merge_request, source_project: project) } let(:referenced_mr) { create(:merge_request, source_project: project) }
let(:note) { create(:note, project: project, note: "Check out #{referenced_mr.to_reference}") } let(:note) { create(:note, project: project, note: "Check out #{referenced_mr.to_reference}", noteable: create(:issue, project: project)) }
let!(:todo) { create(:todo, :mentioned, user: user, project: project, author: author, note: note) } let!(:todo) { create(:todo, :mentioned, user: user, project: project, author: author, note: note, target: note.noteable) }
before do before do
sign_in(user) sign_in(user)
...@@ -39,9 +45,26 @@ RSpec.describe 'Dashboard Todos' do ...@@ -39,9 +45,26 @@ RSpec.describe 'Dashboard Todos' do
end end
end end
context 'User has a todo', :js do context 'user has an unauthorized todo' do
before do before do
sign_in(user)
end
it 'does not render the todo' do
unauthorized_issue = create(:issue)
create(:todo, :mentioned, user: user, project: unauthorized_issue.project, target: unauthorized_issue, author: author)
create(:todo, :mentioned, user: user, project: project, target: issue, author: author) create(:todo, :mentioned, user: user, project: project, target: issue, author: author)
visit dashboard_todos_path
expect(page).to have_selector('.todos-list .todo', count: 1)
end
end
context 'User has a todo', :js do
let_it_be(:user_todo) { create(:todo, :mentioned, user: user, project: project, target: issue, author: author) }
before do
sign_in(user) sign_in(user)
visit dashboard_todos_path visit dashboard_todos_path
...@@ -183,7 +206,7 @@ RSpec.describe 'Dashboard Todos' do ...@@ -183,7 +206,7 @@ RSpec.describe 'Dashboard Todos' do
end end
context 'approval todo' do context 'approval todo' do
let(:merge_request) { create(:merge_request, title: "Fixes issue") } let(:merge_request) { create(:merge_request, title: "Fixes issue", source_project: project) }
before do before do
create(:todo, :approval_required, user: user, project: project, target: merge_request, author: user) create(:todo, :approval_required, user: user, project: project, target: merge_request, author: user)
...@@ -199,7 +222,7 @@ RSpec.describe 'Dashboard Todos' do ...@@ -199,7 +222,7 @@ RSpec.describe 'Dashboard Todos' do
end end
context 'review request todo' do context 'review request todo' do
let(:merge_request) { create(:merge_request, title: "Fixes issue") } let(:merge_request) { create(:merge_request, title: "Fixes issue", source_project: project) }
before do before do
create(:todo, :review_requested, user: user, project: project, target: merge_request, author: user) create(:todo, :review_requested, user: user, project: project, target: merge_request, author: user)
...@@ -355,7 +378,7 @@ RSpec.describe 'Dashboard Todos' do ...@@ -355,7 +378,7 @@ RSpec.describe 'Dashboard Todos' do
end end
context 'User has a Build Failed todo' do context 'User has a Build Failed todo' do
let!(:todo) { create(:todo, :build_failed, user: user, project: project, author: author) } let!(:todo) { create(:todo, :build_failed, user: user, project: project, author: author, target: create(:merge_request, source_project: project)) }
before do before do
sign_in(user) sign_in(user)
...@@ -386,6 +409,7 @@ RSpec.describe 'Dashboard Todos' do ...@@ -386,6 +409,7 @@ RSpec.describe 'Dashboard Todos' do
end end
before do before do
enable_design_management
project.add_developer(user) project.add_developer(user)
sign_in(user) sign_in(user)
......
...@@ -5,17 +5,23 @@ require 'spec_helper' ...@@ -5,17 +5,23 @@ require 'spec_helper'
RSpec.describe Mutations::Todos::MarkDone do RSpec.describe Mutations::Todos::MarkDone do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:project) { create(:project) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let_it_be(:author) { create(:user) } let_it_be(:author) { create(:user) }
let_it_be(:other_user) { create(:user) } let_it_be(:other_user) { create(:user) }
let_it_be(:todo1) { create(:todo, user: current_user, author: author, state: :pending) } let_it_be(:todo1) { create(:todo, user: current_user, author: author, state: :pending, target: issue) }
let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :done) } let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :done, target: issue) }
let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :pending) } let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :pending) }
let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) } let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) }
before_all do
project.add_developer(current_user)
end
specify { expect(described_class).to require_graphql_authorizations(:update_todo) } specify { expect(described_class).to require_graphql_authorizations(:update_todo) }
describe '#resolve' do describe '#resolve' do
......
...@@ -5,17 +5,23 @@ require 'spec_helper' ...@@ -5,17 +5,23 @@ require 'spec_helper'
RSpec.describe Mutations::Todos::Restore do RSpec.describe Mutations::Todos::Restore do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:project) { create(:project) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let_it_be(:author) { create(:user) } let_it_be(:author) { create(:user) }
let_it_be(:other_user) { 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(:todo1) { create(:todo, user: current_user, author: author, state: :done, target: issue) }
let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :pending) } let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :pending, target: issue) }
let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :done) } let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :done) }
let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) } let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) }
before_all do
project.add_developer(current_user)
end
specify { expect(described_class).to require_graphql_authorizations(:update_todo) } specify { expect(described_class).to require_graphql_authorizations(:update_todo) }
describe '#resolve' do describe '#resolve' do
......
...@@ -4,19 +4,28 @@ require 'spec_helper' ...@@ -4,19 +4,28 @@ require 'spec_helper'
RSpec.describe Resolvers::TodoResolver do RSpec.describe Resolvers::TodoResolver do
include GraphqlHelpers include GraphqlHelpers
include DesignManagementTestHelpers
specify do specify do
expect(described_class).to have_nullable_graphql_type(Types::TodoType.connection_type) expect(described_class).to have_nullable_graphql_type(Types::TodoType.connection_type)
end end
describe '#resolve' do describe '#resolve' do
let_it_be(:project) { create(:project) }
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:author1) { create(:user) } let_it_be(:author1) { create(:user) }
let_it_be(:author2) { create(:user) } let_it_be(:author2) { create(:user) }
let_it_be(:merge_request_todo_pending) { create(:todo, user: current_user, target_type: 'MergeRequest', state: :pending, action: Todo::MENTIONED, author: author1) } let_it_be(:issue_todo_done) { create(:todo, user: current_user, state: :done, action: Todo::ASSIGNED, author: author2, target: issue) }
let_it_be(:issue_todo_done) { create(:todo, user: current_user, state: :done, action: Todo::ASSIGNED, author: author2) } let_it_be(:issue_todo_pending) { create(:todo, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1, target: issue) }
let_it_be(:issue_todo_pending) { create(:todo, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1) }
let(:merge_request) { create(:merge_request, source_project: project) }
let!(:merge_request_todo_pending) { create(:todo, user: current_user, target: merge_request, state: :pending, action: Todo::MENTIONED, author: author1) }
before_all do
project.add_developer(current_user)
end
it 'calls TodosFinder' do it 'calls TodosFinder' do
expect_next_instance_of(TodosFinder) do |finder| expect_next_instance_of(TodosFinder) do |finder|
...@@ -40,7 +49,9 @@ RSpec.describe Resolvers::TodoResolver do ...@@ -40,7 +49,9 @@ RSpec.describe Resolvers::TodoResolver do
end end
it 'returns the todos for multiple filters' do it 'returns the todos for multiple filters' do
design_todo_pending = create(:todo, target_type: 'DesignManagement::Design', user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1) enable_design_management
design = create(:design, issue: issue)
design_todo_pending = create(:todo, target: design, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1)
todos = resolve_todos(type: ['MergeRequest', 'DesignManagement::Design']) todos = resolve_todos(type: ['MergeRequest', 'DesignManagement::Design'])
...@@ -59,11 +70,15 @@ RSpec.describe Resolvers::TodoResolver do ...@@ -59,11 +70,15 @@ RSpec.describe Resolvers::TodoResolver do
group3 = create(:group) group3 = create(:group)
group1.add_developer(current_user) group1.add_developer(current_user)
issue1 = create(:issue, project: create(:project, group: group1))
group2.add_developer(current_user) group2.add_developer(current_user)
issue2 = create(:issue, project: create(:project, group: group2))
group3.add_developer(current_user)
issue3 = create(:issue, project: create(:project, group: group3))
todo4 = create(:todo, group: group1, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1) todo4 = create(:todo, group: group1, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1, target: issue1)
todo5 = create(:todo, group: group2, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1) todo5 = create(:todo, group: group2, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1, target: issue2)
create(:todo, group: group3, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1) create(:todo, group: group3, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1, target: issue3)
todos = resolve_todos(group_id: [group2.id, group1.id]) todos = resolve_todos(group_id: [group2.id, group1.id])
...@@ -93,9 +108,13 @@ RSpec.describe Resolvers::TodoResolver do ...@@ -93,9 +108,13 @@ RSpec.describe Resolvers::TodoResolver do
project2 = create(:project) project2 = create(:project)
project3 = create(:project) project3 = create(:project)
todo4 = create(:todo, project: project1, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1) project1.add_developer(current_user)
todo5 = create(:todo, project: project2, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1) project2.add_developer(current_user)
create(:todo, project: project3, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1) project3.add_developer(current_user)
todo4 = create(:todo, project: project1, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1, target: create(:issue, project: project1))
todo5 = create(:todo, project: project2, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1, target: create(:issue, project: project2))
create(:todo, project: project3, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1, target: create(:issue, project: project3))
todos = resolve_todos(project_id: [project2.id, project1.id]) todos = resolve_todos(project_id: [project2.id, project1.id])
......
...@@ -552,4 +552,10 @@ RSpec.describe DiffNote do ...@@ -552,4 +552,10 @@ RSpec.describe DiffNote do
expect(subject.on_image?).to be_truthy expect(subject.on_image?).to be_truthy
end end
end end
describe '#to_ability_name' do
subject { described_class.new.to_ability_name }
it { is_expected.to eq('note') }
end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe DiscussionNote do
describe '#to_ability_name' do
subject { described_class.new.to_ability_name }
it { is_expected.to eq('note') }
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe LegacyDiffNote do
describe '#to_ability_name' do
subject { described_class.new.to_ability_name }
it { is_expected.to eq('note') }
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe SyntheticNote do
describe '#to_ability_name' do
subject { described_class.new.to_ability_name }
it { is_expected.to eq('note') }
end
end
...@@ -9,22 +9,28 @@ RSpec.describe TodoPolicy do ...@@ -9,22 +9,28 @@ RSpec.describe TodoPolicy do
let_it_be(:user2) { create(:user) } let_it_be(:user2) { create(:user) }
let_it_be(:user3) { create(:user) } let_it_be(:user3) { create(:user) }
let_it_be(:todo1) { create(:todo, author: author, user: user1) } let_it_be(:project) { create(:project) }
let_it_be(:todo2) { create(:todo, author: author, user: user2) } let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:todo1) { create(:todo, author: author, user: user1, issue: issue) }
let_it_be(:todo2) { create(:todo, author: author, user: user2, issue: issue) }
let_it_be(:todo3) { create(:todo, author: author, user: user2) } let_it_be(:todo3) { create(:todo, author: author, user: user2) }
let_it_be(:todo4) { create(:todo, author: author, user: user3) } let_it_be(:todo4) { create(:todo, author: author, user: user3, issue: issue) }
def permissions(user, todo) def permissions(user, todo)
described_class.new(user, todo) described_class.new(user, todo)
end end
before_all do
project.add_developer(user1)
project.add_developer(user2)
end
describe 'own_todo' do describe 'own_todo' do
it 'allows owners to access their own todos' do it 'allows owners to access their own todos if they can read todo target' do
[ [
[user1, todo1], [user1, todo1],
[user2, todo2], [user2, todo2]
[user2, todo3],
[user3, todo4]
].each do |user, todo| ].each do |user, todo|
expect(permissions(user, todo)).to be_allowed(:read_todo) expect(permissions(user, todo)).to be_allowed(:read_todo)
end end
...@@ -38,7 +44,9 @@ RSpec.describe TodoPolicy do ...@@ -38,7 +44,9 @@ RSpec.describe TodoPolicy do
[user2, todo4], [user2, todo4],
[user3, todo1], [user3, todo1],
[user3, todo2], [user3, todo2],
[user3, todo3] [user3, todo3],
[user2, todo3],
[user3, todo4]
].each do |user, todo| ].each do |user, todo|
expect(permissions(user, todo)).to be_disallowed(:read_todo) expect(permissions(user, todo)).to be_disallowed(:read_todo)
end end
......
...@@ -4,12 +4,17 @@ require 'spec_helper' ...@@ -4,12 +4,17 @@ require 'spec_helper'
RSpec.describe 'Query current user todos' do RSpec.describe 'Query current user todos' do
include GraphqlHelpers include GraphqlHelpers
include DesignManagementTestHelpers
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let_it_be(:commit_todo) { create(:on_commit_todo, user: current_user, project: create(:project, :repository)) } let_it_be(:project) { create(:project, :repository) }
let_it_be(:issue_todo) { create(:todo, user: current_user, target: create(:issue)) } let_it_be(:unauthorize_project) { create(:project) }
let_it_be(:merge_request_todo) { create(:todo, user: current_user, target: create(:merge_request)) } let_it_be(:commit_todo) { create(:on_commit_todo, user: current_user, project: project) }
let_it_be(:design_todo) { create(:todo, user: current_user, target: create(:design)) } let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:issue_todo) { create(:todo, project: project, user: current_user, target: issue) }
let_it_be(:merge_request_todo) { create(:todo, project: project, user: current_user, target: create(:merge_request, source_project: project)) }
let_it_be(:design_todo) { create(:todo, project: project, user: current_user, target: create(:design, issue: issue)) }
let_it_be(:unauthorized_todo) { create(:todo, user: current_user, project: unauthorize_project, target: create(:issue, project: unauthorize_project)) }
let(:fields) do let(:fields) do
<<~QUERY <<~QUERY
...@@ -23,16 +28,22 @@ RSpec.describe 'Query current user todos' do ...@@ -23,16 +28,22 @@ RSpec.describe 'Query current user todos' do
graphql_query_for('currentUser', {}, query_graphql_field('todos', {}, fields)) graphql_query_for('currentUser', {}, query_graphql_field('todos', {}, fields))
end end
before_all do
project.add_developer(current_user)
end
subject { graphql_data.dig('currentUser', 'todos', 'nodes') } subject { graphql_data.dig('currentUser', 'todos', 'nodes') }
before do before do
enable_design_management
post_graphql(query, current_user: current_user) post_graphql(query, current_user: current_user)
end end
it_behaves_like 'a working graphql query' it_behaves_like 'a working graphql query'
it 'contains the expected ids' do it 'contains the expected ids' do
is_expected.to include( is_expected.to contain_exactly(
a_hash_including('id' => commit_todo.to_global_id.to_s), a_hash_including('id' => commit_todo.to_global_id.to_s),
a_hash_including('id' => issue_todo.to_global_id.to_s), a_hash_including('id' => issue_todo.to_global_id.to_s),
a_hash_including('id' => merge_request_todo.to_global_id.to_s), a_hash_including('id' => merge_request_todo.to_global_id.to_s),
...@@ -41,11 +52,33 @@ RSpec.describe 'Query current user todos' do ...@@ -41,11 +52,33 @@ RSpec.describe 'Query current user todos' do
end end
it 'returns Todos for all target types' do it 'returns Todos for all target types' do
is_expected.to include( is_expected.to contain_exactly(
a_hash_including('targetType' => 'COMMIT'), a_hash_including('targetType' => 'COMMIT'),
a_hash_including('targetType' => 'ISSUE'), a_hash_including('targetType' => 'ISSUE'),
a_hash_including('targetType' => 'MERGEREQUEST'), a_hash_including('targetType' => 'MERGEREQUEST'),
a_hash_including('targetType' => 'DESIGN') a_hash_including('targetType' => 'DESIGN')
) )
end end
context 'when requesting a single field' do
let(:fields) do
<<~QUERY
nodes {
id
}
QUERY
end
it 'avoids N+1 queries', :request_store do
control = ActiveRecord::QueryRecorder.new { post_graphql(query, current_user: current_user) }
project2 = create(:project)
project2.add_developer(current_user)
issue2 = create(:issue, project: project2)
create(:todo, user: current_user, target: issue2, project: project2)
# An additional query is made for each different group that owns a todo through a project
expect { post_graphql(query, current_user: current_user) }.not_to exceed_query_limit(control).with_threshold(2)
end
end
end end
...@@ -5,14 +5,16 @@ require 'spec_helper' ...@@ -5,14 +5,16 @@ require 'spec_helper'
RSpec.describe 'Marking all todos done' do RSpec.describe 'Marking all todos done' do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:project) { create(:project) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let_it_be(:author) { create(:user) } let_it_be(:author) { create(:user) }
let_it_be(:other_user) { create(:user) } let_it_be(:other_user) { create(:user) }
let_it_be(:other_user2) { 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(:todo1) { create(:todo, user: current_user, author: author, state: :pending, target: issue) }
let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :done) } let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :done, target: issue) }
let_it_be(:todo3) { create(:todo, user: current_user, author: author, state: :pending) } let_it_be(:todo3) { create(:todo, user: current_user, author: author, state: :pending, target: issue) }
let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :pending) } let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :pending) }
...@@ -28,6 +30,10 @@ RSpec.describe 'Marking all todos done' do ...@@ -28,6 +30,10 @@ RSpec.describe 'Marking all todos done' do
) )
end end
before_all do
project.add_developer(current_user)
end
def mutation_response def mutation_response
graphql_mutation_response(:todos_mark_all_done) graphql_mutation_response(:todos_mark_all_done)
end end
......
...@@ -5,12 +5,14 @@ require 'spec_helper' ...@@ -5,12 +5,14 @@ require 'spec_helper'
RSpec.describe 'Marking todos done' do RSpec.describe 'Marking todos done' do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:project) { create(:project) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let_it_be(:author) { create(:user) } let_it_be(:author) { create(:user) }
let_it_be(:other_user) { create(:user) } let_it_be(:other_user) { create(:user) }
let_it_be(:todo1) { create(:todo, user: current_user, author: author, state: :pending) } let_it_be(:todo1) { create(:todo, user: current_user, author: author, state: :pending, target: issue) }
let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :done) } let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :done, target: issue) }
let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :pending) } let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :pending) }
...@@ -29,6 +31,10 @@ RSpec.describe 'Marking todos done' do ...@@ -29,6 +31,10 @@ RSpec.describe 'Marking todos done' do
) )
end end
before_all do
project.add_developer(current_user)
end
def mutation_response def mutation_response
graphql_mutation_response(:todo_mark_done) graphql_mutation_response(:todo_mark_done)
end end
......
...@@ -5,12 +5,14 @@ require 'spec_helper' ...@@ -5,12 +5,14 @@ require 'spec_helper'
RSpec.describe 'Restoring many Todos' do RSpec.describe 'Restoring many Todos' do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:project) { create(:project) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let_it_be(:author) { create(:user) } let_it_be(:author) { create(:user) }
let_it_be(:other_user) { 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(:todo1) { create(:todo, user: current_user, author: author, state: :done, target: issue) }
let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :done) } let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :done, target: issue) }
let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :done) } let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :done) }
...@@ -30,6 +32,10 @@ RSpec.describe 'Restoring many Todos' do ...@@ -30,6 +32,10 @@ RSpec.describe 'Restoring many Todos' do
) )
end end
before_all do
project.add_developer(current_user)
end
def mutation_response def mutation_response
graphql_mutation_response(:todo_restore_many) graphql_mutation_response(:todo_restore_many)
end end
......
...@@ -5,12 +5,14 @@ require 'spec_helper' ...@@ -5,12 +5,14 @@ require 'spec_helper'
RSpec.describe 'Restoring Todos' do RSpec.describe 'Restoring Todos' do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:project) { create(:project) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let_it_be(:author) { create(:user) } let_it_be(:author) { create(:user) }
let_it_be(:other_user) { 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(:todo1) { create(:todo, user: current_user, author: author, state: :done, target: issue) }
let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :pending) } let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :pending, target: issue) }
let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :done) } let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :done) }
...@@ -29,6 +31,10 @@ RSpec.describe 'Restoring Todos' do ...@@ -29,6 +31,10 @@ RSpec.describe 'Restoring Todos' do
) )
end end
before_all do
project.add_developer(current_user)
end
def mutation_response def mutation_response
graphql_mutation_response(:todo_restore) graphql_mutation_response(:todo_restore)
end end
......
...@@ -3,18 +3,22 @@ ...@@ -3,18 +3,22 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe API::Todos do RSpec.describe API::Todos do
include DesignManagementTestHelpers
let_it_be(:group) { create(:group) } let_it_be(:group) { create(:group) }
let_it_be(:project_1) { create(:project, :repository, group: group) } let_it_be(:project_1) { create(:project, :repository, group: group) }
let_it_be(:project_2) { create(:project) } let_it_be(:project_2) { create(:project) }
let_it_be(:author_1) { create(:user) } let_it_be(:author_1) { create(:user) }
let_it_be(:author_2) { create(:user) } let_it_be(:author_2) { create(:user) }
let_it_be(:john_doe) { create(:user, username: 'john_doe') } let_it_be(:john_doe) { create(:user, username: 'john_doe') }
let_it_be(:issue) { create(:issue, project: project_1) }
let_it_be(:merge_request) { create(:merge_request, source_project: project_1) } 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(: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_1) { create(:todo, :mentioned, project: project_1, author: author_1, user: john_doe, target: issue) }
let_it_be(:pending_2) { create(:todo, project: project_2, author: author_2, user: john_doe) } let_it_be(:pending_2) { create(:todo, project: project_2, author: author_2, user: john_doe, target: issue) }
let_it_be(:pending_3) { create(:on_commit_todo, project: project_1, 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(:pending_4) { create(:on_commit_todo, project: project_1, author: author_2, user: john_doe, commit_id: 'invalid_id') }
let_it_be(:done) { create(:todo, :done, project: project_1, author: author_1, user: john_doe, target: issue) }
let_it_be(:award_emoji_1) { create(:award_emoji, awardable: merge_request, user: author_1, name: 'thumbsup') } 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_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') } let_it_be(:award_emoji_3) { create(:award_emoji, awardable: pending_2.target, user: author_2, name: 'thumbsdown') }
...@@ -77,13 +81,13 @@ RSpec.describe API::Todos do ...@@ -77,13 +81,13 @@ RSpec.describe API::Todos do
expect(json_response[0]['target_type']).to eq('Commit') expect(json_response[0]['target_type']).to eq('Commit')
expect(json_response[1]['target_type']).to eq('Issue') expect(json_response[1]['target_type']).to eq('Issue')
expect(json_response[1]['target']['upvotes']).to eq(0) expect(json_response[1]['target']['upvotes']).to eq(1)
expect(json_response[1]['target']['downvotes']).to eq(1) expect(json_response[1]['target']['downvotes']).to eq(1)
expect(json_response[1]['target']['merge_requests_count']).to eq(0) 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_type']).to eq('Issue')
expect(json_response[2]['target']['upvotes']).to eq(1) expect(json_response[2]['target']['upvotes']).to eq(1)
expect(json_response[2]['target']['downvotes']).to eq(0) expect(json_response[2]['target']['downvotes']).to eq(1)
expect(json_response[2]['target']['merge_requests_count']).to eq(0) expect(json_response[2]['target']['merge_requests_count']).to eq(0)
expect(json_response[3]['target_type']).to eq('MergeRequest') expect(json_response[3]['target_type']).to eq('MergeRequest')
...@@ -93,6 +97,19 @@ RSpec.describe API::Todos do ...@@ -93,6 +97,19 @@ RSpec.describe API::Todos do
expect(json_response[3]['target']['downvotes']).to eq(0) expect(json_response[3]['target']['downvotes']).to eq(0)
end end
context "when current user does not have access to one of the TODO's target" do
it 'filters out unauthorized todos' do
no_access_project = create(:project, :repository, group: group)
no_access_merge_request = create(:merge_request, source_project: no_access_project)
no_access_todo = create(:todo, project: no_access_project, author: author_2, user: john_doe, target: no_access_merge_request)
get api('/todos', john_doe)
expect(json_response.count).to eq(4)
expect(json_response.map { |t| t['id'] }).not_to include(no_access_todo.id, pending_4.id)
end
end
context 'and using the author filter' do context 'and using the author filter' do
it 'filters based on author_id param' do it 'filters based on author_id param' do
get api('/todos', john_doe), params: { author_id: author_2.id } get api('/todos', john_doe), params: { author_id: author_2.id }
...@@ -163,23 +180,31 @@ RSpec.describe API::Todos do ...@@ -163,23 +180,31 @@ RSpec.describe API::Todos do
end end
it 'avoids N+1 queries', :request_store do it 'avoids N+1 queries', :request_store do
create_issue_todo_for(john_doe)
create(:todo, project: project_1, author: author_2, user: john_doe, target: merge_request) create(:todo, project: project_1, author: author_2, user: john_doe, target: merge_request)
get api('/todos', john_doe) get api('/todos', john_doe)
control = ActiveRecord::QueryRecorder.new { get api('/todos', john_doe) } control1 = ActiveRecord::QueryRecorder.new { get api('/todos', john_doe) }
create_issue_todo_for(john_doe)
create_mr_todo_for(john_doe, project_2)
create(:todo, :mentioned, project: project_1, author: author_1, user: john_doe, target: merge_request)
new_todo = create_mr_todo_for(john_doe)
merge_request_3 = create(:merge_request, :jira_branch, source_project: new_todo.project)
create(:on_commit_todo, project: new_todo.project, author: author_1, user: john_doe, target: merge_request_3)
create(:todo, project: new_todo.project, author: author_2, user: john_doe, target: merge_request_3)
merge_request_2 = create(:merge_request, source_project: project_2) expect { get api('/todos', john_doe) }.not_to exceed_query_limit(control1).with_threshold(4)
create(:todo, project: project_2, author: author_2, user: john_doe, target: merge_request_2) control2 = ActiveRecord::QueryRecorder.new { get api('/todos', john_doe) }
project_3 = create(:project, :repository) create_issue_todo_for(john_doe)
project_3.add_developer(john_doe) create_issue_todo_for(john_doe, project_1)
merge_request_3 = create(:merge_request, source_project: project_3) create_issue_todo_for(john_doe, project_1)
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) # Additional query only when target belongs to project from different group
create(:on_commit_todo, project: project_3, author: author_1, user: john_doe) expect { get api('/todos', john_doe) }.not_to exceed_query_limit(control2).with_threshold(1)
expect { get api('/todos', john_doe) }.not_to exceed_query_limit(control)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
end end
...@@ -201,6 +226,8 @@ RSpec.describe API::Todos do ...@@ -201,6 +226,8 @@ RSpec.describe API::Todos do
end end
before do before do
enable_design_management
api_request api_request
end end
...@@ -222,6 +249,20 @@ RSpec.describe API::Todos do ...@@ -222,6 +249,20 @@ RSpec.describe API::Todos do
) )
end end
end end
def create_mr_todo_for(user, project = nil)
new_project = project || create(:project, group: create(:group))
new_project.add_developer(user) if project.blank?
new_merge_request = create(:merge_request, source_project: new_project)
create(:todo, project: new_project, author: user, user: user, target: new_merge_request)
end
def create_issue_todo_for(user, project = nil)
new_project = project || create(:project, group: create(:group))
new_project.group.add_developer(user) if project.blank?
issue = create(:issue, project: new_project)
create(:todo, project: new_project, target: issue, author: user, user: user)
end
end end
describe 'POST /todos/:id/mark_as_done' do describe 'POST /todos/:id/mark_as_done' do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Todos::AllowedTargetFilterService do
include DesignManagementTestHelpers
let_it_be(:authorized_group) { create(:group, :private) }
let_it_be(:authorized_project) { create(:project, group: authorized_group) }
let_it_be(:unauthorized_group) { create(:group, :private) }
let_it_be(:unauthorized_project) { create(:project, group: unauthorized_group) }
let_it_be(:user) { create(:user) }
let_it_be(:authorized_issue) { create(:issue, project: authorized_project) }
let_it_be(:authorized_issue_todo) { create(:todo, project: authorized_project, target: authorized_issue, user: user) }
let_it_be(:unauthorized_issue) { create(:issue, project: unauthorized_project) }
let_it_be(:unauthorized_issue_todo) { create(:todo, project: unauthorized_project, target: unauthorized_issue, user: user) }
let_it_be(:authorized_design) { create(:design, issue: authorized_issue) }
let_it_be(:authorized_design_todo) { create(:todo, project: authorized_project, target: authorized_design, user: user) }
let_it_be(:unauthorized_design) { create(:design, issue: unauthorized_issue) }
let_it_be(:unauthorized_design_todo) { create(:todo, project: unauthorized_project, target: unauthorized_design, user: user) }
# Cannot use let_it_be with MRs
let(:authorized_mr) { create(:merge_request, source_project: authorized_project) }
let(:authorized_mr_todo) { create(:todo, project: authorized_project, user: user, target: authorized_mr) }
let(:unauthorized_mr) { create(:merge_request, source_project: unauthorized_project) }
let(:unauthorized_mr_todo) { create(:todo, project: unauthorized_project, user: user, target: unauthorized_mr) }
before_all do
authorized_group.add_developer(user)
end
describe '#execute' do
subject(:execute_service) { described_class.new(all_todos, user).execute }
let!(:all_todos) { authorized_todos + unauthorized_todos }
let(:authorized_todos) do
[
authorized_mr_todo,
authorized_issue_todo,
authorized_design_todo
]
end
let(:unauthorized_todos) do
[
unauthorized_mr_todo,
unauthorized_issue_todo,
unauthorized_design_todo
]
end
before do
enable_design_management
end
it { is_expected.to match_array(authorized_todos) }
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment