airbrake-ruby 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. checksums.yaml +7 -0
  2. data/lib/airbrake-ruby.rb +513 -0
  3. data/lib/airbrake-ruby/async_sender.rb +142 -0
  4. data/lib/airbrake-ruby/backtrace.rb +196 -0
  5. data/lib/airbrake-ruby/benchmark.rb +39 -0
  6. data/lib/airbrake-ruby/code_hunk.rb +51 -0
  7. data/lib/airbrake-ruby/config.rb +229 -0
  8. data/lib/airbrake-ruby/config/validator.rb +91 -0
  9. data/lib/airbrake-ruby/deploy_notifier.rb +36 -0
  10. data/lib/airbrake-ruby/file_cache.rb +48 -0
  11. data/lib/airbrake-ruby/filter_chain.rb +95 -0
  12. data/lib/airbrake-ruby/filters/context_filter.rb +29 -0
  13. data/lib/airbrake-ruby/filters/dependency_filter.rb +31 -0
  14. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +46 -0
  15. data/lib/airbrake-ruby/filters/gem_root_filter.rb +33 -0
  16. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +92 -0
  17. data/lib/airbrake-ruby/filters/git_repository_filter.rb +64 -0
  18. data/lib/airbrake-ruby/filters/git_revision_filter.rb +66 -0
  19. data/lib/airbrake-ruby/filters/keys_blacklist.rb +49 -0
  20. data/lib/airbrake-ruby/filters/keys_filter.rb +140 -0
  21. data/lib/airbrake-ruby/filters/keys_whitelist.rb +48 -0
  22. data/lib/airbrake-ruby/filters/root_directory_filter.rb +28 -0
  23. data/lib/airbrake-ruby/filters/sql_filter.rb +104 -0
  24. data/lib/airbrake-ruby/filters/system_exit_filter.rb +23 -0
  25. data/lib/airbrake-ruby/filters/thread_filter.rb +92 -0
  26. data/lib/airbrake-ruby/hash_keyable.rb +37 -0
  27. data/lib/airbrake-ruby/ignorable.rb +44 -0
  28. data/lib/airbrake-ruby/inspectable.rb +39 -0
  29. data/lib/airbrake-ruby/loggable.rb +34 -0
  30. data/lib/airbrake-ruby/monotonic_time.rb +43 -0
  31. data/lib/airbrake-ruby/nested_exception.rb +38 -0
  32. data/lib/airbrake-ruby/notice.rb +162 -0
  33. data/lib/airbrake-ruby/notice_notifier.rb +134 -0
  34. data/lib/airbrake-ruby/performance_breakdown.rb +45 -0
  35. data/lib/airbrake-ruby/performance_notifier.rb +125 -0
  36. data/lib/airbrake-ruby/promise.rb +109 -0
  37. data/lib/airbrake-ruby/query.rb +53 -0
  38. data/lib/airbrake-ruby/request.rb +45 -0
  39. data/lib/airbrake-ruby/response.rb +74 -0
  40. data/lib/airbrake-ruby/stashable.rb +15 -0
  41. data/lib/airbrake-ruby/stat.rb +73 -0
  42. data/lib/airbrake-ruby/sync_sender.rb +113 -0
  43. data/lib/airbrake-ruby/tdigest.rb +393 -0
  44. data/lib/airbrake-ruby/time_truncate.rb +17 -0
  45. data/lib/airbrake-ruby/timed_trace.rb +58 -0
  46. data/lib/airbrake-ruby/truncator.rb +115 -0
  47. data/lib/airbrake-ruby/version.rb +6 -0
  48. data/spec/airbrake_spec.rb +324 -0
  49. data/spec/async_sender_spec.rb +155 -0
  50. data/spec/backtrace_spec.rb +427 -0
  51. data/spec/benchmark_spec.rb +33 -0
  52. data/spec/code_hunk_spec.rb +115 -0
  53. data/spec/config/validator_spec.rb +184 -0
  54. data/spec/config_spec.rb +154 -0
  55. data/spec/deploy_notifier_spec.rb +48 -0
  56. data/spec/file_cache.rb +36 -0
  57. data/spec/filter_chain_spec.rb +92 -0
  58. data/spec/filters/context_filter_spec.rb +23 -0
  59. data/spec/filters/dependency_filter_spec.rb +12 -0
  60. data/spec/filters/exception_attributes_filter_spec.rb +50 -0
  61. data/spec/filters/gem_root_filter_spec.rb +41 -0
  62. data/spec/filters/git_last_checkout_filter_spec.rb +46 -0
  63. data/spec/filters/git_repository_filter.rb +61 -0
  64. data/spec/filters/git_revision_filter_spec.rb +126 -0
  65. data/spec/filters/keys_blacklist_spec.rb +225 -0
  66. data/spec/filters/keys_whitelist_spec.rb +194 -0
  67. data/spec/filters/root_directory_filter_spec.rb +39 -0
  68. data/spec/filters/sql_filter_spec.rb +219 -0
  69. data/spec/filters/system_exit_filter_spec.rb +14 -0
  70. data/spec/filters/thread_filter_spec.rb +277 -0
  71. data/spec/fixtures/notroot.txt +7 -0
  72. data/spec/fixtures/project_root/code.rb +221 -0
  73. data/spec/fixtures/project_root/empty_file.rb +0 -0
  74. data/spec/fixtures/project_root/long_line.txt +1 -0
  75. data/spec/fixtures/project_root/short_file.rb +3 -0
  76. data/spec/fixtures/project_root/vendor/bundle/ignored_file.rb +5 -0
  77. data/spec/helpers.rb +9 -0
  78. data/spec/ignorable_spec.rb +14 -0
  79. data/spec/inspectable_spec.rb +45 -0
  80. data/spec/monotonic_time_spec.rb +12 -0
  81. data/spec/nested_exception_spec.rb +73 -0
  82. data/spec/notice_notifier_spec.rb +356 -0
  83. data/spec/notice_notifier_spec/options_spec.rb +259 -0
  84. data/spec/notice_spec.rb +296 -0
  85. data/spec/performance_breakdown_spec.rb +12 -0
  86. data/spec/performance_notifier_spec.rb +435 -0
  87. data/spec/promise_spec.rb +197 -0
  88. data/spec/query_spec.rb +11 -0
  89. data/spec/request_spec.rb +11 -0
  90. data/spec/response_spec.rb +88 -0
  91. data/spec/spec_helper.rb +100 -0
  92. data/spec/stashable_spec.rb +23 -0
  93. data/spec/stat_spec.rb +47 -0
  94. data/spec/sync_sender_spec.rb +133 -0
  95. data/spec/tdigest_spec.rb +230 -0
  96. data/spec/time_truncate_spec.rb +13 -0
  97. data/spec/timed_trace_spec.rb +125 -0
  98. data/spec/truncator_spec.rb +238 -0
  99. metadata +213 -0
@@ -0,0 +1,48 @@
1
+ RSpec.describe Airbrake::DeployNotifier do
2
+ before do
3
+ Airbrake::Config.instance = Airbrake::Config.new(project_id: 1, project_key: '123')
4
+ end
5
+
6
+ describe "#notify" do
7
+ it "returns a promise" do
8
+ stub_request(:post, 'https://api.airbrake.io/api/v4/projects/1/deploys')
9
+ .to_return(status: 201, body: '{}')
10
+ expect(subject.notify({})).to be_an(Airbrake::Promise)
11
+ end
12
+
13
+ context "when config is invalid" do
14
+ before { Airbrake::Config.instance.merge(project_id: nil) }
15
+
16
+ it "returns a rejected promise" do
17
+ promise = subject.notify({})
18
+ expect(promise).to be_rejected
19
+ end
20
+ end
21
+
22
+ context "when environment is configured" do
23
+ before { Airbrake::Config.instance.merge(environment: 'fooenv') }
24
+
25
+ it "prefers the passed environment to the config env" do
26
+ expect_any_instance_of(Airbrake::SyncSender).to receive(:send).with(
27
+ { environment: 'barenv' },
28
+ instance_of(Airbrake::Promise),
29
+ URI('https://api.airbrake.io/api/v4/projects/1/deploys')
30
+ )
31
+ subject.notify(environment: 'barenv')
32
+ end
33
+ end
34
+
35
+ context "when environment is not configured" do
36
+ before { Airbrake::Config.instance.merge(environment: 'fooenv') }
37
+
38
+ it "sets the environment from the config" do
39
+ expect_any_instance_of(Airbrake::SyncSender).to receive(:send).with(
40
+ { environment: 'fooenv' },
41
+ instance_of(Airbrake::Promise),
42
+ URI('https://api.airbrake.io/api/v4/projects/1/deploys')
43
+ )
44
+ subject.notify({})
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,36 @@
1
+ RSpec.describe Airbrake::FileCache do
2
+ after do
3
+ %i[banana mango].each { |k| described_class.delete(k) }
4
+ expect(described_class).to be_empty
5
+ end
6
+
7
+ describe ".[]=" do
8
+ context "when cache limit isn't reached" do
9
+ before do
10
+ stub_const("#{described_class.name}::MAX_SIZE", 10)
11
+ end
12
+
13
+ it "adds objects" do
14
+ described_class[:banana] = 1
15
+ described_class[:mango] = 2
16
+
17
+ expect(described_class[:banana]).to eq(1)
18
+ expect(described_class[:mango]).to eq(2)
19
+ end
20
+ end
21
+
22
+ context "when cache limit is reached" do
23
+ before do
24
+ stub_const("#{described_class.name}::MAX_SIZE", 1)
25
+ end
26
+
27
+ it "replaces old objects with new ones" do
28
+ described_class[:banana] = 1
29
+ described_class[:mango] = 2
30
+
31
+ expect(described_class[:banana]).to be_nil
32
+ expect(described_class[:mango]).to eq(2)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,92 @@
1
+ RSpec.describe Airbrake::FilterChain do
2
+ let(:notice) { Airbrake::Notice.new(AirbrakeTestError.new) }
3
+
4
+ describe "#refine" do
5
+ let(:filter) do
6
+ Class.new do
7
+ attr_reader :weight
8
+
9
+ def initialize(weight)
10
+ @weight = weight
11
+ end
12
+
13
+ def call(notice)
14
+ notice[:params][:bingo] << @weight
15
+ end
16
+ end
17
+ end
18
+
19
+ it "executes filters from heaviest to lightest" do
20
+ notice[:params][:bingo] = []
21
+
22
+ (0...3).reverse_each { |i| subject.add_filter(filter.new(i)) }
23
+ subject.refine(notice)
24
+
25
+ expect(notice[:params][:bingo]).to eq([2, 1, 0])
26
+ end
27
+
28
+ it "stops execution once a notice was ignored" do
29
+ f2 = filter.new(2)
30
+ expect(f2).to receive(:call)
31
+
32
+ f1 = proc { |notice| notice.ignore! }
33
+
34
+ f0 = filter.new(-1)
35
+ expect(f0).not_to receive(:call)
36
+
37
+ [f2, f1, f0].each { |f| subject.add_filter(f) }
38
+
39
+ subject.refine(notice)
40
+ end
41
+ end
42
+
43
+ describe "#delete_filter" do
44
+ let(:filter) do
45
+ Class.new do
46
+ class << self
47
+ def name
48
+ 'FooFilter'
49
+ end
50
+ end
51
+
52
+ def initialize(foo)
53
+ @foo = foo
54
+ end
55
+
56
+ def call(notice)
57
+ notice[:params][:foo] << @foo
58
+ end
59
+ end
60
+ end
61
+
62
+ it "deletes a class filter" do
63
+ notice[:params][:foo] = []
64
+
65
+ f1 = filter.new(1)
66
+ subject.add_filter(f1)
67
+
68
+ foo_filter_mock = double
69
+ expect(foo_filter_mock).to(
70
+ receive(:name).at_least(:once).and_return('FooFilter')
71
+ )
72
+ subject.delete_filter(foo_filter_mock)
73
+
74
+ f2 = filter.new(2)
75
+ subject.add_filter(f2)
76
+
77
+ subject.refine(notice)
78
+ expect(notice[:params][:foo]).to eq([2])
79
+ end
80
+ end
81
+
82
+ describe "#inspect" do
83
+ it "returns a string representation of an empty FilterChain" do
84
+ expect(subject.inspect).to eq('[]')
85
+ end
86
+
87
+ it "returns a string representation of a non-empty FilterChain" do
88
+ subject.add_filter(proc {})
89
+ expect(subject.inspect).to eq('[Proc]')
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,23 @@
1
+ RSpec.describe Airbrake::Filters::ContextFilter do
2
+ let(:notice) { Airbrake::Notice.new(AirbrakeTestError.new) }
3
+
4
+ context "when the current context is empty" do
5
+ it "doesn't merge anything with params" do
6
+ described_class.new({}).call(notice)
7
+ expect(notice[:params]).to be_empty
8
+ end
9
+ end
10
+
11
+ context "when the current context has some data" do
12
+ it "merges the data with params" do
13
+ described_class.new(apples: 'oranges').call(notice)
14
+ expect(notice[:params]).to eq(airbrake_context: { apples: 'oranges' })
15
+ end
16
+
17
+ it "clears the data from the provided context" do
18
+ context = { apples: 'oranges' }
19
+ described_class.new(context).call(notice)
20
+ expect(context).to be_empty
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,12 @@
1
+ RSpec.describe Airbrake::Filters::DependencyFilter do
2
+ let(:notice) { Airbrake::Notice.new(AirbrakeTestError.new) }
3
+
4
+ describe "#call" do
5
+ it "attaches loaded dependencies to context/versions/dependencies" do
6
+ subject.call(notice)
7
+ expect(notice[:context][:versions][:dependencies]).to include(
8
+ 'airbrake-ruby' => Airbrake::AIRBRAKE_RUBY_VERSION
9
+ )
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,50 @@
1
+ RSpec.describe Airbrake::Filters::ExceptionAttributesFilter do
2
+ describe "#call" do
3
+ let(:notice) { Airbrake::Notice.new(ex) }
4
+
5
+ context "when #to_airbrake returns a non-Hash object" do
6
+ let(:ex) do
7
+ Class.new(AirbrakeTestError) do
8
+ def to_airbrake
9
+ Object.new
10
+ end
11
+ end.new
12
+ end
13
+
14
+ it "doesn't raise" do
15
+ expect { subject.call(notice) }.not_to raise_error
16
+ expect(notice[:params]).to be_empty
17
+ end
18
+ end
19
+
20
+ context "when #to_airbrake errors out" do
21
+ let(:ex) do
22
+ Class.new(AirbrakeTestError) do
23
+ def to_airbrake
24
+ 1 / 0
25
+ end
26
+ end.new
27
+ end
28
+
29
+ it "doesn't raise" do
30
+ expect { subject.call(notice) }.not_to raise_error
31
+ expect(notice[:params]).to be_empty
32
+ end
33
+ end
34
+
35
+ context "when #to_airbrake returns a hash" do
36
+ let(:ex) do
37
+ Class.new(AirbrakeTestError) do
38
+ def to_airbrake
39
+ { params: { foo: '1' } }
40
+ end
41
+ end.new
42
+ end
43
+
44
+ it "merges parameters with the notice" do
45
+ subject.call(notice)
46
+ expect(notice[:params]).to eq(foo: '1')
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,41 @@
1
+ RSpec.describe Airbrake::Filters::GemRootFilter do
2
+ let(:notice) { Airbrake::Notice.new(AirbrakeTestError.new) }
3
+ let(:root1) { '/my/gem/root' }
4
+ let(:root2) { '/my/other/gem/root' }
5
+
6
+ before { Gem.path << root1 << root2 }
7
+ after { 2.times { Gem.path.pop } }
8
+
9
+ it "replaces gem root in the backtrace with a label" do
10
+ # rubocop:disable Metrics/LineLength
11
+ notice[:errors].first[:backtrace] = [
12
+ { file: "/home/kyrylo/code/airbrake/ruby/spec/spec_helper.rb" },
13
+ { file: "#{root1}/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb" },
14
+ { file: "/opt/rubies/ruby-2.2.2/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb" },
15
+ { file: "#{root2}/gems/rspec-core-3.3.2/exe/rspec" }
16
+ ]
17
+ # rubocop:enable Metrics/LineLength
18
+
19
+ subject.call(notice)
20
+
21
+ # rubocop:disable Metrics/LineLength
22
+ expect(notice[:errors].first[:backtrace]).to(
23
+ eq(
24
+ [
25
+ { file: "/home/kyrylo/code/airbrake/ruby/spec/spec_helper.rb" },
26
+ { file: "/GEM_ROOT/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb" },
27
+ { file: "/opt/rubies/ruby-2.2.2/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb" },
28
+ { file: "/GEM_ROOT/gems/rspec-core-3.3.2/exe/rspec" }
29
+ ]
30
+ )
31
+ )
32
+ # rubocop:enable Metrics/LineLength
33
+ end
34
+
35
+ it "does not filter file when it is nil" do
36
+ expect(notice[:errors].first[:file]).to be_nil
37
+ expect { subject.call(notice) }.not_to(
38
+ change { notice[:errors].first[:file] }
39
+ )
40
+ end
41
+ end
@@ -0,0 +1,46 @@
1
+ RSpec.describe Airbrake::Filters::GitLastCheckoutFilter do
2
+ subject { described_class.new('.') }
3
+
4
+ let(:notice) { Airbrake::Notice.new(AirbrakeTestError.new) }
5
+
6
+ context "when context/lastCheckout is defined" do
7
+ it "doesn't attach anything to context/lastCheckout" do
8
+ notice[:context][:lastCheckout] = '123'
9
+ subject.call(notice)
10
+ expect(notice[:context][:lastCheckout]).to eq('123')
11
+ end
12
+ end
13
+
14
+ context "when .git directory doesn't exist" do
15
+ subject { described_class.new('root/dir') }
16
+
17
+ it "doesn't attach anything to context/lastCheckout" do
18
+ subject.call(notice)
19
+ expect(notice[:context][:lastCheckout]).to be_nil
20
+ end
21
+ end
22
+
23
+ context "when .git directory exists" do
24
+ before { subject.call(notice) }
25
+
26
+ it "attaches last checkouted username" do
27
+ expect(notice[:context][:lastCheckout][:username]).not_to be_empty
28
+ end
29
+
30
+ it "attaches last checkouted email" do
31
+ expect(notice[:context][:lastCheckout][:email]).to(
32
+ match(/\A\w+[\w.-]*@\w+\.?\w+?\z/)
33
+ )
34
+ end
35
+
36
+ it "attaches last checkouted revision" do
37
+ expect(notice[:context][:lastCheckout][:revision]).not_to be_empty
38
+ expect(notice[:context][:lastCheckout][:revision].size).to eq(40)
39
+ end
40
+
41
+ it "attaches last checkouted time" do
42
+ expect(notice[:context][:lastCheckout][:time]).not_to be_empty
43
+ expect(notice[:context][:lastCheckout][:time].size).to eq(25)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,61 @@
1
+ RSpec.describe Airbrake::Filters::GitRepositoryFilter do
2
+ subject { described_class.new('.') }
3
+
4
+ let(:notice) do
5
+ Airbrake::Notice.new(Airbrake::Config.new, AirbrakeTestError.new)
6
+ end
7
+
8
+ describe "#initialize" do
9
+ it "parses standard git version" do
10
+ allow_any_instance_of(Kernel)
11
+ .to receive(:`).and_return('git version 2.18.0')
12
+ expect { subject }.not_to raise_error
13
+ end
14
+
15
+ it "parses release candidate git version" do
16
+ allow_any_instance_of(Kernel)
17
+ .to receive(:`).and_return('git version 2.21.0-rc0')
18
+ expect { subject }.not_to raise_error
19
+ end
20
+
21
+ it "parses git version with brackets" do
22
+ allow_any_instance_of(Kernel)
23
+ .to receive(:`).and_return('git version 2.17.2 (Apple Git-113)')
24
+ expect { subject }.not_to raise_error
25
+ end
26
+ end
27
+
28
+ context "when context/repository is defined" do
29
+ it "doesn't attach anything to context/repository" do
30
+ notice[:context][:repository] = 'git@github.com:kyrylo/test.git'
31
+ subject.call(notice)
32
+ expect(notice[:context][:repository]).to eq('git@github.com:kyrylo/test.git')
33
+ end
34
+ end
35
+
36
+ context "when .git directory doesn't exist" do
37
+ subject { described_class.new('root/dir') }
38
+
39
+ it "doesn't attach anything to context/repository" do
40
+ subject.call(notice)
41
+ expect(notice[:context][:repository]).to be_nil
42
+ end
43
+ end
44
+
45
+ context "when .git directory exists" do
46
+ it "attaches context/repository" do
47
+ subject.call(notice)
48
+ expect(notice[:context][:repository]).to eq(
49
+ 'ssh://git@github.com/airbrake/airbrake-ruby.git'
50
+ )
51
+ end
52
+ end
53
+
54
+ context "when git is not in PATH" do
55
+ it "does not attach context/repository" do
56
+ ENV['PATH'] = ''
57
+ subject.call(notice)
58
+ expect(notice[:context][:repository]).to be_nil
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,126 @@
1
+ RSpec.describe Airbrake::Filters::GitRevisionFilter do
2
+ subject { described_class.new('root/dir') }
3
+
4
+ # 'let!', not 'let' to make sure Notice doesn't call File.exist? with
5
+ # unexpected arguments.
6
+ let!(:notice) { Airbrake::Notice.new(AirbrakeTestError.new) }
7
+
8
+ context "when context/revision is defined" do
9
+ it "doesn't attach anything to context/revision" do
10
+ notice[:context][:revision] = '1.2.3'
11
+ subject.call(notice)
12
+ expect(notice[:context][:revision]).to eq('1.2.3')
13
+ end
14
+ end
15
+
16
+ context "when .git directory doesn't exist" do
17
+ it "doesn't attach anything to context/revision" do
18
+ subject.call(notice)
19
+ expect(notice[:context][:revision]).to be_nil
20
+ end
21
+ end
22
+
23
+ context "when .git directory exists" do
24
+ before do
25
+ expect(File).to receive(:exist?).with('root/dir/.git').and_return(true)
26
+ end
27
+
28
+ context "and when HEAD doesn't exist" do
29
+ before do
30
+ expect(File).to receive(:exist?).with('root/dir/.git/HEAD').and_return(false)
31
+ end
32
+
33
+ it "doesn't attach anything to context/revision" do
34
+ subject.call(notice)
35
+ expect(notice[:context][:revision]).to be_nil
36
+ end
37
+ end
38
+
39
+ context "and when HEAD exists" do
40
+ before do
41
+ expect(File).to receive(:exist?).with('root/dir/.git/HEAD').and_return(true)
42
+ end
43
+
44
+ context "and also when HEAD doesn't start with 'ref: '" do
45
+ before do
46
+ expect(File).to(
47
+ receive(:read).with('root/dir/.git/HEAD').and_return('refs/foo')
48
+ )
49
+ end
50
+
51
+ it "attaches the content of HEAD to context/revision" do
52
+ subject.call(notice)
53
+ expect(notice[:context][:revision]).to eq('refs/foo')
54
+ end
55
+ end
56
+
57
+ context "and also when HEAD starts with 'ref: " do
58
+ before do
59
+ expect(File).to(
60
+ receive(:read).with('root/dir/.git/HEAD').and_return("ref: refs/foo\n")
61
+ )
62
+ end
63
+
64
+ context "when the ref exists" do
65
+ before do
66
+ expect(File).to(
67
+ receive(:exist?).with('root/dir/.git/refs/foo').and_return(true)
68
+ )
69
+ expect(File).to(
70
+ receive(:read).with('root/dir/.git/refs/foo').and_return("d34db33f\n")
71
+ )
72
+ end
73
+
74
+ it "attaches the revision from the ref to context/revision" do
75
+ subject.call(notice)
76
+ expect(notice[:context][:revision]).to eq('d34db33f')
77
+ end
78
+ end
79
+
80
+ context "when the ref doesn't exist" do
81
+ before do
82
+ expect(File).to(
83
+ receive(:exist?).with('root/dir/.git/refs/foo').and_return(false)
84
+ )
85
+ end
86
+
87
+ context "and when '.git/packed-refs' exists" do
88
+ before do
89
+ expect(File).to(
90
+ receive(:exist?).with('root/dir/.git/packed-refs').and_return(true)
91
+ )
92
+ expect(File).to(
93
+ receive(:readlines).with('root/dir/.git/packed-refs').and_return(
94
+ [
95
+ "# pack-refs with: peeled fully-peeled\n",
96
+ "ccb316eecff79c7528d1ad43e5fa165f7a44d52e refs/tags/v3.0.30\n",
97
+ "^d358900f73ee5bfd6ca3a592cf23ac6e82df83c1",
98
+ "d34db33f refs/foo\n"
99
+ ]
100
+ )
101
+ )
102
+ end
103
+
104
+ it "attaches the revision from 'packed-refs' to context/revision" do
105
+ subject.call(notice)
106
+ expect(notice[:context][:revision]).to eq('d34db33f')
107
+ end
108
+ end
109
+
110
+ context "and when '.git/packed-refs' doesn't exist" do
111
+ before do
112
+ expect(File).to(
113
+ receive(:exist?).with('root/dir/.git/packed-refs').and_return(false)
114
+ )
115
+ end
116
+
117
+ it "attaches the content of HEAD to context/revision" do
118
+ subject.call(notice)
119
+ expect(notice[:context][:revision]).to eq('refs/foo')
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end