todo_service_spec.rb 50.3 KB
Newer Older
1 2
require 'spec_helper'

3
describe TodoService do
4
  let(:author) { create(:user) }
5 6 7
  let(:assignee) { create(:user) }
  let(:non_member) { create(:user) }
  let(:member) { create(:user) }
8
  let(:guest) { create(:user) }
9 10
  let(:admin) { create(:admin) }
  let(:john_doe) { create(:user) }
11 12
  let(:skipped) { create(:user) }
  let(:skip_users) { [skipped] }
13
  let(:project) { create(:project, :repository) }
14 15 16
  let(:mentions) { 'FYI: ' + [author, assignee, john_doe, member, guest, non_member, admin, skipped].map(&:to_reference).join(' ') }
  let(:directly_addressed) { [author, assignee, john_doe, member, guest, non_member, admin, skipped].map(&:to_reference).join(' ') }
  let(:directly_addressed_and_mentioned) { member.to_reference + ", what do you think? cc: " + [guest, admin, skipped].map(&:to_reference).join(' ') }
17 18 19
  let(:service) { described_class.new }

  before do
20 21
    project.add_guest(guest)
    project.add_developer(author)
22
    project.add_developer(assignee)
23 24 25
    project.add_developer(member)
    project.add_developer(john_doe)
    project.add_developer(skipped)
26 27 28
  end

  describe 'Issues' do
29 30 31 32 33
    let(:issue) { create(:issue, project: project, assignees: [john_doe], author: author, description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") }
    let(:addressed_issue) { create(:issue, project: project, assignees: [john_doe], author: author, description: "#{directly_addressed}\n- [ ] Task 1\n- [ ] Task 2") }
    let(:unassigned_issue) { create(:issue, project: project, assignees: []) }
    let(:confidential_issue) { create(:issue, :confidential, project: project, author: author, assignees: [assignee], description: mentions) }
    let(:addressed_confident_issue) { create(:issue, :confidential, project: project, author: author, assignees: [assignee], description: directly_addressed) }
34 35 36 37 38 39 40 41 42 43 44 45

    describe '#new_issue' do
      it 'creates a todo if assigned' do
        service.new_issue(issue, author)

        should_create_todo(user: john_doe, target: issue, action: Todo::ASSIGNED)
      end

      it 'does not create a todo if unassigned' do
        should_not_create_any_todo { service.new_issue(unassigned_issue, author) }
      end

46
      it 'creates a todo if assignee is the current user' do
47
        unassigned_issue.assignees = [john_doe]
48 49 50
        service.new_issue(unassigned_issue, john_doe)

        should_create_todo(user: john_doe, target: unassigned_issue, author: john_doe, action: Todo::ASSIGNED)
51 52 53 54 55
      end

      it 'creates a todo for each valid mentioned user' do
        service.new_issue(issue, author)

56
        should_create_todo(user: member, target: issue, action: Todo::MENTIONED)
57
        should_create_todo(user: guest, target: issue, action: Todo::MENTIONED)
58
        should_create_todo(user: author, target: issue, action: Todo::MENTIONED)
59
        should_not_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED)
60 61 62
        should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED)
      end

63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
      it 'creates a directly addressed todo for each valid addressed user' do
        service.new_issue(addressed_issue, author)

        should_create_todo(user: member, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: guest, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: author, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
        should_not_create_todo(user: john_doe, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
        should_not_create_todo(user: non_member, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
      end

      it 'creates correct todos for each valid user based on the type of mention' do
        issue.update(description: directly_addressed_and_mentioned)

        service.new_issue(issue, author)

        should_create_todo(user: member, target: issue, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: admin, target: issue, action: Todo::MENTIONED)
        should_create_todo(user: guest, target: issue, action: Todo::MENTIONED)
      end

83
      it 'does not create todo if user can not see the issue when issue is confidential' do
84 85 86 87 88 89
        service.new_issue(confidential_issue, john_doe)

        should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::ASSIGNED)
        should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
        should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
        should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
90
        should_not_create_todo(user: guest, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
91
        should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
92
      end
93

94 95 96 97 98 99 100 101 102 103 104
      it 'does not create directly addressed todo if user cannot see the issue when issue is confidential' do
        service.new_issue(addressed_confident_issue, john_doe)

        should_create_todo(user: assignee, target: addressed_confident_issue, author: john_doe, action: Todo::ASSIGNED)
        should_create_todo(user: author, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: member, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: admin, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
        should_not_create_todo(user: guest, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: john_doe, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
      end

105
      context 'when a private group is mentioned' do
106
        let(:group)   { create(:group, :private) }
107
        let(:project) { create(:project, :private, group: group) }
108
        let(:issue)   { create(:issue, author: author, project: project, description: group.to_reference) }
109 110 111 112 113 114 115 116 117 118 119 120 121 122

        before do
          group.add_owner(author)
          group.add_user(member, Gitlab::Access::DEVELOPER)
          group.add_user(john_doe, Gitlab::Access::DEVELOPER)

          service.new_issue(issue, author)
        end

        it 'creates a todo for group members' do
          should_create_todo(user: member, target: issue)
          should_create_todo(user: john_doe, target: issue)
        end
      end
123 124 125
    end

    describe '#update_issue' do
126 127
      it 'creates a todo for each valid mentioned user not included in skip_users' do
        service.update_issue(issue, author, skip_users)
128

129
        should_create_todo(user: member, target: issue, action: Todo::MENTIONED)
130
        should_create_todo(user: guest, target: issue, action: Todo::MENTIONED)
131
        should_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED)
132
        should_create_todo(user: author, target: issue, action: Todo::MENTIONED)
133
        should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED)
134
        should_not_create_todo(user: skipped, target: issue, action: Todo::MENTIONED)
135 136
      end

137
      it 'creates a todo for each valid user not included in skip_users based on the type of mention' do
138 139
        issue.update(description: directly_addressed_and_mentioned)

140
        service.update_issue(issue, author, skip_users)
141 142 143 144

        should_create_todo(user: member, target: issue, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: guest, target: issue, action: Todo::MENTIONED)
        should_create_todo(user: admin, target: issue, action: Todo::MENTIONED)
145
        should_not_create_todo(user: skipped, target: issue)
146 147
      end

148 149
      it 'creates a directly addressed todo for each valid addressed user not included in skip_users' do
        service.update_issue(addressed_issue, author, skip_users)
150 151 152 153 154 155

        should_create_todo(user: member, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: guest, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: john_doe, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: author, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
        should_not_create_todo(user: non_member, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
156
        should_not_create_todo(user: skipped, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
157 158
      end

159
      it 'does not create a todo if user was already mentioned and todo is pending' do
160
        create(:todo, :mentioned, user: member, project: project, target: issue, author: author)
161

162 163 164 165 166 167 168
        expect { service.update_issue(issue, author, skip_users) }.not_to change(member.todos, :count)
      end

      it 'does not create a todo if user was already mentioned and todo is done' do
        create(:todo, :mentioned, :done, user: skipped, project: project, target: issue, author: author)

        expect { service.update_issue(issue, author, skip_users) }.not_to change(skipped.todos, :count)
169 170
      end

171
      it 'does not create a directly addressed todo if user was already mentioned or addressed and todo is pending' do
172 173
        create(:todo, :directly_addressed, user: member, project: project, target: addressed_issue, author: author)

174 175 176 177 178 179 180
        expect { service.update_issue(addressed_issue, author, skip_users) }.not_to change(member.todos, :count)
      end

      it 'does not create a directly addressed todo if user was already mentioned or addressed and todo is done' do
        create(:todo, :directly_addressed, :done, user: skipped, project: project, target: addressed_issue, author: author)

        expect { service.update_issue(addressed_issue, author, skip_users) }.not_to change(skipped.todos, :count)
181 182
      end

183
      it 'does not create todo if user can not see the issue when issue is confidential' do
184 185 186 187 188 189
        service.update_issue(confidential_issue, john_doe)

        should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
        should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
        should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
        should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
190
        should_not_create_todo(user: guest, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
191
        should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
192
      end
193

194 195 196 197 198 199 200 201 202 203 204
      it 'does not create a directly addressed todo if user can not see the issue when issue is confidential' do
        service.update_issue(addressed_confident_issue, john_doe)

        should_create_todo(user: author, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: assignee, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: member, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: admin, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
        should_not_create_todo(user: guest, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: john_doe, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
      end

205 206 207 208 209 210 211 212 213 214 215 216 217
      context 'issues with a task list' do
        it 'does not create todo when tasks are marked as completed' do
          issue.update(description: "- [x] Task 1\n- [X] Task 2 #{mentions}")

          service.update_issue(issue, author)

          should_not_create_todo(user: admin, target: issue, action: Todo::MENTIONED)
          should_not_create_todo(user: assignee, target: issue, action: Todo::MENTIONED)
          should_not_create_todo(user: author, target: issue, action: Todo::MENTIONED)
          should_not_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED)
          should_not_create_todo(user: member, target: issue, action: Todo::MENTIONED)
          should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED)
        end
218

219 220 221 222 223 224 225 226 227 228 229 230 231
        it 'does not create directly addressed todo when tasks are marked as completed' do
          addressed_issue.update(description: "#{directly_addressed}\n- [x] Task 1\n- [x] Task 2\n")

          service.update_issue(addressed_issue, author)

          should_not_create_todo(user: admin, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
          should_not_create_todo(user: assignee, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
          should_not_create_todo(user: author, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
          should_not_create_todo(user: john_doe, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
          should_not_create_todo(user: member, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
          should_not_create_todo(user: non_member, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
        end

232 233
        it 'does not raise an error when description not change' do
          issue.update(title: 'Sample')
234

235 236
          expect { service.update_issue(issue, author) }.not_to raise_error
        end
237
      end
238 239 240 241 242 243 244 245 246 247 248 249 250 251
    end

    describe '#close_issue' do
      it 'marks related pending todos to the target for the user as done' do
        first_todo = create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author)
        second_todo = create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author)

        service.close_issue(issue, john_doe)

        expect(first_todo.reload).to be_done
        expect(second_todo.reload).to be_done
      end
    end

252 253 254
    describe '#destroy_target' do
      it 'refreshes the todos count cache for users with todos on the target' do
        create(:todo, target: issue, user: john_doe, author: john_doe, project: issue.project)
255

256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
        expect_any_instance_of(User).to receive(:update_todos_count_cache).and_call_original

        service.destroy_target(issue) { }
      end

      it 'does not refresh the todos count cache for users with only done todos on the target' do
        create(:todo, :done, target: issue, user: john_doe, author: john_doe, project: issue.project)

        expect_any_instance_of(User).not_to receive(:update_todos_count_cache)

        service.destroy_target(issue) { }
      end

      it 'yields the target to the caller' do
        expect { |b| service.destroy_target(issue, &b) }
          .to yield_with_args(issue)
272 273 274
      end
    end

275 276
    describe '#reassigned_issue' do
      it 'creates a pending todo for new assignee' do
277
        unassigned_issue.assignees << john_doe
278 279 280 281 282 283
        service.reassigned_issue(unassigned_issue, author)

        should_create_todo(user: john_doe, target: unassigned_issue, action: Todo::ASSIGNED)
      end

      it 'does not create a todo if unassigned' do
284
        issue.assignees.destroy_all # rubocop: disable DestroyAll
285 286 287 288

        should_not_create_any_todo { service.reassigned_issue(issue, author) }
      end

289
      it 'creates a todo if new assignee is the current user' do
290
        unassigned_issue.assignees << john_doe
291
        service.reassigned_issue(unassigned_issue, john_doe)
292

293
        should_create_todo(user: john_doe, target: unassigned_issue, author: john_doe, action: Todo::ASSIGNED)
294 295 296 297 298 299 300 301 302 303 304 305 306
      end
    end

    describe '#mark_pending_todos_as_done' do
      it 'marks related pending todos to the target for the user as done' do
        first_todo = create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author)
        second_todo = create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author)

        service.mark_pending_todos_as_done(issue, john_doe)

        expect(first_todo.reload).to be_done
        expect(second_todo.reload).to be_done
      end
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323

      describe 'cached counts' do
        it 'updates when todos change' do
          create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author)

          expect(john_doe.todos_done_count).to eq(0)
          expect(john_doe.todos_pending_count).to eq(1)
          expect(john_doe).to receive(:update_todos_count_cache).and_call_original

          service.mark_pending_todos_as_done(issue, john_doe)

          expect(john_doe.todos_done_count).to eq(1)
          expect(john_doe.todos_pending_count).to eq(0)
        end
      end
    end

324 325 326
    shared_examples 'updating todos state' do |meth, state, new_state|
      let!(:first_todo) { create(:todo, state, user: john_doe, project: project, target: issue, author: author) }
      let!(:second_todo) { create(:todo, state, user: john_doe, project: project, target: issue, author: author) }
327

328
      it 'updates related todos for the user with the new_state' do
329
        service.send(meth, collection, john_doe)
330

331 332
        expect(first_todo.reload.state?(new_state)).to be true
        expect(second_todo.reload.state?(new_state)).to be true
333 334
      end

335 336 337 338
      it 'returns the updated ids' do
        expect(service.send(meth, collection, john_doe)).to match_array([first_todo.id, second_todo.id])
      end

339 340
      describe 'cached counts' do
        it 'updates when todos change' do
341 342
          expect(john_doe.todos.where(state: new_state).count).to eq(0)
          expect(john_doe.todos.where(state: state).count).to eq(2)
343 344
          expect(john_doe).to receive(:update_todos_count_cache).and_call_original

345
          service.send(meth, collection, john_doe)
346

347 348
          expect(john_doe.todos.where(state: new_state).count).to eq(2)
          expect(john_doe.todos.where(state: state).count).to eq(0)
349 350
        end
      end
351 352
    end

353
    describe '#mark_todos_as_done' do
354
      it_behaves_like 'updating todos state', :mark_todos_as_done, :pending, :done do
355
        let(:collection) { Todo.all }
356 357 358
      end
    end

359
    describe '#mark_todos_as_done_by_ids' do
360 361 362 363 364 365 366
      it_behaves_like 'updating todos state', :mark_todos_as_done_by_ids, :pending, :done do
        let(:collection) { [first_todo, second_todo].map(&:id) }
      end
    end

    describe '#mark_todos_as_pending' do
      it_behaves_like 'updating todos state', :mark_todos_as_pending, :done, :pending do
367
        let(:collection) { Todo.all }
368 369 370 371 372
      end
    end

    describe '#mark_todos_as_pending_by_ids' do
      it_behaves_like 'updating todos state', :mark_todos_as_pending_by_ids, :done, :pending do
373 374 375 376
        let(:collection) { [first_todo, second_todo].map(&:id) }
      end
    end

377 378 379
    describe '#new_note' do
      let!(:first_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) }
      let!(:second_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) }
380
      let(:confidential_issue) { create(:issue, :confidential, project: project, author: author, assignees: [assignee]) }
381
      let(:note) { create(:note, project: project, noteable: issue, author: john_doe, note: mentions) }
382
      let(:addressed_note) { create(:note, project: project, noteable: issue, author: john_doe, note: directly_addressed) }
383
      let(:note_on_commit) { create(:note_on_commit, project: project, author: john_doe, note: mentions) }
384
      let(:addressed_note_on_commit) { create(:note_on_commit, project: project, author: john_doe, note: directly_addressed) }
385
      let(:note_on_confidential_issue) { create(:note_on_issue, noteable: confidential_issue, project: project, note: mentions) }
386
      let(:addressed_note_on_confidential_issue) { create(:note_on_issue, noteable: confidential_issue, project: project, note: directly_addressed) }
387
      let(:note_on_project_snippet) { create(:note_on_project_snippet, project: project, author: john_doe, note: mentions) }
388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409
      let(:system_note) { create(:system_note, project: project, noteable: issue) }

      it 'mark related pending todos to the noteable for the note author as done' do
        first_todo = create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author)
        second_todo = create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author)

        service.new_note(note, john_doe)

        expect(first_todo.reload).to be_done
        expect(second_todo.reload).to be_done
      end

      it 'does not mark related pending todos it is a system note' do
        service.new_note(system_note, john_doe)

        expect(first_todo.reload).to be_pending
        expect(second_todo.reload).to be_pending
      end

      it 'creates a todo for each valid mentioned user' do
        service.new_note(note, john_doe)

410
        should_create_todo(user: member, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
411
        should_create_todo(user: guest, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
412
        should_create_todo(user: author, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
413
        should_create_todo(user: john_doe, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
414 415 416
        should_not_create_todo(user: non_member, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
      end

417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436
      it 'creates a todo for each valid user based on the type of mention' do
        note.update(note: directly_addressed_and_mentioned)

        service.new_note(note, john_doe)

        should_create_todo(user: member, target: issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: note)
        should_create_todo(user: admin, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
        should_create_todo(user: guest, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
      end

      it 'creates a directly addressed todo for each valid addressed user' do
        service.new_note(addressed_note, john_doe)

        should_create_todo(user: member, target: issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note)
        should_create_todo(user: guest, target: issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note)
        should_create_todo(user: author, target: issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note)
        should_create_todo(user: john_doe, target: issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note)
        should_not_create_todo(user: non_member, target: issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note)
      end

437
      it 'does not create todo if user can not see the issue when leaving a note on a confidential issue' do
438 439 440 441 442 443
        service.new_note(note_on_confidential_issue, john_doe)

        should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
        should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
        should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
        should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
444
        should_not_create_todo(user: guest, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
445
        should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
446
      end
447

448 449 450 451 452 453 454 455 456 457 458
      it 'does not create a directly addressed todo if user can not see the issue when leaving a note on a confidential issue' do
        service.new_note(addressed_note_on_confidential_issue, john_doe)

        should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
        should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
        should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
        should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
        should_not_create_todo(user: guest, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
        should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
      end

459
      context 'on commit' do
460
        let(:project) { create(:project, :repository) }
461

462 463 464 465 466 467 468 469
        it 'creates a todo for each valid mentioned user when leaving a note on commit' do
          service.new_note(note_on_commit, john_doe)

          should_create_todo(user: member, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
          should_create_todo(user: author, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
          should_create_todo(user: john_doe, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
          should_not_create_todo(user: non_member, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
        end
470

471 472
        it 'creates a directly addressed todo for each valid mentioned user when leaving a note on commit' do
          service.new_note(addressed_note_on_commit, john_doe)
473

474 475 476 477 478
          should_create_todo(user: member, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit)
          should_create_todo(user: author, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit)
          should_create_todo(user: john_doe, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit)
          should_not_create_todo(user: non_member, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit)
        end
479 480
      end

481 482 483
      it 'does not create todo when leaving a note on snippet' do
        should_not_create_any_todo { service.new_note(note_on_project_snippet, john_doe) }
      end
484
    end
Phil Hughes's avatar
Phil Hughes committed
485 486 487 488 489 490 491 492

    describe '#mark_todo' do
      it 'creates a todo from a issue' do
        service.mark_todo(unassigned_issue, author)

        should_create_todo(user: author, target: unassigned_issue, action: Todo::MARKED)
      end
    end
493 494 495 496 497 498 499 500 501 502 503 504

    describe '#todo_exists?' do
      it 'returns false when no todo exist for the given issuable' do
        expect(service.todo_exist?(unassigned_issue, author)).to be_falsy
      end

      it 'returns true when a todo exist for the given issuable' do
        service.mark_todo(unassigned_issue, author)

        expect(service.todo_exist?(unassigned_issue, author)).to be_truthy
      end
    end
505 506 507
  end

  describe 'Merge Requests' do
508
    let(:mr_assigned) { create(:merge_request, source_project: project, author: author, assignee: john_doe, description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") }
509
    let(:addressed_mr_assigned) { create(:merge_request, source_project: project, author: author, assignee: john_doe, description: "#{directly_addressed}\n- [ ] Task 1\n- [ ] Task 2") }
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529
    let(:mr_unassigned) { create(:merge_request, source_project: project, author: author, assignee: nil) }

    describe '#new_merge_request' do
      it 'creates a pending todo if assigned' do
        service.new_merge_request(mr_assigned, author)

        should_create_todo(user: john_doe, target: mr_assigned, action: Todo::ASSIGNED)
      end

      it 'does not create a todo if unassigned' do
        should_not_create_any_todo { service.new_merge_request(mr_unassigned, author) }
      end

      it 'does not create a todo if assignee is the current user' do
        should_not_create_any_todo { service.new_merge_request(mr_unassigned, john_doe) }
      end

      it 'creates a todo for each valid mentioned user' do
        service.new_merge_request(mr_assigned, author)

530
        should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED)
531
        should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED)
532
        should_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED)
533
        should_not_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED)
534
        should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED)
535
      end
536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554

      it 'creates a todo for each valid user based on the type of mention' do
        mr_assigned.update(description: directly_addressed_and_mentioned)

        service.new_merge_request(mr_assigned, author)

        should_create_todo(user: member, target: mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: admin, target: mr_assigned, action: Todo::MENTIONED)
      end

      it 'creates a directly addressed todo for each valid addressed user' do
        service.new_merge_request(addressed_mr_assigned, author)

        should_create_todo(user: member, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
        should_not_create_todo(user: guest, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: author, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
        should_not_create_todo(user: john_doe, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
        should_not_create_todo(user: non_member, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
      end
555 556 557
    end

    describe '#update_merge_request' do
558 559
      it 'creates a todo for each valid mentioned user not included in skip_users' do
        service.update_merge_request(mr_assigned, author, skip_users)
560

561
        should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED)
562
        should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED)
563
        should_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED)
564
        should_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED)
565
        should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED)
566
        should_not_create_todo(user: skipped, target: mr_assigned, action: Todo::MENTIONED)
567 568
      end

569
      it 'creates a todo for each valid user not included in skip_users based on the type of mention' do
570 571
        mr_assigned.update(description: directly_addressed_and_mentioned)

572
        service.update_merge_request(mr_assigned, author, skip_users)
573 574 575

        should_create_todo(user: member, target: mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: admin, target: mr_assigned, action: Todo::MENTIONED)
576
        should_not_create_todo(user: skipped, target: mr_assigned)
577 578
      end

579 580
      it 'creates a directly addressed todo for each valid addressed user not included in skip_users' do
        service.update_merge_request(addressed_mr_assigned, author, skip_users)
581 582 583 584 585 586

        should_create_todo(user: member, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
        should_not_create_todo(user: guest, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: john_doe, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: author, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
        should_not_create_todo(user: non_member, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
587
        should_not_create_todo(user: skipped, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
588 589
      end

590
      it 'does not create a todo if user was already mentioned and todo is pending' do
591
        create(:todo, :mentioned, user: member, project: project, target: mr_assigned, author: author)
592

593
        expect { service.update_merge_request(mr_assigned, author) }.not_to change(member.todos, :count)
594
      end
595

596 597 598 599 600 601 602
      it 'does not create a todo if user was already mentioned and todo is done' do
        create(:todo, :mentioned, :done, user: skipped, project: project, target: mr_assigned, author: author)

        expect { service.update_merge_request(mr_assigned, author, skip_users) }.not_to change(skipped.todos, :count)
      end

      it 'does not create a directly addressed todo if user was already mentioned or addressed and todo is pending' do
603 604
        create(:todo, :directly_addressed, user: member, project: project, target: addressed_mr_assigned, author: author)

605
        expect { service.update_merge_request(addressed_mr_assigned, author) }.not_to change(member.todos, :count)
606 607
      end

608 609 610
      it 'does not create a directly addressed todo if user was already mentioned or addressed and todo is done' do
        create(:todo, :directly_addressed, user: skipped, project: project, target: addressed_mr_assigned, author: author)

611
        expect { service.update_merge_request(addressed_mr_assigned, author, skip_users) }.not_to change(skipped.todos, :count)
612 613
      end

614 615 616
      context 'with a task list' do
        it 'does not create todo when tasks are marked as completed' do
          mr_assigned.update(description: "- [x] Task 1\n- [X] Task 2 #{mentions}")
617

618
          service.update_merge_request(mr_assigned, author)
619

620 621 622 623 624 625
          should_not_create_todo(user: admin, target: mr_assigned, action: Todo::MENTIONED)
          should_not_create_todo(user: assignee, target: mr_assigned, action: Todo::MENTIONED)
          should_not_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED)
          should_not_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED)
          should_not_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED)
          should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED)
626
          should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED)
627 628
        end

629 630 631 632 633 634 635 636 637 638 639 640 641 642
        it 'does not create directly addressed todo when tasks are marked as completed' do
          addressed_mr_assigned.update(description: "#{directly_addressed}\n- [x] Task 1\n- [X] Task 2")

          service.update_merge_request(addressed_mr_assigned, author)

          should_not_create_todo(user: admin, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
          should_not_create_todo(user: assignee, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
          should_not_create_todo(user: author, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
          should_not_create_todo(user: john_doe, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
          should_not_create_todo(user: member, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
          should_not_create_todo(user: non_member, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
          should_not_create_todo(user: guest, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
        end

643 644 645 646 647
        it 'does not raise an error when description not change' do
          mr_assigned.update(title: 'Sample')

          expect { service.update_merge_request(mr_assigned, author) }.not_to raise_error
        end
648
      end
649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675
    end

    describe '#close_merge_request' do
      it 'marks related pending todos to the target for the user as done' do
        first_todo = create(:todo, :assigned, user: john_doe, project: project, target: mr_assigned, author: author)
        second_todo = create(:todo, :assigned, user: john_doe, project: project, target: mr_assigned, author: author)
        service.close_merge_request(mr_assigned, john_doe)

        expect(first_todo.reload).to be_done
        expect(second_todo.reload).to be_done
      end
    end

    describe '#reassigned_merge_request' do
      it 'creates a pending todo for new assignee' do
        mr_unassigned.update_attribute(:assignee, john_doe)
        service.reassigned_merge_request(mr_unassigned, author)

        should_create_todo(user: john_doe, target: mr_unassigned, action: Todo::ASSIGNED)
      end

      it 'does not create a todo if unassigned' do
        mr_assigned.update_attribute(:assignee, nil)

        should_not_create_any_todo { service.reassigned_merge_request(mr_assigned, author) }
      end

676
      it 'creates a todo if new assignee is the current user' do
677
        mr_assigned.update_attribute(:assignee, john_doe)
678
        service.reassigned_merge_request(mr_assigned, john_doe)
679

680
        should_create_todo(user: john_doe, target: mr_assigned, author: john_doe, action: Todo::ASSIGNED)
681
      end
682 683 684 685 686

      it 'does not create a todo for guests' do
        service.reassigned_merge_request(mr_assigned, author)
        should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED)
      end
687 688 689 690 691

      it 'does not create a directly addressed todo for guests' do
        service.reassigned_merge_request(addressed_mr_assigned, author)
        should_not_create_todo(user: guest, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
      end
692 693 694 695 696 697 698 699 700 701 702
    end

    describe '#merge_merge_request' do
      it 'marks related pending todos to the target for the user as done' do
        first_todo = create(:todo, :assigned, user: john_doe, project: project, target: mr_assigned, author: author)
        second_todo = create(:todo, :assigned, user: john_doe, project: project, target: mr_assigned, author: author)
        service.merge_merge_request(mr_assigned, john_doe)

        expect(first_todo.reload).to be_done
        expect(second_todo.reload).to be_done
      end
703 704 705 706 707

      it 'does not create todo for guests' do
        service.merge_merge_request(mr_assigned, john_doe)
        should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED)
      end
708 709 710 711 712

      it 'does not create directly addressed todo for guests' do
        service.merge_merge_request(addressed_mr_assigned, john_doe)
        should_not_create_todo(user: guest, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
      end
713
    end
714 715 716 717 718 719 720 721 722

    describe '#new_award_emoji' do
      it 'marks related pending todos to the target for the user as done' do
        todo = create(:todo, user: john_doe, project: project, target: mr_assigned, author: author)
        service.new_award_emoji(mr_assigned, john_doe)

        expect(todo.reload).to be_done
      end
    end
723

724
    describe '#merge_request_build_failed' do
725
      let(:merge_participants) { [mr_unassigned.author, admin] }
726

727 728
      before do
        allow(mr_unassigned).to receive(:merge_participants).and_return(merge_participants)
729
      end
730

731
      it 'creates a pending todo for each merge_participant' do
732 733
        service.merge_request_build_failed(mr_unassigned)

734 735 736
        merge_participants.each do |participant|
          should_create_todo(user: participant, author: participant, target: mr_unassigned, action: Todo::BUILD_FAILED)
        end
737
      end
738 739 740 741 742 743 744 745 746 747 748 749
    end

    describe '#merge_request_push' do
      it 'marks related pending todos to the target for the user as done' do
        first_todo = create(:todo, :build_failed, user: author, project: project, target: mr_assigned, author: john_doe)
        second_todo = create(:todo, :build_failed, user: john_doe, project: project, target: mr_assigned, author: john_doe)
        service.merge_request_push(mr_assigned, author)

        expect(first_todo.reload).to be_done
        expect(second_todo.reload).not_to be_done
      end
    end
Phil Hughes's avatar
Phil Hughes committed
750

751
    describe '#merge_request_became_unmergeable' do
752 753 754 755 756 757 758
      let(:merge_participants) { [admin, create(:user)] }

      before do
        allow(mr_unassigned).to receive(:merge_participants).and_return(merge_participants)
      end

      it 'creates a pending todo for each merge_participant' do
759
        mr_unassigned.update(merge_when_pipeline_succeeds: true, merge_user: admin)
760 761
        service.merge_request_became_unmergeable(mr_unassigned)

762 763 764
        merge_participants.each do |participant|
          should_create_todo(user: participant, author: participant, target: mr_unassigned, action: Todo::UNMERGEABLE)
        end
765 766
      end
    end
767

Phil Hughes's avatar
Phil Hughes committed
768 769 770 771 772 773 774
    describe '#mark_todo' do
      it 'creates a todo from a merge request' do
        service.mark_todo(mr_unassigned, author)

        should_create_todo(user: author, target: mr_unassigned, action: Todo::MARKED)
      end
    end
775 776

    describe '#new_note' do
777
      let(:project) { create(:project, :repository) }
778 779
      let(:mention) { john_doe.to_reference }
      let(:diff_note_on_merge_request) { create(:diff_note_on_merge_request, project: project, noteable: mr_unassigned, author: author, note: "Hey #{mention}") }
780
      let(:addressed_diff_note_on_merge_request) { create(:diff_note_on_merge_request, project: project, noteable: mr_unassigned, author: author, note: "#{mention}, hey!") }
781 782 783 784 785 786 787 788
      let(:legacy_diff_note_on_merge_request) { create(:legacy_diff_note_on_merge_request, project: project, noteable: mr_unassigned, author: author, note: "Hey #{mention}") }

      it 'creates a todo for mentioned user on new diff note' do
        service.new_note(diff_note_on_merge_request, author)

        should_create_todo(user: john_doe, target: mr_unassigned, author: author, action: Todo::MENTIONED, note: diff_note_on_merge_request)
      end

789 790 791 792 793 794
      it 'creates a directly addressed todo for addressed user on new diff note' do
        service.new_note(addressed_diff_note_on_merge_request, author)

        should_create_todo(user: john_doe, target: mr_unassigned, author: author, action: Todo::DIRECTLY_ADDRESSED, note: addressed_diff_note_on_merge_request)
      end

795 796 797 798 799
      it 'creates a todo for mentioned user on legacy diff note' do
        service.new_note(legacy_diff_note_on_merge_request, author)

        should_create_todo(user: john_doe, target: mr_unassigned, author: author, action: Todo::MENTIONED, note: legacy_diff_note_on_merge_request)
      end
800 801 802 803 804 805 806

      it 'does not create todo for guests' do
        note_on_merge_request = create :note_on_merge_request, project: project, noteable: mr_assigned, note: mentions
        service.new_note(note_on_merge_request, author)

        should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED)
      end
807
    end
808 809
  end

810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872
  describe '#update_note' do
    let(:noteable) { create(:issue, project: project) }
    let(:note) { create(:note, project: project, note: mentions, noteable: noteable) }
    let(:addressed_note) { create(:note, project: project, note: "#{directly_addressed}", noteable: noteable) }

    it 'creates a todo for each valid mentioned user not included in skip_users' do
      service.update_note(note, author, skip_users)

      should_create_todo(user: member, target: noteable, action: Todo::MENTIONED)
      should_create_todo(user: guest, target: noteable, action: Todo::MENTIONED)
      should_create_todo(user: john_doe, target: noteable, action: Todo::MENTIONED)
      should_create_todo(user: author, target: noteable, action: Todo::MENTIONED)
      should_not_create_todo(user: non_member, target: noteable, action: Todo::MENTIONED)
      should_not_create_todo(user: skipped, target: noteable, action: Todo::MENTIONED)
    end

    it 'creates a todo for each valid user not included in skip_users based on the type of mention' do
      note.update(note: directly_addressed_and_mentioned)

      service.update_note(note, author, skip_users)

      should_create_todo(user: member, target: noteable, action: Todo::DIRECTLY_ADDRESSED)
      should_create_todo(user: guest, target: noteable, action: Todo::MENTIONED)
      should_create_todo(user: admin, target: noteable, action: Todo::MENTIONED)
      should_not_create_todo(user: skipped, target: noteable)
    end

    it 'creates a directly addressed todo for each valid addressed user not included in skip_users' do
      service.update_note(addressed_note, author, skip_users)

      should_create_todo(user: member, target: noteable, action: Todo::DIRECTLY_ADDRESSED)
      should_create_todo(user: guest, target: noteable, action: Todo::DIRECTLY_ADDRESSED)
      should_create_todo(user: john_doe, target: noteable, action: Todo::DIRECTLY_ADDRESSED)
      should_create_todo(user: author, target: noteable, action: Todo::DIRECTLY_ADDRESSED)
      should_not_create_todo(user: non_member, target: noteable, action: Todo::DIRECTLY_ADDRESSED)
      should_not_create_todo(user: skipped, target: noteable, action: Todo::DIRECTLY_ADDRESSED)
    end

    it 'does not create a todo if user was already mentioned and todo is pending' do
      create(:todo, :mentioned, user: member, project: project, target: noteable, author: author)

      expect { service.update_note(note, author, skip_users) }.not_to change(member.todos, :count)
    end

    it 'does not create a todo if user was already mentioned and todo is done' do
      create(:todo, :mentioned, :done, user: skipped, project: project, target: noteable, author: author)

      expect { service.update_note(note, author, skip_users) }.not_to change(skipped.todos, :count)
    end

    it 'does not create a directly addressed todo if user was already mentioned or addressed and todo is pending' do
      create(:todo, :directly_addressed, user: member, project: project, target: noteable, author: author)

      expect { service.update_note(addressed_note, author, skip_users) }.not_to change(member.todos, :count)
    end

    it 'does not create a directly addressed todo if user was already mentioned or addressed and todo is done' do
      create(:todo, :directly_addressed, :done, user: skipped, project: project, target: noteable, author: author)

      expect { service.update_note(addressed_note, author, skip_users) }.not_to change(skipped.todos, :count)
    end
  end

873
  it 'updates cached counts when a todo is created' do
874
    issue = create(:issue, project: project, assignees: [john_doe], author: author, description: mentions)
875 876

    expect(john_doe.todos_pending_count).to eq(0)
877
    expect(john_doe).to receive(:update_todos_count_cache).and_call_original
878 879 880 881 882 883 884

    service.new_issue(issue, author)

    expect(Todo.where(user_id: john_doe.id, state: :pending).count).to eq 1
    expect(john_doe.todos_pending_count).to eq(1)
  end

885
  describe '#mark_todos_as_done' do
886 887
    let(:issue) { create(:issue, project: project, author: author, assignees: [john_doe]) }
    let(:another_issue) { create(:issue, project: project, author: author, assignees: [john_doe]) }
888 889 890 891 892

    it 'marks a relation of todos as done' do
      create(:todo, :mentioned, user: john_doe, target: issue, project: project)

      todos = TodosFinder.new(john_doe, {}).execute
893
      expect { described_class.new.mark_todos_as_done(todos, john_doe) }
894 895 896
       .to change { john_doe.todos.done.count }.from(0).to(1)
    end

897 898 899
    it 'marks an array of todos as done' do
      todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project)

900 901
      todos = TodosFinder.new(john_doe, {}).execute
      expect { described_class.new.mark_todos_as_done(todos, john_doe) }
902 903 904
        .to change { todo.reload.state }.from('pending').to('done')
    end

905
    it 'returns the ids of updated todos' do # Needed on API
906 907
      todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project)

908 909
      todos = TodosFinder.new(john_doe, {}).execute
      expect(described_class.new.mark_todos_as_done(todos, john_doe)).to eq([todo.id])
910 911
    end

912
    context 'when some of the todos are done already' do
913 914
      let!(:first_todo) { create(:todo, :mentioned, user: john_doe, target: issue, project: project) }
      let!(:second_todo) { create(:todo, :mentioned, user: john_doe, target: another_issue, project: project) }
915

916
      it 'returns the ids of those still pending' do
917
        described_class.new.mark_pending_todos_as_done(issue, john_doe)
918

919
        expect(described_class.new.mark_todos_as_done(Todo.all, john_doe)).to eq([second_todo.id])
920 921
      end

922
      it 'returns an empty array if all are done' do
923 924
        described_class.new.mark_pending_todos_as_done(issue, john_doe)
        described_class.new.mark_pending_todos_as_done(another_issue, john_doe)
925

926
        expect(described_class.new.mark_todos_as_done(Todo.all, john_doe)).to eq([])
927 928
      end
    end
929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948
  end

  describe '#mark_todos_as_done_by_ids' do
    let(:issue) { create(:issue, project: project, author: author, assignees: [john_doe]) }
    let(:another_issue) { create(:issue, project: project, author: author, assignees: [john_doe]) }

    it 'marks an array of todo ids as done' do
      todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project)
      another_todo = create(:todo, :mentioned, user: john_doe, target: another_issue, project: project)

      expect { described_class.new.mark_todos_as_done_by_ids([todo.id, another_todo.id], john_doe) }
        .to change { john_doe.todos.done.count }.from(0).to(2)
    end

    it 'marks a single todo id as done' do
      todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project)

      expect { described_class.new.mark_todos_as_done_by_ids(todo.id, john_doe) }
        .to change { todo.reload.state }.from('pending').to('done')
    end
949

950
    it 'caches the number of todos of a user', :use_clean_rails_memory_store_caching do
951 952
      create(:todo, :mentioned, user: john_doe, target: issue, project: project)
      todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project)
953 954

      described_class.new.mark_todos_as_done_by_ids(todo, john_doe)
955

956 957
      # Make sure no TodosFinder is inialized to perform counting
      expect(TodosFinder).not_to receive(:new)
958 959 960 961 962 963

      expect(john_doe.todos_done_count).to eq(1)
      expect(john_doe.todos_pending_count).to eq(1)
    end
  end

964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987
  def should_create_todo(attributes = {})
    attributes.reverse_merge!(
      project: project,
      author: author,
      state: :pending
    )

    expect(Todo.where(attributes).count).to eq 1
  end

  def should_not_create_todo(attributes = {})
    attributes.reverse_merge!(
      project: project,
      author: author,
      state: :pending
    )

    expect(Todo.where(attributes).count).to eq 0
  end

  def should_not_create_any_todo
    expect { yield }.not_to change(Todo, :count)
  end
end