issuable_spec.rb 17.0 KB
Newer Older
1 2
require 'spec_helper'

S
Sean McGivern 已提交
3 4
describe Issuable do
  let(:issuable_class) { Issue }
5
  let(:issue) { create(:issue) }
6
  let(:user) { create(:user) }
7 8

  describe "Associations" do
S
Sean McGivern 已提交
9 10
    subject { build(:issue) }

11 12 13 14
    it { is_expected.to belong_to(:project) }
    it { is_expected.to belong_to(:author) }
    it { is_expected.to belong_to(:assignee) }
    it { is_expected.to have_many(:notes).dependent(:destroy) }
15
    it { is_expected.to have_many(:todos).dependent(:destroy) }
16 17 18 19 20 21 22 23 24 25

    context 'Notes' do
      let!(:note) { create(:note, noteable: issue, project: issue.project) }
      let(:scoped_issue) { Issue.includes(notes: :author).find(issue.id) }

      it 'indicates if the notes have their authors loaded' do
        expect(issue.notes).not_to be_authors_loaded
        expect(scoped_issue.notes).to be_authors_loaded
      end
    end
26 27
  end

28
  describe 'Included modules' do
S
Sean McGivern 已提交
29 30
    let(:described_class) { issuable_class }

31 32 33
    it { is_expected.to include_module(Awardable) }
  end

34
  describe "Validation" do
S
Sean McGivern 已提交
35 36
    subject { build(:issue) }

37 38 39 40
    before do
      allow(subject).to receive(:set_iid).and_return(false)
    end

41 42 43 44
    it { is_expected.to validate_presence_of(:project) }
    it { is_expected.to validate_presence_of(:iid) }
    it { is_expected.to validate_presence_of(:author) }
    it { is_expected.to validate_presence_of(:title) }
45
    it { is_expected.to validate_length_of(:title).is_at_most(255) }
46 47 48
  end

  describe "Scope" do
S
Sean McGivern 已提交
49 50 51 52 53
    subject { build(:issue) }

    it { expect(issuable_class).to respond_to(:opened) }
    it { expect(issuable_class).to respond_to(:closed) }
    it { expect(issuable_class).to respond_to(:assigned) }
54 55
  end

56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
  describe 'author_name' do
    it 'is delegated to author' do
      expect(issue.author_name).to eq issue.author.name
    end

    it 'returns nil when author is nil' do
      issue.author_id = nil
      issue.save(validate: false)

      expect(issue.author_name).to eq nil
    end
  end

  describe 'assignee_name' do
    it 'is delegated to assignee' do
      issue.update!(assignee: create(:user))

      expect(issue.assignee_name).to eq issue.assignee.name
    end

    it 'returns nil when assignee is nil' do
      issue.assignee_id = nil
      issue.save(validate: false)

      expect(issue.assignee_name).to eq nil
    end
  end

84
  describe "before_save" do
85 86
    describe "#update_cache_counts" do
      context "when previous assignee exists" do
87 88 89 90 91 92 93 94 95
        before do
          assignee = create(:user)
          issue.project.team << [assignee, :developer]
          issue.update(assignee: assignee)
        end

        it "updates cache counts for new assignee" do
          user = create(:user)

96 97 98 99
          expect(user).to receive(:update_cache_counts)

          issue.update(assignee: user)
        end
100 101 102 103 104 105 106 107 108

        it "updates cache counts for previous assignee" do
          old_assignee = issue.assignee
          allow(User).to receive(:find_by_id).with(old_assignee.id).and_return(old_assignee)

          expect(old_assignee).to receive(:update_cache_counts)

          issue.update(assignee: nil)
        end
109 110 111
      end

      context "when previous assignee does not exist" do
112
        before{ issue.update(assignee: nil) }
113

114 115 116 117
        it "updates cache count for the new assignee" do
          expect_any_instance_of(User).to receive(:update_cache_counts)

          issue.update(assignee: user)
118 119 120 121 122
        end
      end
    end
  end

123 124 125
  describe ".search" do
    let!(:searchable_issue) { create(:issue, title: "Searchable issue") }

Y
Yorick Peterse 已提交
126
    it 'returns notes with a matching title' do
S
Sean McGivern 已提交
127
      expect(issuable_class.search(searchable_issue.title)).
128 129 130 131
        to eq([searchable_issue])
    end

    it 'returns notes with a partially matching title' do
S
Sean McGivern 已提交
132
      expect(issuable_class.search('able')).to eq([searchable_issue])
133
    end
134 135

    it 'returns notes with a matching title regardless of the casing' do
S
Sean McGivern 已提交
136
      expect(issuable_class.search(searchable_issue.title.upcase)).
137 138 139 140 141 142 143 144 145
        to eq([searchable_issue])
    end
  end

  describe ".full_search" do
    let!(:searchable_issue) do
      create(:issue, title: "Searchable issue", description: 'kittens')
    end

Y
Yorick Peterse 已提交
146
    it 'returns notes with a matching title' do
S
Sean McGivern 已提交
147
      expect(issuable_class.full_search(searchable_issue.title)).
148 149 150 151
        to eq([searchable_issue])
    end

    it 'returns notes with a partially matching title' do
S
Sean McGivern 已提交
152
      expect(issuable_class.full_search('able')).to eq([searchable_issue])
153 154 155
    end

    it 'returns notes with a matching title regardless of the casing' do
S
Sean McGivern 已提交
156
      expect(issuable_class.full_search(searchable_issue.title.upcase)).
157 158 159
        to eq([searchable_issue])
    end

Y
Yorick Peterse 已提交
160
    it 'returns notes with a matching description' do
S
Sean McGivern 已提交
161
      expect(issuable_class.full_search(searchable_issue.description)).
162 163 164 165
        to eq([searchable_issue])
    end

    it 'returns notes with a partially matching description' do
S
Sean McGivern 已提交
166
      expect(issuable_class.full_search(searchable_issue.description)).
167 168 169 170
        to eq([searchable_issue])
    end

    it 'returns notes with a matching description regardless of the casing' do
S
Sean McGivern 已提交
171
      expect(issuable_class.full_search(searchable_issue.description.upcase)).
172 173
        to eq([searchable_issue])
    end
174 175
  end

176 177 178 179 180
  describe '.to_ability_name' do
    it { expect(Issue.to_ability_name).to eq("issue") }
    it { expect(MergeRequest.to_ability_name).to eq("merge_request") }
  end

181 182 183
  describe "#today?" do
    it "returns true when created today" do
      # Avoid timezone differences and just return exactly what we want
184 185
      allow(Date).to receive(:today).and_return(issue.created_at.to_date)
      expect(issue.today?).to be_truthy
186 187 188
    end

    it "returns false when not created today" do
189 190
      allow(Date).to receive(:today).and_return(Date.yesterday)
      expect(issue.today?).to be_falsey
191 192 193 194 195
    end
  end

  describe "#new?" do
    it "returns true when created today and record hasn't been updated" do
196 197
      allow(issue).to receive(:today?).and_return(true)
      expect(issue.new?).to be_truthy
198 199 200
    end

    it "returns false when not created today" do
201 202
      allow(issue).to receive(:today?).and_return(false)
      expect(issue.new?).to be_falsey
203 204 205
    end

    it "returns false when record has been updated" do
206
      allow(issue).to receive(:today?).and_return(true)
207
      issue.touch
208
      expect(issue.new?).to be_falsey
209 210
    end
  end
211

212
  describe "#sort" do
213 214 215
    let(:project) { build_stubbed(:empty_project) }

    context "by milestone due date" do
F
Felipe Artur 已提交
216 217 218 219
      # Correct order is:
      # Issues/MRs with milestones ordered by date
      # Issues/MRs with milestones without dates
      # Issues/MRs without milestones
220

221 222 223 224 225 226 227 228 229 230 231
      let!(:issue) { create(:issue, project: project) }
      let!(:early_milestone) { create(:milestone, project: project, due_date: 10.days.from_now) }
      let!(:late_milestone) { create(:milestone, project: project, due_date: 30.days.from_now) }
      let!(:issue1) { create(:issue, project: project, milestone: early_milestone) }
      let!(:issue2) { create(:issue, project: project, milestone: late_milestone) }
      let!(:issue3) { create(:issue, project: project) }

      it "sorts desc" do
        issues = project.issues.sort('milestone_due_desc')
        expect(issues).to match_array([issue2, issue1, issue, issue3])
      end
232

233 234 235
      it "sorts asc" do
        issues = project.issues.sort('milestone_due_asc')
        expect(issues).to match_array([issue1, issue2, issue, issue3])
236 237
      end
    end
238 239 240 241 242 243 244 245 246 247 248 249 250 251

    context 'when all of the results are level on the sort key' do
      let!(:issues) do
        10.times { create(:issue, project: project) }
      end

      it 'has no duplicates across pages' do
        sorted_issue_ids = 1.upto(10).map do |i|
          project.issues.sort('milestone_due_desc').page(i).per(1).first.id
        end

        expect(sorted_issue_ids).to eq(sorted_issue_ids.uniq)
      end
    end
252 253
  end

254
  describe '#subscribed?' do
255 256
    let(:project) { issue.project }

257 258 259 260
    context 'user is not a participant in the issue' do
      before { allow(issue).to receive(:participants).with(user).and_return([]) }

      it 'returns false when no subcription exists' do
261
        expect(issue.subscribed?(user, project)).to be_falsey
262 263 264
      end

      it 'returns true when a subcription exists and subscribed is true' do
265
        issue.subscriptions.create(user: user, project: project, subscribed: true)
266

267
        expect(issue.subscribed?(user, project)).to be_truthy
268 269 270
      end

      it 'returns false when a subcription exists and subscribed is false' do
271
        issue.subscriptions.create(user: user, project: project, subscribed: false)
272

273
        expect(issue.subscribed?(user, project)).to be_falsey
274 275 276 277 278 279 280
      end
    end

    context 'user is a participant in the issue' do
      before { allow(issue).to receive(:participants).with(user).and_return([user]) }

      it 'returns false when no subcription exists' do
281
        expect(issue.subscribed?(user, project)).to be_truthy
282 283 284
      end

      it 'returns true when a subcription exists and subscribed is true' do
285
        issue.subscriptions.create(user: user, project: project, subscribed: true)
286

287
        expect(issue.subscribed?(user, project)).to be_truthy
288 289 290
      end

      it 'returns false when a subcription exists and subscribed is false' do
291
        issue.subscriptions.create(user: user, project: project, subscribed: false)
292

293
        expect(issue.subscribed?(user, project)).to be_falsey
294 295 296 297
      end
    end
  end

298
  describe "#to_hook_data" do
299 300 301
    let(:data) { issue.to_hook_data(user) }
    let(:project) { issue.project }

302
    it "returns correct hook data" do
303 304 305
      expect(data[:object_kind]).to eq("issue")
      expect(data[:user]).to eq(user.hook_attrs)
      expect(data[:object_attributes]).to eq(issue.hook_attrs)
306
      expect(data).not_to have_key(:assignee)
307 308 309 310 311 312
    end

    context "issue is assigned" do
      before { issue.update_attribute(:assignee, user) }

      it "returns correct hook data" do
313 314
        expect(data[:object_attributes]['assignee_id']).to eq(user.id)
        expect(data[:assignee]).to eq(user.hook_attrs)
315
      end
316
    end
317

318 319 320 321 322 323 324 325 326 327
    context 'issue has labels' do
      let(:labels) { [create(:label), create(:label)] }

      before { issue.update_attribute(:labels, labels)}

      it 'includes labels in the hook data' do
        expect(data[:labels]).to eq(labels.map(&:hook_attrs))
      end
    end

328 329
    include_examples 'project hook data'
    include_examples 'deprecated repository hook data'
330
  end
331 332 333 334 335 336 337

  describe '#card_attributes' do
    it 'includes the author name' do
      allow(issue).to receive(:author).and_return(double(name: 'Robert'))
      allow(issue).to receive(:assignee).and_return(nil)

      expect(issue.card_attributes).
D
Douwe Maan 已提交
338
        to eq({ 'Author' => 'Robert', 'Assignee' => nil })
339 340 341 342 343 344 345
    end

    it 'includes the assignee name' do
      allow(issue).to receive(:author).and_return(double(name: 'Robert'))
      allow(issue).to receive(:assignee).and_return(double(name: 'Douwe'))

      expect(issue.card_attributes).
D
Douwe Maan 已提交
346
        to eq({ 'Author' => 'Robert', 'Assignee' => 'Douwe' })
347 348
    end
  end
349

350
  describe '#labels_array' do
351
    let(:project) { create(:empty_project) }
352 353 354 355 356 357 358 359 360 361 362 363
    let(:bug) { create(:label, project: project, title: 'bug') }
    let(:issue) { create(:issue, project: project) }

    before(:each) do
      issue.labels << bug
    end

    it 'loads the association and returns it as an array' do
      expect(issue.reload.labels_array).to eq([bug])
    end
  end

364
  describe '#user_notes_count' do
365
    let(:project) { create(:empty_project) }
366 367 368 369 370 371 372 373 374 375 376 377 378 379
    let(:issue1) { create(:issue, project: project) }
    let(:issue2) { create(:issue, project: project) }

    before do
      create_list(:note, 3, noteable: issue1, project: project)
      create_list(:note, 6, noteable: issue2, project: project)
    end

    it 'counts the user notes' do
      expect(issue1.user_notes_count).to be(3)
      expect(issue2.user_notes_count).to be(6)
    end
  end

380
  describe "votes" do
381 382
    let(:project) { issue.project }

383
    before do
Z
Zeger-Jan van de Weg 已提交
384 385
      create(:award_emoji, :upvote, awardable: issue)
      create(:award_emoji, :downvote, awardable: issue)
386 387 388 389 390
    end

    it "returns correct values" do
      expect(issue.upvotes).to eq(1)
      expect(issue.downvotes).to eq(1)
391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430
    end
  end

  describe '.order_due_date_and_labels_priority' do
    let(:project) { create(:empty_project) }

    def create_issue(milestone, labels)
      create(:labeled_issue, milestone: milestone, labels: labels, project: project)
    end

    it 'sorts issues in order of milestone due date, then label priority' do
      first_priority = create(:label, project: project, priority: 1)
      second_priority = create(:label, project: project, priority: 2)
      no_priority = create(:label, project: project)

      first_milestone = create(:milestone, project: project, due_date: Time.now)
      second_milestone = create(:milestone, project: project, due_date: Time.now + 1.month)
      third_milestone = create(:milestone, project: project)

      # The issues here are ordered by label priority, to ensure that we don't
      # accidentally just sort by creation date.
      second_milestone_first_priority = create_issue(second_milestone, [first_priority, second_priority, no_priority])
      third_milestone_first_priority = create_issue(third_milestone, [first_priority, second_priority, no_priority])
      first_milestone_second_priority = create_issue(first_milestone, [second_priority, no_priority])
      second_milestone_second_priority = create_issue(second_milestone, [second_priority, no_priority])
      no_milestone_second_priority = create_issue(nil, [second_priority, no_priority])
      first_milestone_no_priority = create_issue(first_milestone, [no_priority])
      second_milestone_no_labels = create_issue(second_milestone, [])
      third_milestone_no_priority = create_issue(third_milestone, [no_priority])

      result = Issue.order_due_date_and_labels_priority

      expect(result).to eq([first_milestone_second_priority,
                            first_milestone_no_priority,
                            second_milestone_first_priority,
                            second_milestone_second_priority,
                            second_milestone_no_labels,
                            third_milestone_first_priority,
                            no_milestone_second_priority,
                            third_milestone_no_priority])
431 432
    end
  end
433

434 435 436 437 438 439 440 441 442 443 444 445 446 447
  describe '.order_labels_priority' do
    let(:label_1) { create(:label, title: 'label_1', project: issue.project, priority: 1) }
    let(:label_2) { create(:label, title: 'label_2', project: issue.project, priority: 2) }

    subject { Issue.order_labels_priority(excluded_labels: ['label_1']).first.highest_priority }

    before do
      issue.labels << label_1
      issue.labels << label_2
    end

    it { is_expected.to eq(2) }
  end

448
  describe ".with_label" do
449
    let(:project) { create(:empty_project, :public) }
450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476
    let(:bug) { create(:label, project: project, title: 'bug') }
    let(:feature) { create(:label, project: project, title: 'feature') }
    let(:enhancement) { create(:label, project: project, title: 'enhancement') }
    let(:issue1) { create(:issue, title: "Bugfix1", project: project) }
    let(:issue2) { create(:issue, title: "Bugfix2", project: project) }
    let(:issue3) { create(:issue, title: "Feature1", project: project) }

    before(:each) do
      issue1.labels << bug
      issue1.labels << feature
      issue2.labels << bug
      issue2.labels << enhancement
      issue3.labels << feature
    end

    it 'finds the correct issue containing just enhancement label' do
      expect(Issue.with_label(enhancement.title)).to match_array([issue2])
    end

    it 'finds the correct issues containing the same label' do
      expect(Issue.with_label(bug.title)).to match_array([issue1, issue2])
    end

    it 'finds the correct issues containing only both labels' do
      expect(Issue.with_label([bug.title, enhancement.title])).to match_array([issue2])
    end
  end
Y
Yorick Peterse 已提交
477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497

  describe '#assignee_or_author?' do
    let(:user) { build(:user, id: 1) }
    let(:issue) { build(:issue) }

    it 'returns true for a user that is assigned to an issue' do
      issue.assignee = user

      expect(issue.assignee_or_author?(user)).to eq(true)
    end

    it 'returns true for a user that is the author of an issue' do
      issue.author = user

      expect(issue.assignee_or_author?(user)).to eq(true)
    end

    it 'returns false for a user that is not the assignee or author' do
      expect(issue.assignee_or_author?(user)).to eq(false)
    end
  end
498 499 500 501 502 503

  describe '#spend_time' do
    let(:user) { create(:user) }
    let(:issue) { create(:issue) }

    def spend_time(seconds)
504
      issue.spend_time(duration: seconds, user: user)
505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527
      issue.save!
    end

    context 'adding time' do
      it 'should update the total time spent' do
        spend_time(1800)

        expect(issue.total_time_spent).to eq(1800)
      end
    end

    context 'substracting time' do
      before do
        spend_time(1800)
      end

      it 'should update the total time spent' do
        spend_time(-900)

        expect(issue.total_time_spent).to eq(900)
      end

      context 'when time to substract exceeds the total time spent' do
528 529 530 531
        it 'raise a validation error' do
          expect do
            spend_time(-3600)
          end.to raise_error(ActiveRecord::RecordInvalid)
532 533 534 535
        end
      end
    end
  end
536
end