rack-attack 5.4.0 → 6.2.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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +78 -27
  3. data/Rakefile +3 -1
  4. data/bin/setup +8 -0
  5. data/lib/rack/attack.rb +137 -148
  6. data/lib/rack/attack/allow2ban.rb +2 -0
  7. data/lib/rack/attack/blocklist.rb +3 -1
  8. data/lib/rack/attack/cache.rb +9 -4
  9. data/lib/rack/attack/check.rb +5 -2
  10. data/lib/rack/attack/fail2ban.rb +2 -0
  11. data/lib/rack/attack/path_normalizer.rb +22 -18
  12. data/lib/rack/attack/railtie.rb +21 -0
  13. data/lib/rack/attack/request.rb +2 -0
  14. data/lib/rack/attack/safelist.rb +3 -1
  15. data/lib/rack/attack/store_proxy.rb +12 -24
  16. data/lib/rack/attack/store_proxy/active_support_redis_store_proxy.rb +39 -0
  17. data/lib/rack/attack/store_proxy/dalli_proxy.rb +27 -13
  18. data/lib/rack/attack/store_proxy/mem_cache_store_proxy.rb +21 -0
  19. data/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb +23 -9
  20. data/lib/rack/attack/store_proxy/redis_proxy.rb +16 -10
  21. data/lib/rack/attack/store_proxy/redis_store_proxy.rb +5 -5
  22. data/lib/rack/attack/throttle.rb +12 -8
  23. data/lib/rack/attack/track.rb +9 -6
  24. data/lib/rack/attack/version.rb +3 -1
  25. data/spec/acceptance/allow2ban_spec.rb +2 -0
  26. data/spec/acceptance/blocking_ip_spec.rb +4 -2
  27. data/spec/acceptance/blocking_spec.rb +45 -3
  28. data/spec/acceptance/blocking_subnet_spec.rb +4 -2
  29. data/spec/acceptance/cache_store_config_for_allow2ban_spec.rb +50 -39
  30. data/spec/acceptance/cache_store_config_for_fail2ban_spec.rb +38 -29
  31. data/spec/acceptance/cache_store_config_for_throttle_spec.rb +2 -0
  32. data/spec/acceptance/cache_store_config_with_rails_spec.rb +2 -0
  33. data/spec/acceptance/customizing_blocked_response_spec.rb +2 -0
  34. data/spec/acceptance/customizing_throttled_response_spec.rb +2 -0
  35. data/spec/acceptance/extending_request_object_spec.rb +2 -0
  36. data/spec/acceptance/fail2ban_spec.rb +2 -0
  37. data/spec/acceptance/rails_middleware_spec.rb +41 -0
  38. data/spec/acceptance/safelisting_ip_spec.rb +4 -2
  39. data/spec/acceptance/safelisting_spec.rb +57 -3
  40. data/spec/acceptance/safelisting_subnet_spec.rb +4 -2
  41. data/spec/acceptance/stores/active_support_dalli_store_spec.rb +3 -23
  42. data/spec/acceptance/stores/active_support_mem_cache_store_pooled_spec.rb +20 -0
  43. data/spec/acceptance/stores/active_support_mem_cache_store_spec.rb +4 -24
  44. data/spec/acceptance/stores/active_support_memory_store_spec.rb +3 -23
  45. data/spec/acceptance/stores/active_support_redis_cache_store_pooled_spec.rb +10 -24
  46. data/spec/acceptance/stores/active_support_redis_cache_store_spec.rb +9 -25
  47. data/spec/acceptance/stores/active_support_redis_store_spec.rb +4 -24
  48. data/spec/acceptance/stores/connection_pool_dalli_client_spec.rb +5 -23
  49. data/spec/acceptance/stores/dalli_client_spec.rb +3 -23
  50. data/spec/acceptance/stores/redis_spec.rb +1 -23
  51. data/spec/acceptance/stores/redis_store_spec.rb +3 -23
  52. data/spec/acceptance/throttling_spec.rb +7 -5
  53. data/spec/acceptance/track_spec.rb +5 -3
  54. data/spec/acceptance/track_throttle_spec.rb +5 -3
  55. data/spec/allow2ban_spec.rb +20 -15
  56. data/spec/fail2ban_spec.rb +20 -17
  57. data/spec/integration/offline_spec.rb +3 -1
  58. data/spec/rack_attack_dalli_proxy_spec.rb +2 -0
  59. data/spec/rack_attack_instrumentation_spec.rb +42 -0
  60. data/spec/rack_attack_path_normalizer_spec.rb +4 -2
  61. data/spec/rack_attack_request_spec.rb +2 -0
  62. data/spec/rack_attack_spec.rb +38 -34
  63. data/spec/rack_attack_throttle_spec.rb +50 -19
  64. data/spec/rack_attack_track_spec.rb +12 -7
  65. data/spec/spec_helper.rb +10 -8
  66. data/spec/support/cache_store_helper.rb +27 -1
  67. metadata +48 -28
  68. data/lib/rack/attack/store_proxy/mem_cache_proxy.rb +0 -50
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/cache'
2
4
  require_relative '../spec_helper'
3
5
 
@@ -22,7 +24,7 @@ if defined?(::ActiveSupport::Cache::RedisStore)
22
24
  before do
23
25
  @cache = Rack::Attack::Cache.new
24
26
  # Use presumably unused port for Redis client
25
- @cache.store = ActiveSupport::Cache::RedisStore.new(:host => '127.0.0.1', :port => 3333)
27
+ @cache.store = ActiveSupport::Cache::RedisStore.new(host: '127.0.0.1', port: 3333)
26
28
  end
27
29
  end
28
30
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'spec_helper'
2
4
 
3
5
  describe Rack::Attack::StoreProxy::DalliProxy do
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "spec_helper"
4
+
5
+ # ActiveSupport::Subscribers added in ~> 4.0.2.0
6
+ if ActiveSupport::VERSION::MAJOR > 3
7
+ require_relative 'spec_helper'
8
+ require 'active_support/subscriber'
9
+ class CustomSubscriber < ActiveSupport::Subscriber
10
+ @notification_count = 0
11
+
12
+ class << self
13
+ attr_accessor :notification_count
14
+ end
15
+
16
+ def throttle(_event)
17
+ self.class.notification_count += 1
18
+ end
19
+ end
20
+
21
+ describe 'Rack::Attack.instrument' do
22
+ before do
23
+ @period = 60 # Use a long period; failures due to cache key rotation less likely
24
+ Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
25
+ Rack::Attack.throttle('ip/sec', limit: 1, period: @period) { |req| req.ip }
26
+ end
27
+
28
+ describe "with throttling" do
29
+ before do
30
+ ActiveSupport::Notifications.stub(:notifier, ActiveSupport::Notifications::Fanout.new) do
31
+ CustomSubscriber.attach_to("rack_attack")
32
+ 2.times { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' }
33
+ end
34
+ end
35
+
36
+ it 'should instrument without error' do
37
+ _(last_response.status).must_equal 429
38
+ assert_equal 1, CustomSubscriber.notification_count
39
+ end
40
+ end
41
+ end
42
+ end
@@ -1,17 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'spec_helper'
2
4
 
3
5
  describe Rack::Attack::PathNormalizer do
4
6
  subject { Rack::Attack::PathNormalizer }
5
7
 
6
8
  it 'should have a normalize_path method' do
7
- subject.normalize_path('/foo').must_equal '/foo'
9
+ _(subject.normalize_path('/foo')).must_equal '/foo'
8
10
  end
9
11
 
10
12
  describe 'FallbackNormalizer' do
11
13
  subject { Rack::Attack::FallbackPathNormalizer }
12
14
 
13
15
  it '#normalize_path does not change the path' do
14
- subject.normalize_path('').must_equal ''
16
+ _(subject.normalize_path('')).must_equal ''
15
17
  end
16
18
  end
17
19
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'spec_helper'
2
4
 
3
5
  describe 'Rack::Attack' do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'spec_helper'
2
4
 
3
5
  describe 'Rack::Attack' do
@@ -10,7 +12,7 @@ describe 'Rack::Attack' do
10
12
 
11
13
  it 'blocks requests with trailing slash' do
12
14
  get '/foo/'
13
- last_response.status.must_equal 403
15
+ _(last_response.status).must_equal 403
14
16
  end
15
17
  end
16
18
 
@@ -20,28 +22,21 @@ describe 'Rack::Attack' do
20
22
  Rack::Attack.blocklist("ip #{@bad_ip}") { |req| req.ip == @bad_ip }
21
23
  end
22
24
 
23
- it('has a blocklist') {
24
- Rack::Attack.blocklists.key?("ip #{@bad_ip}").must_equal true
25
- }
26
-
27
- it('has a blacklist with a deprication warning') {
28
- _, stderror = capture_io do
29
- Rack::Attack.blacklists.key?("ip #{@bad_ip}").must_equal true
30
- end
31
- assert_match "[DEPRECATION] 'Rack::Attack.blacklists' is deprecated. Please use 'blocklists' instead.", stderror
32
- }
25
+ it 'has a blocklist' do
26
+ _(Rack::Attack.blocklists.key?("ip #{@bad_ip}")).must_equal true
27
+ end
33
28
 
34
29
  describe "a bad request" do
35
30
  before { get '/', {}, 'REMOTE_ADDR' => @bad_ip }
36
31
 
37
32
  it "should return a blocklist response" do
38
- last_response.status.must_equal 403
39
- last_response.body.must_equal "Forbidden\n"
33
+ _(last_response.status).must_equal 403
34
+ _(last_response.body).must_equal "Forbidden\n"
40
35
  end
41
36
 
42
37
  it "should tag the env" do
43
- last_request.env['rack.attack.matched'].must_equal "ip #{@bad_ip}"
44
- last_request.env['rack.attack.match_type'].must_equal :blocklist
38
+ _(last_request.env['rack.attack.matched']).must_equal "ip #{@bad_ip}"
39
+ _(last_request.env['rack.attack.match_type']).must_equal :blocklist
45
40
  end
46
41
 
47
42
  it_allows_ok_requests
@@ -55,43 +50,52 @@ describe 'Rack::Attack' do
55
50
 
56
51
  it('has a safelist') { Rack::Attack.safelists.key?("good ua") }
57
52
 
58
- it('has a whitelist with a deprication warning') {
59
- _, stderror = capture_io do
60
- Rack::Attack.whitelists.key?("good ua")
61
- end
62
- assert_match "[DEPRECATION] 'Rack::Attack.whitelists' is deprecated. Please use 'safelists' instead.", stderror
63
- }
64
-
65
53
  describe "with a request match both safelist & blocklist" do
66
54
  before { get '/', {}, 'REMOTE_ADDR' => @bad_ip, 'HTTP_USER_AGENT' => @good_ua }
67
55
 
68
56
  it "should allow safelists before blocklists" do
69
- last_response.status.must_equal 200
57
+ _(last_response.status).must_equal 200
70
58
  end
71
59
 
72
60
  it "should tag the env" do
73
- last_request.env['rack.attack.matched'].must_equal 'good ua'
74
- last_request.env['rack.attack.match_type'].must_equal :safelist
61
+ _(last_request.env['rack.attack.matched']).must_equal 'good ua'
62
+ _(last_request.env['rack.attack.match_type']).must_equal :safelist
75
63
  end
76
64
  end
77
65
  end
78
66
 
79
67
  describe '#blocklisted_response' do
80
68
  it 'should exist' do
81
- Rack::Attack.blocklisted_response.must_respond_to :call
82
- end
83
-
84
- it 'should give a deprication warning for blacklisted_response' do
85
- _, stderror = capture_io do
86
- Rack::Attack.blacklisted_response
87
- end
88
- assert_match "[DEPRECATION] 'Rack::Attack.blacklisted_response' is deprecated. Please use 'blocklisted_response' instead.", stderror
69
+ _(Rack::Attack.blocklisted_response).must_respond_to :call
89
70
  end
90
71
  end
91
72
 
92
73
  describe '#throttled_response' do
93
74
  it 'should exist' do
94
- Rack::Attack.throttled_response.must_respond_to :call
75
+ _(Rack::Attack.throttled_response).must_respond_to :call
76
+ end
77
+ end
78
+ end
79
+
80
+ describe 'enabled' do
81
+ it 'should be enabled by default' do
82
+ _(Rack::Attack.enabled).must_equal true
83
+ end
84
+
85
+ it 'should directly pass request when disabled' do
86
+ bad_ip = '1.2.3.4'
87
+ Rack::Attack.blocklist("ip #{bad_ip}") { |req| req.ip == bad_ip }
88
+
89
+ get '/', {}, 'REMOTE_ADDR' => bad_ip
90
+ _(last_response.status).must_equal 403
91
+
92
+ prev_enabled = Rack::Attack.enabled
93
+ begin
94
+ Rack::Attack.enabled = false
95
+ get '/', {}, 'REMOTE_ADDR' => bad_ip
96
+ _(last_response.status).must_equal 200
97
+ ensure
98
+ Rack::Attack.enabled = prev_enabled
95
99
  end
96
100
  end
97
101
  end
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'spec_helper'
2
4
 
3
5
  describe 'Rack::Attack.throttle' do
4
6
  before do
5
7
  @period = 60 # Use a long period; failures due to cache key rotation less likely
6
8
  Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
7
- Rack::Attack.throttle('ip/sec', :limit => 1, :period => @period) { |req| req.ip }
9
+ Rack::Attack.throttle('ip/sec', limit: 1, period: @period) { |req| req.ip }
8
10
  end
9
11
 
10
12
  it('should have a throttle') { Rack::Attack.throttles.key?('ip/sec') }
@@ -16,12 +18,19 @@ describe 'Rack::Attack.throttle' do
16
18
 
17
19
  it 'should set the counter for one request' do
18
20
  key = "rack::attack:#{Time.now.to_i / @period}:ip/sec:1.2.3.4"
19
- Rack::Attack.cache.store.read(key).must_equal 1
21
+ _(Rack::Attack.cache.store.read(key)).must_equal 1
20
22
  end
21
23
 
22
24
  it 'should populate throttle data' do
23
- data = { :count => 1, :limit => 1, :period => @period, epoch_time: Rack::Attack.cache.last_epoch_time.to_i }
24
- last_request.env['rack.attack.throttle_data']['ip/sec'].must_equal data
25
+ data = {
26
+ count: 1,
27
+ limit: 1,
28
+ period: @period,
29
+ epoch_time: Rack::Attack.cache.last_epoch_time.to_i,
30
+ discriminator: "1.2.3.4"
31
+ }
32
+
33
+ _(last_request.env['rack.attack.throttle_data']['ip/sec']).must_equal data
25
34
  end
26
35
  end
27
36
 
@@ -31,18 +40,26 @@ describe 'Rack::Attack.throttle' do
31
40
  end
32
41
 
33
42
  it 'should block the last request' do
34
- last_response.status.must_equal 429
43
+ _(last_response.status).must_equal 429
35
44
  end
36
45
 
37
46
  it 'should tag the env' do
38
- last_request.env['rack.attack.matched'].must_equal 'ip/sec'
39
- last_request.env['rack.attack.match_type'].must_equal :throttle
40
- last_request.env['rack.attack.match_data'].must_equal(:count => 2, :limit => 1, :period => @period, epoch_time: Rack::Attack.cache.last_epoch_time.to_i)
41
- last_request.env['rack.attack.match_discriminator'].must_equal('1.2.3.4')
47
+ _(last_request.env['rack.attack.matched']).must_equal 'ip/sec'
48
+ _(last_request.env['rack.attack.match_type']).must_equal :throttle
49
+
50
+ _(last_request.env['rack.attack.match_data']).must_equal(
51
+ count: 2,
52
+ limit: 1,
53
+ period: @period,
54
+ epoch_time: Rack::Attack.cache.last_epoch_time.to_i,
55
+ discriminator: "1.2.3.4"
56
+ )
57
+
58
+ _(last_request.env['rack.attack.match_discriminator']).must_equal('1.2.3.4')
42
59
  end
43
60
 
44
61
  it 'should set a Retry-After header' do
45
- last_response.headers['Retry-After'].must_equal @period.to_s
62
+ _(last_response.headers['Retry-After']).must_equal @period.to_s
46
63
  end
47
64
  end
48
65
  end
@@ -51,7 +68,7 @@ describe 'Rack::Attack.throttle with limit as proc' do
51
68
  before do
52
69
  @period = 60 # Use a long period; failures due to cache key rotation less likely
53
70
  Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
54
- Rack::Attack.throttle('ip/sec', :limit => lambda { |_req| 1 }, :period => @period) { |req| req.ip }
71
+ Rack::Attack.throttle('ip/sec', limit: lambda { |_req| 1 }, period: @period) { |req| req.ip }
55
72
  end
56
73
 
57
74
  it_allows_ok_requests
@@ -61,12 +78,19 @@ describe 'Rack::Attack.throttle with limit as proc' do
61
78
 
62
79
  it 'should set the counter for one request' do
63
80
  key = "rack::attack:#{Time.now.to_i / @period}:ip/sec:1.2.3.4"
64
- Rack::Attack.cache.store.read(key).must_equal 1
81
+ _(Rack::Attack.cache.store.read(key)).must_equal 1
65
82
  end
66
83
 
67
84
  it 'should populate throttle data' do
68
- data = { :count => 1, :limit => 1, :period => @period, epoch_time: Rack::Attack.cache.last_epoch_time.to_i }
69
- last_request.env['rack.attack.throttle_data']['ip/sec'].must_equal data
85
+ data = {
86
+ count: 1,
87
+ limit: 1,
88
+ period: @period,
89
+ epoch_time: Rack::Attack.cache.last_epoch_time.to_i,
90
+ discriminator: "1.2.3.4"
91
+ }
92
+
93
+ _(last_request.env['rack.attack.throttle_data']['ip/sec']).must_equal data
70
94
  end
71
95
  end
72
96
  end
@@ -75,7 +99,7 @@ describe 'Rack::Attack.throttle with period as proc' do
75
99
  before do
76
100
  @period = 60 # Use a long period; failures due to cache key rotation less likely
77
101
  Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
78
- Rack::Attack.throttle('ip/sec', :limit => lambda { |_req| 1 }, :period => lambda { |_req| @period }) { |req| req.ip }
102
+ Rack::Attack.throttle('ip/sec', limit: lambda { |_req| 1 }, period: lambda { |_req| @period }) { |req| req.ip }
79
103
  end
80
104
 
81
105
  it_allows_ok_requests
@@ -85,12 +109,19 @@ describe 'Rack::Attack.throttle with period as proc' do
85
109
 
86
110
  it 'should set the counter for one request' do
87
111
  key = "rack::attack:#{Time.now.to_i / @period}:ip/sec:1.2.3.4"
88
- Rack::Attack.cache.store.read(key).must_equal 1
112
+ _(Rack::Attack.cache.store.read(key)).must_equal 1
89
113
  end
90
114
 
91
115
  it 'should populate throttle data' do
92
- data = { :count => 1, :limit => 1, :period => @period, epoch_time: Rack::Attack.cache.last_epoch_time.to_i }
93
- last_request.env['rack.attack.throttle_data']['ip/sec'].must_equal data
116
+ data = {
117
+ count: 1,
118
+ limit: 1,
119
+ period: @period,
120
+ epoch_time: Rack::Attack.cache.last_epoch_time.to_i,
121
+ discriminator: "1.2.3.4"
122
+ }
123
+
124
+ _(last_request.env['rack.attack.throttle_data']['ip/sec']).must_equal data
94
125
  end
95
126
  end
96
127
  end
@@ -99,7 +130,7 @@ describe 'Rack::Attack.throttle with block retuning nil' do
99
130
  before do
100
131
  @period = 60
101
132
  Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
102
- Rack::Attack.throttle('ip/sec', :limit => 1, :period => @period) { |_| nil }
133
+ Rack::Attack.throttle('ip/sec', limit: 1, period: @period) { |_| nil }
103
134
  end
104
135
 
105
136
  it_allows_ok_requests
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'spec_helper'
2
4
 
3
5
  describe 'Rack::Attack.track' do
@@ -23,8 +25,9 @@ describe 'Rack::Attack.track' do
23
25
 
24
26
  it "should tag the env" do
25
27
  get '/'
26
- last_request.env['rack.attack.matched'].must_equal 'everything'
27
- last_request.env['rack.attack.match_type'].must_equal :track
28
+
29
+ _(last_request.env['rack.attack.matched']).must_equal 'everything'
30
+ _(last_request.env['rack.attack.match_type']).must_equal :track
28
31
  end
29
32
 
30
33
  describe "with a notification subscriber and two tracks" do
@@ -33,7 +36,7 @@ describe 'Rack::Attack.track' do
33
36
  # A second track
34
37
  Rack::Attack.track("homepage") { |req| req.path == "/" }
35
38
 
36
- ActiveSupport::Notifications.subscribe("rack.attack") do |*_args|
39
+ ActiveSupport::Notifications.subscribe("track.rack_attack") do |*_args|
37
40
  Counter.incr
38
41
  end
39
42
 
@@ -41,21 +44,23 @@ describe 'Rack::Attack.track' do
41
44
  end
42
45
 
43
46
  it "should notify twice" do
44
- Counter.check.must_equal 2
47
+ _(Counter.check).must_equal 2
45
48
  end
46
49
  end
47
50
 
48
51
  describe "without limit and period options" do
49
52
  it "should assign the track filter to a Check instance" do
50
53
  track = Rack::Attack.track("homepage") { |req| req.path == "/" }
51
- track.filter.class.must_equal Rack::Attack::Check
54
+
55
+ _(track.filter.class).must_equal Rack::Attack::Check
52
56
  end
53
57
  end
54
58
 
55
59
  describe "with limit and period options" do
56
60
  it "should assign the track filter to a Throttle instance" do
57
- track = Rack::Attack.track("homepage", :limit => 10, :period => 10) { |req| req.path == "/" }
58
- track.filter.class.must_equal Rack::Attack::Throttle
61
+ track = Rack::Attack.track("homepage", limit: 10, period: 10) { |req| req.path == "/" }
62
+
63
+ _(track.filter.class).must_equal Rack::Attack::Throttle
59
64
  end
60
65
  end
61
66
  end
@@ -1,10 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler/setup"
2
4
 
3
5
  require "minitest/autorun"
4
6
  require "minitest/pride"
5
7
  require "rack/test"
6
- require 'active_support'
7
- require 'action_dispatch'
8
+ require "rails"
8
9
 
9
10
  require "rack/attack"
10
11
 
@@ -13,10 +14,9 @@ if RUBY_ENGINE == "ruby"
13
14
  end
14
15
 
15
16
  def safe_require(name)
16
- begin
17
- require name
18
- rescue LoadError
19
- end
17
+ require name
18
+ rescue LoadError
19
+ nil
20
20
  end
21
21
 
22
22
  safe_require "connection_pool"
@@ -29,6 +29,7 @@ class MiniTest::Spec
29
29
  include Rack::Test::Methods
30
30
 
31
31
  before do
32
+ Rails.cache = nil
32
33
  @_original_throttled_response = Rack::Attack.throttled_response
33
34
  @_original_blocklisted_response = Rack::Attack.blocklisted_response
34
35
  end
@@ -55,8 +56,9 @@ class MiniTest::Spec
55
56
  def self.it_allows_ok_requests
56
57
  it "must allow ok requests" do
57
58
  get '/', {}, 'REMOTE_ADDR' => '127.0.0.1'
58
- last_response.status.must_equal 200
59
- last_response.body.must_equal 'Hello World'
59
+
60
+ _(last_response.status).must_equal 200
61
+ _(last_response.body).must_equal 'Hello World'
60
62
  end
61
63
  end
62
64
  end