problem_spec.rb 15.1 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 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 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 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 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 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 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500
describe Problem, type: 'model' do
  context 'validations' do
    it 'requires an environment' do
      err = Fabricate.build(:problem, environment: nil)
      expect(err).to_not be_valid
      expect(err.errors[:environment]).to include("can't be blank")
    end
  end

  describe "Fabrication" do
    context "Fabricate(:problem)" do
      it 'should have no comment' do
        expect do
          Fabricate(:problem)
        end.to_not change(Comment, :count)
      end
    end

    context "Fabricate(:problem_with_comments)" do
      it 'should have 3 comments' do
        expect do
          Fabricate(:problem_with_comments)
        end.to change(Comment, :count).by(3)
      end
    end

    context "Fabricate(:problem_with_errs)" do
      it 'should have 3 errs' do
        expect do
          Fabricate(:problem_with_errs)
        end.to change(Err, :count).by(3)
      end
    end
  end

  context '#last_notice_at' do
    it "returns the created_at timestamp of the latest notice" do
      err = Fabricate(:err)
      problem = err.problem
      expect(problem).to_not be_nil

      notice1 = Fabricate(:notice, err: err)
      expect(problem.last_notice_at).to eq notice1.reload.created_at

      notice2 = Fabricate(:notice, err: err)
      expect(problem.last_notice_at).to eq notice2.reload.created_at
    end
  end

  context '#first_notice_at' do
    it "returns the created_at timestamp of the first notice" do
      err = Fabricate(:err)
      problem = err.problem
      expect(problem).to_not be_nil

      notice1 = Fabricate(:notice, err: err)
      expect(problem.first_notice_at.to_i).to be_within(1).of(notice1.created_at.to_i)

      Fabricate(:notice, err: err)
      expect(problem.first_notice_at.to_i).to be_within(1).of(notice1.created_at.to_i)
    end
  end

  context '#message' do
    it "adding a notice caches its message" do
      err = Fabricate(:err)
      problem = err.problem
      expect do
        Fabricate(:notice, err: err, message: 'ERR 1')
      end.to change(problem, :message).from(nil).to('ERR 1')
    end
  end

  context 'being created' do
    context 'when the app has err notifications set to false' do
      it 'should not send an email notification' do
        app = Fabricate(:app_with_watcher, notify_on_errs: false)
        expect(Mailer).to_not receive(:err_notification)
        Fabricate(:problem, app: app)
      end
    end
  end

  context "#resolved?" do
    it "should start out as unresolved" do
      problem = Problem.new
      expect(problem).to_not be_resolved
      expect(problem).to be_unresolved
    end

    it "should be able to be resolved" do
      problem = Fabricate(:problem)
      expect(problem).to_not be_resolved
      problem.resolve!
      expect(problem.reload).to be_resolved
    end
  end

  context "resolve!" do
    it "marks the problem as resolved" do
      problem = Fabricate(:problem)
      expect(problem).to_not be_resolved
      problem.resolve!
      expect(problem).to be_resolved
    end

    it "should record the time when it was resolved" do
      problem = Fabricate(:problem)
      expected_resolved_at = Time.zone.now
      Timecop.freeze(expected_resolved_at) do
        problem.resolve!
      end
      expect(problem.resolved_at.to_s).to eq expected_resolved_at.to_s
    end

    it "should not reset notice count" do
      problem = Fabricate(:problem, notices_count: 1)
      original_notices_count = problem.notices_count
      expect(original_notices_count).to be > 0

      problem.resolve!
      expect(problem.notices_count).to eq original_notices_count
    end

    it "should throw an err if it's not successful" do
      problem = Fabricate(:problem)
      expect(problem).to_not be_resolved
      allow(problem).to receive(:valid?).and_return(false)
      ## update_attributes not test #valid? but #errors.any?
      # https://github.com/mongoid/mongoid/blob/master/lib/mongoid/persistence.rb#L137
      er = ActiveModel::Errors.new(problem)
      er.add_on_blank(:resolved)
      allow(problem).to receive(:errors).and_return(er)
      expect(problem).to_not be_valid
      expect do
        problem.resolve!
      end.to raise_error(Mongoid::Errors::Validations)
    end
  end

  context "#unmerge!" do
    it "creates a separate problem for each err" do
      problem1 = Fabricate(:notice).problem
      problem2 = Fabricate(:notice).problem
      merged_problem = Problem.merge!(problem1, problem2)
      expect(merged_problem.errs.length).to eq 2

      expect { merged_problem.unmerge! }.to change(Problem, :count).by(1)
      expect(merged_problem.errs(true).length).to eq 1
    end

    it "runs smoothly for problem without errs" do
      expect { Fabricate(:problem).unmerge! }.not_to raise_error
    end
  end

  context "Scopes" do
    context "resolved" do
      it 'only finds resolved Problems' do
        resolved = Fabricate(:problem, resolved: true)
        unresolved = Fabricate(:problem, resolved: false)
        expect(Problem.resolved.all).to include(resolved)
        expect(Problem.resolved.all).to_not include(unresolved)
      end
    end

    context "unresolved" do
      it 'only finds unresolved Problems' do
        resolved = Fabricate(:problem, resolved: true)
        unresolved = Fabricate(:problem, resolved: false)
        expect(Problem.unresolved.all).to_not include(resolved)
        expect(Problem.unresolved.all).to include(unresolved)
      end
    end

    context "searching" do
      it 'finds the correct record' do
        find = Fabricate(:problem, resolved: false, error_class: 'theErrorclass::other',
                         message: "other", where: 'errorclass', environment: 'development', app_name: 'other')
        dont_find = Fabricate(:problem, resolved: false, error_class: "Batman",
                              message: 'todo', where: 'classerror', environment: 'development', app_name: 'other')
        expect(Problem.search("theErrorClass").unresolved).to include(find)
        expect(Problem.search("theErrorClass").unresolved).to_not include(dont_find)
      end
      it 'find on where message' do
        problem = Fabricate(:problem, where: 'cyril')
        expect(Problem.search('cyril').entries).to eq [problem]
      end
    end
  end

  context "notice counter cache" do
    before do
      @app = Fabricate(:app)
      @problem = Fabricate(:problem, app: @app)
      @err = Fabricate(:err, problem: @problem)
    end

    it "#notices_count returns 0 by default" do
      expect(@problem.notices_count).to eq 0
    end

    it "adding a notice increases #notices_count by 1" do
      expect do
        Fabricate(:notice, err: @err, message: 'ERR 1')
      end.to change(@problem.reload, :notices_count).from(0).to(1)
    end

    it "removing a notice decreases #notices_count by 1" do
      Fabricate(:notice, err: @err, message: 'ERR 1')
      expect do
        @err.notices.first.destroy
        @problem.reload
      end.to change(@problem, :notices_count).from(1).to(0)
    end
  end

  context "#app_name" do
    let!(:app) { Fabricate(:app) }
    let!(:problem) { Fabricate(:problem, app: app) }

    before { app.reload }

    it "is set when a problem is created" do
      assert_equal app.name, problem.app_name
    end

    it "is updated when an app is updated" do
      expect do
        app.update_attributes!(name: "Bar App")
        problem.reload
      end.to change(problem, :app_name).to("Bar App")
    end
  end

  context "notice messages cache" do
    before do
      @app = Fabricate(:app)
      @problem = Fabricate(:problem, app: @app)
      @err = Fabricate(:err, problem: @problem)
    end

    it "#messages should be empty by default" do
      expect(@problem.messages).to eq({})
    end

    it "removing a notice removes string from #messages" do
      Fabricate(:notice, err: @err, message: 'ERR 1')
      expect do
        @err.notices.first.destroy
        @problem.reload
      end.to change(@problem, :messages).from(Digest::MD5.hexdigest('ERR 1') => { 'value' => 'ERR 1', 'count' => 1 }).to({})
    end

    it "removing a notice from the problem with broken counter should not raise an error" do
      Fabricate(:notice, err: @err, message: 'ERR 1')
      @problem.messages = {}
      @problem.save!
      expect { @err.notices.first.destroy }.not_to raise_error
    end
  end

  context "notice hosts cache" do
    before do
      @app = Fabricate(:app)
      @problem = Fabricate(:problem, app: @app)
      @err = Fabricate(:err, problem: @problem)
    end

    it "#hosts should be empty by default" do
      expect(@problem.hosts).to eq({})
    end

    it "removing a notice removes string from #hosts" do
      Fabricate(:notice, err: @err, request: { 'url' => "http://example.com/resource/12" })
      expect do
        @err.notices.first.destroy
        @problem.reload
      end.to change(@problem, :hosts).from(Digest::MD5.hexdigest('example.com') => { 'value' => 'example.com', 'count' => 1 }).to({})
    end
  end

  context "notice user_agents cache" do
    before do
      @app = Fabricate(:app)
      @problem = Fabricate(:problem, app: @app)
      @err = Fabricate(:err, problem: @problem)
    end

    it "#user_agents should be empty by default" do
      expect(@problem.user_agents).to eq({})
    end

    it "removing a notice removes string from #user_agents" do
      Fabricate(
        :notice,
        err:     @err,
        request: {
          'cgi-data' => {
            'HTTP_USER_AGENT' => 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204 Safari/534.16'
          }
        }
      )
      expect do
        @err.notices.first.destroy
        @problem.reload
      end.to change(@problem, :user_agents).
        from(
          Digest::MD5.hexdigest('Chrome 10.0.648.204 (OS X 10.6.7)') => {
            'value' => 'Chrome 10.0.648.204 (OS X 10.6.7)', 'count' => 1 }
        ).to({})
    end
  end

  context "comment counter cache" do
    before do
      @app = Fabricate(:app)
      @problem = Fabricate(:problem, app: @app)
    end

    it "#comments_count returns 0 by default" do
      expect(@problem.comments_count).to eq 0
    end

    it "adding a comment increases #comments_count by 1" do
      expect do
        Fabricate(:comment, err: @problem)
      end.to change(@problem, :comments_count).from(0).to(1)
    end

    it "removing a comment decreases #comments_count by 1" do
      Fabricate(:comment, err: @problem)
      expect do
        @problem.reload.comments.first.destroy
        @problem.reload
      end.to change(@problem, :comments_count).from(1).to(0)
    end
  end

  describe "#issue_type" do
    context "without issue_type fill in Problem" do
      let(:problem) do
        Problem.new(app: app)
      end

      let(:app) do
        App.new(issue_tracker: issue_tracker)
      end

      context "without issue_tracker associate to app" do
        let(:issue_tracker) do
          nil
        end
        it 'return nil' do
          expect(problem.issue_type).to be_nil
        end
      end

      context "with issue_tracker valid associate to app" do
        let(:issue_tracker) do
          Fabricate(:issue_tracker).tap do |t|
            t.instance_variable_set(:@tracker, ErrbitPlugin::MockIssueTracker.new(t.options))
          end
        end

        it 'return the issue_tracker label' do
          expect(problem.issue_type).to eql 'mock'
        end
      end

      context "with issue_tracker not valid associate to app" do
        let(:issue_tracker) do
          IssueTracker.new(type_tracker: 'fake')
        end

        it 'return nil' do
          expect(problem.issue_type).to be_nil
        end
      end
    end

    context "with issue_type fill in Problem" do
      it 'return the value associate' do
        expect(Problem.new(issue_type: 'foo').issue_type).to eql 'foo'
      end
    end
  end

  describe '#recache' do
    let(:problem) { Fabricate(:problem_with_errs) }
    let(:first_errs) { problem.errs }
    let!(:notice) { Fabricate(:notice, err: first_errs.first) }

    before do
      problem.update_attribute(:notices_count, 0)
    end

    it 'update the notice_count' do
      expect do
        problem.recache
      end.to change {
        problem.notices_count
      }.from(0).to(1)
    end

    context "with only one notice" do
      before do
        problem.update_attributes!(messages: {})
        problem.recache
      end

      it 'update information about this notice' do
        expect(problem.message).to eq notice.message
        expect(problem.where).to eq notice.where
      end

      it 'update first_notice_at' do
        expect(problem.first_notice_at).to eq notice.reload.created_at
      end

      it 'update last_notice_at' do
        expect(problem.last_notice_at).to eq notice.reload.created_at
      end

      it 'update stats messages' do
        expect(problem.messages).to eq(
          Digest::MD5.hexdigest(notice.message) => { 'value' => notice.message, 'count' => 1 }
        )
      end

      it 'update stats hosts' do
        expect(problem.hosts).to eq(
          Digest::MD5.hexdigest(notice.host) => { 'value' => notice.host, 'count' => 1 }
        )
      end

      it 'update stats user_agents' do
        expect(problem.user_agents).to eq(
          Digest::MD5.hexdigest(notice.user_agent_string) => { 'value' => notice.user_agent_string, 'count' => 1 }
        )
      end
    end

    context "with several notices" do
      let!(:notice_2) { Fabricate(:notice, err: first_errs.first) }
      let!(:notice_3) { Fabricate(:notice, err: first_errs.first) }
      before do
        problem.update_attributes!(messages: {})
        problem.recache
      end

      it 'update information about this notice' do
        expect(problem.message).to eq notice.message
        expect(problem.where).to eq notice.where
      end

      it 'update first_notice_at' do
        expect(problem.first_notice_at.to_i).to be_within(2).of(notice.created_at.to_i)
      end

      it 'update last_notice_at' do
        expect(problem.last_notice_at.to_i).to be_within(2).of(notice.created_at.to_i)
      end

      it 'update stats messages' do
        expect(problem.messages).to eq(Digest::MD5.hexdigest(notice.message) => { 'value' => notice.message, 'count' => 3 })
      end

      it 'update stats hosts' do
        expect(problem.hosts).to eq(Digest::MD5.hexdigest(notice.host) => { 'value' => notice.host, 'count' => 3 })
      end

      it 'update stats user_agents' do
        expect(problem.user_agents).to eq(Digest::MD5.hexdigest(notice.user_agent_string) => { 'value' => notice.user_agent_string, 'count' => 3 })
      end
    end
  end

  context "#url" do
    subject { Fabricate(:problem) }

    it "uses the configured protocol" do
      allow(Errbit::Config).to receive(:protocol).and_return("https")

      expect(subject.url).to eq "https://errbit.example.com/apps/#{subject.app.id}/problems/#{subject.id}"
    end

    it "uses the configured host" do
      allow(Errbit::Config).to receive(:host).and_return("memyselfandi.com")

      expect(subject.url).to eq "http://memyselfandi.com/apps/#{subject.app.id}/problems/#{subject.id}"
    end

    it "uses the configured port" do
      allow(Errbit::Config).to receive(:port).and_return(8123)

      expect(subject.url).to eq "http://errbit.example.com:8123/apps/#{subject.app.id}/problems/#{subject.id}"
    end
  end
end