airbrake-ruby 2.12.0 → 2.13.0.pre.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3f92ef10b01c37b9374a6662bedba21c1593ef34
4
- data.tar.gz: 957da6a830307c3d3cd9f583f1eaa9bf4a2df96f
3
+ metadata.gz: 3ae033f8b21b79ecf3902d570014f379e76b4c16
4
+ data.tar.gz: 131f71f8a92906a05aadf1ed3dbd4ffc35753162
5
5
  SHA512:
6
- metadata.gz: b0ca6c8f84dfc39758f22203519765820a25c4bddd8e7aae90928d46b1f201ee7ab283679fdc16020043cd222c3a6f3e09def0642d1428d005b3022b8dfc59ee
7
- data.tar.gz: 46f263d46199864c89c70c721ecb1c50f13262a13b9b3603b31260cd0f6ea44536fa2ceb419b3efaee1bd82b7877f53246c754288f433054c43ca3b0af3bba58
6
+ metadata.gz: 62a0b41680b377eefd25a0e9433ada861ce92841d954477de949f3ee16bbb32a198d3133433b0783e7e4f747a7c7bff39e76dcf4744bfbb8da8fffbcd54c4a6d
7
+ data.tar.gz: 6864d904a38696d892dcca249ddeff4b1c973e61735ef792a19518bf1e17c430fc9f1bd09ea59fe4c6d7685d6d1a1e911848a657a6647199bb3208d25183192c
data/lib/airbrake-ruby.rb CHANGED
@@ -4,6 +4,7 @@ require 'json'
4
4
  require 'thread'
5
5
  require 'set'
6
6
  require 'socket'
7
+ require 'time'
7
8
 
8
9
  require 'airbrake-ruby/version'
9
10
  require 'airbrake-ruby/config'
@@ -33,6 +34,7 @@ require 'airbrake-ruby/filter_chain'
33
34
  require 'airbrake-ruby/notifier'
34
35
  require 'airbrake-ruby/code_hunk'
35
36
  require 'airbrake-ruby/file_cache'
37
+ require 'airbrake-ruby/route_sender'
36
38
 
37
39
  # This module defines the Airbrake API. The user is meant to interact with
38
40
  # Airbrake via its public class methods. Before using the library, you must to
@@ -115,6 +117,9 @@ module Airbrake
115
117
 
116
118
  # @macro see_public_api_method
117
119
  def merge_context(_context); end
120
+
121
+ # @macro see_public_api_method
122
+ def inc_request(method, route, status_code, dur, time); end
118
123
  end
119
124
 
120
125
  # A Hash that holds all notifiers. The keys of the Hash are notifier
@@ -343,5 +348,27 @@ module Airbrake
343
348
  def merge_context(context)
344
349
  @notifiers[:default].merge_context(context)
345
350
  end
351
+
352
+ # Increments request count of a certain +route+ that was invoked with
353
+ # +method+, and returned +status_code+ at +time+ and took +dur+
354
+ # milliseconds.
355
+ #
356
+ # After a certain amount of time (n seconds) the aggregated route
357
+ # information will be sent to Airbrake.
358
+ #
359
+ # @example
360
+ # Airbrake.inc_request('POST', '/thing/:id/create', 200, 123, Time.now)
361
+ #
362
+ # @param [String] method The HTTP method that was invoked
363
+ # @param [String] route The route that was invoked
364
+ # @param [Integer] status_code The respose code that the route returned
365
+ # @param [Float] dur How much time the processing of the request took in
366
+ # milliseconds
367
+ # @param [Time] time When the request happened
368
+ # @return [void]
369
+ # @since v2.13.0
370
+ def inc_request(method, route, status_code, dur, time)
371
+ @notifiers[:default].inc_request(method, route, status_code, dur, time)
372
+ end
346
373
  end
347
374
  end
@@ -83,6 +83,12 @@ module Airbrake
83
83
  # @since v2.5.0
84
84
  attr_accessor :code_hunks
85
85
 
86
+ # @return [Integer] how many seconds to wait before sending collected route
87
+ # stats
88
+ # @api public
89
+ # @since v2.13.0
90
+ attr_accessor :route_stats_flush_period
91
+
86
92
  # @param [Hash{Symbol=>Object}] user_config the hash to be used to build the
87
93
  # config
88
94
  def initialize(user_config = {})
@@ -113,6 +119,7 @@ module Airbrake
113
119
  )
114
120
 
115
121
  self.versions = {}
122
+ self.route_stats_flush_period = 15
116
123
 
117
124
  merge(user_config)
118
125
  end
@@ -36,6 +36,8 @@ module Airbrake
36
36
 
37
37
  @async_sender = AsyncSender.new(@config)
38
38
  @sync_sender = SyncSender.new(@config)
39
+
40
+ @route_sender = RouteSender.new(@config)
39
41
  end
40
42
 
41
43
  # @macro see_public_api_method
@@ -95,6 +97,11 @@ module Airbrake
95
97
  @context.merge!(context)
96
98
  end
97
99
 
100
+ # @macro see_public_api_method
101
+ def inc_request(*args)
102
+ @route_sender.inc_request(*args)
103
+ end
104
+
98
105
  private
99
106
 
100
107
  def convert_to_exception(ex)
@@ -16,16 +16,19 @@ module Airbrake
16
16
  # @param [Net::HTTPResponse] response
17
17
  # @param [Logger] logger
18
18
  # @return [Hash{String=>String}] parsed response
19
- # rubocop:disable Metrics/MethodLength
19
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
20
20
  def self.parse(response, logger)
21
21
  code = response.code.to_i
22
22
  body = response.body
23
23
 
24
24
  begin
25
25
  case code
26
+ when 200
27
+ logger.debug("#{LOG_LABEL} #{name} (#{code}): #{body}")
28
+ { response.msg => response.body }
26
29
  when 201
27
30
  parsed_body = JSON.parse(body)
28
- logger.debug("#{LOG_LABEL} #{parsed_body}")
31
+ logger.debug("#{LOG_LABEL} #{name} (#{code}): #{parsed_body}")
29
32
  parsed_body
30
33
  when 400, 401, 403, 420
31
34
  parsed_body = JSON.parse(body)
@@ -47,7 +50,7 @@ module Airbrake
47
50
  { 'error' => ex.inspect }
48
51
  end
49
52
  end
50
- # rubocop:enable Metrics/MethodLength
53
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
51
54
 
52
55
  def self.truncated_body(body)
53
56
  if body.nil?
@@ -0,0 +1,106 @@
1
+ module Airbrake
2
+ # RouteSender aggregates information about requests and periodically sends
3
+ # collected data to Airbrake.
4
+ # @since v2.13.0
5
+ class RouteSender
6
+ # The key that represents a route.
7
+ RouteKey = Struct.new(:method, :route, :statusCode, :time)
8
+
9
+ # RouteStat holds data that describes a route's performance.
10
+ RouteStat = Struct.new(:count, :sum, :sumsq, :min, :max) do
11
+ # @param [Integer] count The number of requests
12
+ # @param [Float] sum The sum of request duration in milliseconds
13
+ # @param [Float] sumsq The squared sum of request duration in milliseconds
14
+ # @param [Float] min The minimal request duration
15
+ # @param [Float] max The maximum request duration
16
+ def initialize(count: 0, sum: 0.0, sumsq: 0.0, min: 0.0, max: 0.0)
17
+ super(count, sum, sumsq, min, max)
18
+ end
19
+ end
20
+
21
+ # @param [Airbrake::Config] config
22
+ def initialize(config)
23
+ @config = config
24
+ @flush_period = config.route_stats_flush_period
25
+ @sender = SyncSender.new(config, :put)
26
+ @routes = {}
27
+ @thread = nil
28
+ @mutex = Mutex.new
29
+ end
30
+
31
+ # @macro see_public_api_method
32
+ def inc_request(method, route, status_code, dur, tm)
33
+ route = create_route_key(method, route, status_code, tm)
34
+
35
+ promise = Airbrake::Promise.new
36
+
37
+ @mutex.synchronize do
38
+ @routes[route] ||= RouteStat.new
39
+ increment_stats(@routes[route], dur)
40
+
41
+ if @flush_period > 0
42
+ schedule_flush(promise)
43
+ else
44
+ send(@routes, promise)
45
+ end
46
+ end
47
+
48
+ promise
49
+ end
50
+
51
+ private
52
+
53
+ def create_route_key(method, route, status_code, tm)
54
+ # rubocop:disable Style/DateTime
55
+ time = DateTime.new(
56
+ tm.year, tm.month, tm.day, tm.hour, tm.min, 0, tm.zone || 0
57
+ )
58
+ # rubocop:enable Style/DateTime
59
+ RouteKey.new(method, route, status_code, time.rfc3339)
60
+ end
61
+
62
+ def increment_stats(stat, dur)
63
+ stat.count += 1
64
+
65
+ ms = dur.to_f
66
+ stat.sum += ms
67
+ stat.sumsq += ms * ms
68
+
69
+ stat.min = ms if ms < stat.min || stat.min == 0
70
+ stat.max = ms if ms > stat.max
71
+ end
72
+
73
+ def schedule_flush(promise)
74
+ @thread ||= Thread.new do
75
+ sleep(@flush_period)
76
+
77
+ routes = nil
78
+ @mutex.synchronize do
79
+ routes = @routes
80
+ @routes = {}
81
+ @thread = nil
82
+ end
83
+
84
+ send(routes, promise)
85
+ end
86
+
87
+ # Setting a name is needed to test the timer.
88
+ # Ruby <=2.2 doesn't support Thread#name, so we have this check.
89
+ @thread.name = 'route-stat-thread' if @thread.respond_to?(:name)
90
+ end
91
+
92
+ def send(routes, promise)
93
+ if routes.none?
94
+ raise "#{self.class.name}##{__method__}: routes cannot be empty. Race?"
95
+ end
96
+
97
+ @config.logger.debug("#{LOG_LABEL} RouteStats#send: #{routes}")
98
+
99
+ @sender.send(
100
+ { routes: routes.map { |k, v| k.to_h.merge(v.to_h) } },
101
+ promise,
102
+ URI.join(@config.host, "api/v4/projects/#{@config.project_id}/routes-stats")
103
+ )
104
+ end
105
+ end
106
+ end
@@ -1,5 +1,6 @@
1
1
  module Airbrake
2
- # Responsible for sending notices to Airbrake synchronously. Supports proxies.
2
+ # Responsible for sending data to Airbrake synchronously via PUT or POST
3
+ # methods. Supports proxies.
3
4
  #
4
5
  # @see AsyncSender
5
6
  # @api private
@@ -9,21 +10,22 @@ module Airbrake
9
10
  CONTENT_TYPE = 'application/json'.freeze
10
11
 
11
12
  # @param [Airbrake::Config] config
12
- def initialize(config)
13
+ def initialize(config, method = :post)
13
14
  @config = config
15
+ @method = method
14
16
  @rate_limit_reset = Time.now
15
17
  end
16
18
 
17
- # Sends a POST request to the given +endpoint+ with the +notice+ payload.
19
+ # Sends a POST or PUT request to the given +endpoint+ with the +data+ payload.
18
20
  #
19
- # @param [Airbrake::Notice] notice
20
- # @param [Airbrake::Notice] endpoint
21
+ # @param [#to_json] data
22
+ # @param [URI::HTTPS] endpoint
21
23
  # @return [Hash{String=>String}] the parsed HTTP response
22
- def send(notice, promise, endpoint = @config.endpoint)
24
+ def send(data, promise, endpoint = @config.endpoint)
23
25
  return promise if rate_limited_ip?(promise)
24
26
 
25
27
  response = nil
26
- req = build_post_request(endpoint, notice)
28
+ req = build_request(endpoint, data)
27
29
 
28
30
  return promise if missing_body?(req, promise)
29
31
 
@@ -58,16 +60,27 @@ module Airbrake
58
60
  end
59
61
  end
60
62
 
61
- def build_post_request(uri, notice)
62
- Net::HTTP::Post.new(uri.request_uri).tap do |req|
63
- req.body = notice.to_json
63
+ def build_request(uri, data)
64
+ req =
65
+ if @method == :put
66
+ Net::HTTP::Put.new(uri.request_uri)
67
+ else
68
+ Net::HTTP::Post.new(uri.request_uri)
69
+ end
64
70
 
65
- req['Authorization'] = "Bearer #{@config.project_key}"
66
- req['Content-Type'] = CONTENT_TYPE
67
- req['User-Agent'] =
68
- "#{Airbrake::Notice::NOTIFIER[:name]}/#{Airbrake::AIRBRAKE_RUBY_VERSION}" \
69
- " Ruby/#{RUBY_VERSION}"
70
- end
71
+ build_request_body(req, data)
72
+ end
73
+
74
+ def build_request_body(req, data)
75
+ req.body = data.to_json
76
+
77
+ req['Authorization'] = "Bearer #{@config.project_key}"
78
+ req['Content-Type'] = CONTENT_TYPE
79
+ req['User-Agent'] =
80
+ "#{Airbrake::Notice::NOTIFIER[:name]}/#{Airbrake::AIRBRAKE_RUBY_VERSION}" \
81
+ " Ruby/#{RUBY_VERSION}"
82
+
83
+ req
71
84
  end
72
85
 
73
86
  def proxy_params
@@ -87,7 +100,7 @@ module Airbrake
87
100
  missing = req.body.nil?
88
101
 
89
102
  if missing
90
- reason = "#{LOG_LABEL} notice was not sent because of missing body"
103
+ reason = "#{LOG_LABEL} data was not sent because of missing body"
91
104
  @config.logger.error(reason)
92
105
  promise.reject(reason)
93
106
  end
@@ -2,5 +2,5 @@
2
2
  # More information: http://semver.org/
3
3
  module Airbrake
4
4
  # @return [String] the library version
5
- AIRBRAKE_RUBY_VERSION = '2.12.0'.freeze
5
+ AIRBRAKE_RUBY_VERSION = '2.13.0.pre.1'.freeze
6
6
  end
@@ -110,4 +110,14 @@ RSpec.describe Airbrake do
110
110
  described_class.merge_context(foo: 'bar')
111
111
  end
112
112
  end
113
+
114
+ describe ".inc_request" do
115
+ it "forwards 'inc_request' to the notifier" do
116
+ t = Time.now
117
+ expect(default_notifier).to receive(:inc_request).with(
118
+ 'GET', '/foo', 200, 1000, t
119
+ )
120
+ described_class.inc_request('GET', '/foo', 200, 1000, t)
121
+ end
122
+ end
113
123
  end
@@ -21,7 +21,7 @@ RSpec.describe Airbrake::AsyncSender do
21
21
  sender.close
22
22
 
23
23
  log = stdout.string.split("\n")
24
- notices_sent = log.grep(/\*\*Airbrake: \{\}/).size
24
+ notices_sent = log.grep(/\*\*Airbrake: Airbrake::Response \(201\): \{\}/).size
25
25
  notices_dropped = log.grep(/\*\*Airbrake:.*not.*delivered/).size
26
26
  expect(notices_sent).to be >= queue_size
27
27
  expect(notices_sent + notices_dropped).to eq(notices_count)
@@ -60,7 +60,7 @@ RSpec.describe Airbrake::AsyncSender do
60
60
 
61
61
  it "prints the correct number of log messages" do
62
62
  log = @stderr.string.split("\n")
63
- notices_sent = log.grep(/\*\*Airbrake: \{\}/).size
63
+ notices_sent = log.grep(/\*\*Airbrake: Airbrake::Response \(201\): \{\}/).size
64
64
  notices_dropped = log.grep(/\*\*Airbrake:.*not.*delivered/).size
65
65
  expect(notices_sent).to be >= @sender.instance_variable_get(:@unsent).max
66
66
  expect(notices_sent + notices_dropped).to eq(300)
data/spec/config_spec.rb CHANGED
@@ -78,6 +78,10 @@ RSpec.describe Airbrake::Config do
78
78
  it "doesn't set default whitelist" do
79
79
  expect(config.whitelist_keys).to be_empty
80
80
  end
81
+
82
+ it "sets the default route_stats_flush_period" do
83
+ expect(config.route_stats_flush_period).to eq(15)
84
+ end
81
85
  end
82
86
  end
83
87
 
@@ -449,5 +449,15 @@ RSpec.describe Airbrake::Notifier do
449
449
  subject.merge_context(apples: 'oranges')
450
450
  end
451
451
  end
452
+
453
+ describe "#inc_request" do
454
+ it "forwards 'inc_request' to RouteSender" do
455
+ t = Time.now
456
+ expect_any_instance_of(Airbrake::RouteSender).to receive(:inc_request).with(
457
+ 'GET', '/foo', 200, 1000, t
458
+ )
459
+ subject.inc_request('GET', '/foo', 200, 1000, t)
460
+ end
461
+ end
452
462
  end
453
463
  # rubocop:enable Layout/DotPosition
@@ -5,10 +5,12 @@ RSpec.describe Airbrake::Response do
5
5
  let(:out) { StringIO.new }
6
6
  let(:logger) { Logger.new(out) }
7
7
 
8
- context "when response code is 201" do
9
- it "logs response body" do
10
- described_class.parse(OpenStruct.new(code: 201, body: '{}'), logger)
11
- expect(out.string).to match(/Airbrake: {}/)
8
+ [200, 201].each do |code|
9
+ context "when response code is #{code}" do
10
+ it "logs response body" do
11
+ described_class.parse(OpenStruct.new(code: code, body: '{}'), logger)
12
+ expect(out.string).to match(/Airbrake: Airbrake::Response \(#{code}\): {}/)
13
+ end
12
14
  end
13
15
  end
14
16
 
@@ -0,0 +1,86 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Airbrake::RouteSender do
4
+ let(:endpoint) { 'https://api.airbrake.io/api/v4/projects/1/routes-stats' }
5
+
6
+ let(:config) do
7
+ Airbrake::Config.new(
8
+ project_id: 1,
9
+ project_key: 'banana',
10
+ route_stats_flush_period: 0.1
11
+ )
12
+ end
13
+
14
+ subject { described_class.new(config) }
15
+
16
+ describe "#inc_request" do
17
+ before do
18
+ stub_request(:put, endpoint).to_return(status: 200, body: '')
19
+ end
20
+
21
+ # Let the request finish.
22
+ after { sleep 0.2 }
23
+
24
+ it "rounds time to the floor minute" do
25
+ subject.inc_request('GET', '/foo', 200, 24, Time.new(2018, 1, 1, 0, 0, 20, 0))
26
+ sleep 0.2
27
+ expect(
28
+ a_request(:put, endpoint).with(body: /"time":"2018-01-01T00:00:00\+00:00"/)
29
+ ).to have_been_made
30
+ end
31
+
32
+ it "increments routes with the same key" do
33
+ subject.inc_request('GET', '/foo', 200, 24, Time.new(2018, 1, 1, 0, 0, 20, 0))
34
+ subject.inc_request('GET', '/foo', 200, 24, Time.new(2018, 1, 1, 0, 0, 50, 0))
35
+ sleep 0.2
36
+ expect(
37
+ a_request(:put, endpoint).with(body: /"count":2/)
38
+ ).to have_been_made
39
+ end
40
+
41
+ it "groups routes by time" do
42
+ subject.inc_request('GET', '/foo', 200, 24, Time.new(2018, 1, 1, 0, 0, 20, 0))
43
+ subject.inc_request('GET', '/foo', 200, 10, Time.new(2018, 1, 1, 0, 1, 20, 0))
44
+ sleep 0.2
45
+ expect(
46
+ a_request(:put, endpoint).with(
47
+ body: %r|\A
48
+ {"routes":\[
49
+ {"method":"GET","route":"/foo","statusCode":200,
50
+ "time":"2018-01-01T00:00:00\+00:00","count":1,"sum":24.0,
51
+ "sumsq":576.0,"min":24.0,"max":24.0},
52
+ {"method":"GET","route":"/foo","statusCode":200,
53
+ "time":"2018-01-01T00:01:00\+00:00","count":1,"sum":10.0,
54
+ "sumsq":100.0,"min":10.0,"max":10.0}\]}
55
+ \z|x
56
+ )
57
+ ).to have_been_made
58
+ end
59
+
60
+ it "groups routes by route key" do
61
+ subject.inc_request('GET', '/foo', 200, 24, Time.new(2018, 1, 1, 0, 0, 20, 0))
62
+ subject.inc_request('POST', '/foo', 200, 10, Time.new(2018, 1, 1, 0, 0, 20, 0))
63
+ sleep 0.2
64
+ expect(
65
+ a_request(:put, endpoint).with(
66
+ body: %r|\A
67
+ {"routes":\[
68
+ {"method":"GET","route":"/foo","statusCode":200,
69
+ "time":"2018-01-01T00:00:00\+00:00","count":1,"sum":24.0,
70
+ "sumsq":576.0,"min":24.0,"max":24.0},
71
+ {"method":"POST","route":"/foo","statusCode":200,
72
+ "time":"2018-01-01T00:00:00\+00:00","count":1,"sum":10.0,
73
+ "sumsq":100.0,"min":10.0,"max":10.0}\]}
74
+ \z|x
75
+ )
76
+ ).to have_been_made
77
+ end
78
+
79
+ it "returns a promise" do
80
+ promise = subject.inc_request('GET', '/foo', 200, 24, Time.new)
81
+ sleep 0.2
82
+ expect(promise).to be_an(Airbrake::Promise)
83
+ expect(promise.value).to eq('' => nil)
84
+ end
85
+ end
86
+ end
@@ -30,7 +30,7 @@ RSpec.describe Airbrake::SyncSender do
30
30
  before { stub_request(:post, endpoint).to_return(body: '{}') }
31
31
 
32
32
  it "sets the Content-Type header to JSON" do
33
- sender.send(notice, promise)
33
+ sender.send({}, promise)
34
34
  expect(
35
35
  a_request(:post, endpoint).with(
36
36
  headers: { 'Content-Type' => 'application/json' }
@@ -39,18 +39,18 @@ RSpec.describe Airbrake::SyncSender do
39
39
  end
40
40
 
41
41
  it "sets the User-Agent header to the notifier slug" do
42
- sender.send(notice, promise)
42
+ sender.send({}, promise)
43
43
  expect(
44
44
  a_request(:post, endpoint).with(
45
45
  headers: {
46
- 'User-Agent' => %r{airbrake-ruby/\d+\.\d+\.\d+ Ruby/\d+\.\d+\.\d+}
46
+ 'User-Agent' => %r{airbrake-ruby/\d+\.\d+\.\d+.+ Ruby/\d+\.\d+\.\d+}
47
47
  }
48
48
  )
49
49
  ).to have_been_made.once
50
50
  end
51
51
 
52
52
  it "sets the Authorization header to the project key" do
53
- sender.send(notice, promise)
53
+ sender.send({}, promise)
54
54
  expect(
55
55
  a_request(:post, endpoint).with(
56
56
  headers: { 'Authorization' => 'Bearer banana' }
@@ -62,13 +62,13 @@ RSpec.describe Airbrake::SyncSender do
62
62
  https = double("foo")
63
63
  allow(sender).to receive(:build_https).and_return(https)
64
64
  allow(https).to receive(:request).and_raise(StandardError.new('foo'))
65
- expect(sender.send(notice, promise)).to be_an(Airbrake::Promise)
65
+ expect(sender.send({}, promise)).to be_an(Airbrake::Promise)
66
66
  expect(promise.value).to eq('error' => '**Airbrake: HTTP error: foo')
67
67
  expect(stdout.string).to match(/ERROR -- : .+ HTTP error: foo/)
68
68
  end
69
69
 
70
70
  context "when request body is nil" do
71
- it "doesn't send a notice" do
71
+ it "doesn't send data" do
72
72
  expect_any_instance_of(Airbrake::Truncator).
73
73
  to receive(:reduce_max_size).and_return(0)
74
74
 
@@ -84,8 +84,8 @@ RSpec.describe Airbrake::SyncSender do
84
84
 
85
85
  expect(sender.send(notice, promise)).to be_an(Airbrake::Promise)
86
86
  expect(promise.value).
87
- to match('error' => '**Airbrake: notice was not sent because of missing body')
88
- expect(stdout.string).to match(/ERROR -- : .+ notice was not sent/)
87
+ to match('error' => '**Airbrake: data was not sent because of missing body')
88
+ expect(stdout.string).to match(/ERROR -- : .+ data was not sent/)
89
89
  end
90
90
  end
91
91
 
@@ -102,11 +102,11 @@ RSpec.describe Airbrake::SyncSender do
102
102
 
103
103
  it "returns error" do
104
104
  p1 = Airbrake::Promise.new
105
- sender.send(notice, p1)
105
+ sender.send({}, p1)
106
106
  expect(p1.value).to match('error' => '**Airbrake: IP is rate limited')
107
107
 
108
108
  p2 = Airbrake::Promise.new
109
- sender.send(notice, p2)
109
+ sender.send({}, p2)
110
110
  expect(p2.value).to match('error' => '**Airbrake: IP is rate limited')
111
111
 
112
112
  # Wait for X-RateLimit-Delay and then make a new request to make sure p2
@@ -114,11 +114,29 @@ RSpec.describe Airbrake::SyncSender do
114
114
  sleep 1
115
115
 
116
116
  p3 = Airbrake::Promise.new
117
- sender.send(notice, p3)
117
+ sender.send({}, p3)
118
118
  expect(p3.value).to match('error' => '**Airbrake: IP is rate limited')
119
119
 
120
120
  expect(a_request(:post, endpoint)).to have_been_made.twice
121
121
  end
122
122
  end
123
+
124
+ context "when the provided method is :put" do
125
+ before { stub_request(:put, endpoint).to_return(status: 200, body: '') }
126
+
127
+ it "PUTs the request" do
128
+ sender = described_class.new(config, :put)
129
+ sender.send({}, promise)
130
+ expect(a_request(:put, endpoint)).to have_been_made
131
+ end
132
+ end
133
+
134
+ context "when the provided method is :post" do
135
+ it "POSTs the request" do
136
+ sender = described_class.new(config, :post)
137
+ sender.send({}, promise)
138
+ expect(a_request(:post, endpoint)).to have_been_made
139
+ end
140
+ end
123
141
  end
124
142
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: airbrake-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.12.0
4
+ version: 2.13.0.pre.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Airbrake Technologies, Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-10-11 00:00:00.000000000 Z
11
+ date: 2018-10-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -141,6 +141,7 @@ files:
141
141
  - lib/airbrake-ruby/notifier.rb
142
142
  - lib/airbrake-ruby/promise.rb
143
143
  - lib/airbrake-ruby/response.rb
144
+ - lib/airbrake-ruby/route_sender.rb
144
145
  - lib/airbrake-ruby/sync_sender.rb
145
146
  - lib/airbrake-ruby/truncator.rb
146
147
  - lib/airbrake-ruby/version.rb
@@ -177,6 +178,7 @@ files:
177
178
  - spec/notifier_spec/options_spec.rb
178
179
  - spec/promise_spec.rb
179
180
  - spec/response_spec.rb
181
+ - spec/route_sender_spec.rb
180
182
  - spec/spec_helper.rb
181
183
  - spec/sync_sender_spec.rb
182
184
  - spec/truncator_spec.rb
@@ -195,9 +197,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
195
197
  version: '2.0'
196
198
  required_rubygems_version: !ruby/object:Gem::Requirement
197
199
  requirements:
198
- - - ">="
200
+ - - ">"
199
201
  - !ruby/object:Gem::Version
200
- version: '0'
202
+ version: 1.3.1
201
203
  requirements: []
202
204
  rubyforge_project:
203
205
  rubygems_version: 2.6.13
@@ -205,6 +207,7 @@ signing_key:
205
207
  specification_version: 4
206
208
  summary: Ruby notifier for https://airbrake.io
207
209
  test_files:
210
+ - spec/route_sender_spec.rb
208
211
  - spec/truncator_spec.rb
209
212
  - spec/helpers.rb
210
213
  - spec/filters/exception_attributes_filter_spec.rb