heroku_hatchet 6.0.0 → 7.1.3

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.
@@ -218,12 +218,13 @@ module Hatchet
218
218
  "Content-Type" => "application/json"
219
219
  }.merge(options[:headers] || {})
220
220
  options[:body] = JSON.generate(options[:body]) if options[:body]
221
+ options[:expects] << 429 if options[:expects]
221
222
 
222
223
  Hatchet::RETRIES.times.retry do
223
224
  PlatformAPI.rate_throttle.call do
224
225
  connection = Excon.new("https://api.heroku.com")
225
226
 
226
- return connection.request(options)
227
+ connection.request(options)
227
228
  end
228
229
  end
229
230
  end
@@ -1,3 +1,3 @@
1
1
  module Hatchet
2
- VERSION = "6.0.0"
2
+ VERSION = "7.1.3"
3
3
  end
@@ -1,15 +1,55 @@
1
1
  require("spec_helper")
2
2
 
3
3
  describe "AllowFailureGitTest" do
4
+ describe "release failures" do
5
+ let(:release_fail_proc) {
6
+ Proc.new do
7
+ File.open("Procfile", "w+") do |f|
8
+ f.write <<~EOM
9
+ release: echo "failing on release" && exit 1
10
+ EOM
11
+ end
12
+ end
13
+ }
14
+
15
+ it "is marked as a failure if the release fails" do
16
+ app = Hatchet::GitApp.new("default_ruby", before_deploy: release_fail_proc, retries: 2)
17
+ def app.retry_error_message(*args); @test_attempts_count ||= 0; @test_attempts_count += 1; "" end
18
+ def app.test_attempts_count; @test_attempts_count ; end
19
+
20
+ expect {
21
+ app.deploy {}
22
+ }.to raise_error { |error|
23
+ expect(error).to be_a(Hatchet::App::FailedReleaseError)
24
+ expect(error.message).to_not match("Everything up-to-date")
25
+ }
26
+
27
+ expect(app.test_attempts_count).to eq(2)
28
+ end
29
+
30
+ it "works when failure is allowed" do
31
+ Hatchet::GitApp.new("default_ruby", before_deploy: release_fail_proc, allow_failure: true, retries: 3).tap do |app|
32
+ def app.retry_error_message(*args); @test_attempts_count ||= 0; @test_attempts_count += 1; "" end
33
+ def app.test_attempts_count; @test_attempts_count ; end
34
+
35
+ app.deploy do
36
+ expect(app.output).to match("failing on release")
37
+ expect(app.test_attempts_count).to eq(nil)
38
+ end
39
+ end
40
+ end
41
+ end
42
+
4
43
  it "allowed failure" do
5
44
  Hatchet::GitApp.new("no_lockfile", allow_failure: true).deploy do |app|
6
- puts app.output
7
45
  expect(app.deployed?).to be_falsey
8
46
  expect(app.output).to match("Gemfile.lock required")
9
47
  end
10
48
  end
11
49
 
12
50
  it "failure with no flag" do
13
- expect { Hatchet::GitApp.new("no_lockfile").deploy }.to(raise_error(Hatchet::App::FailedDeploy))
51
+ expect {
52
+ Hatchet::GitApp.new("no_lockfile").deploy {}
53
+ }.to(raise_error(Hatchet::App::FailedDeploy))
14
54
  end
15
55
  end
@@ -1,6 +1,42 @@
1
1
  require("spec_helper")
2
2
 
3
3
  describe "AppTest" do
4
+ it "rate throttles `git push` " do
5
+ app = Hatchet::GitApp.new("default_ruby")
6
+ def app.git_push_heroku_yall
7
+ @_git_push_heroku_yall_call_count ||= 0
8
+ @_git_push_heroku_yall_call_count += 1
9
+ if @_git_push_heroku_yall_call_count >= 2
10
+ "Success"
11
+ else
12
+ raise Hatchet::App::FailedDeployError.new(self, "message", output: "Your account reached the API rate limit Please wait a few minutes before making new requests")
13
+ end
14
+ end
15
+
16
+ def app.sleep_called?; @sleep_called; end
17
+
18
+ def app.what_is_git_push_heroku_yall_call_count; @_git_push_heroku_yall_call_count; end
19
+ app.push_without_retry!
20
+
21
+ expect(app.what_is_git_push_heroku_yall_call_count).to be(2)
22
+ end
23
+
24
+ it "calls reaper if cannot create an app" do
25
+ app = Hatchet::App.new("default_ruby", buildpacks: [:default])
26
+ def app.heroku_api_create_app(*args); raise StandardError.new("made you look"); end
27
+
28
+ reaper = app.reaper
29
+
30
+ def reaper.cycle(app_exception_message: ); @app_exception_message = app_exception_message; end
31
+ def reaper.recorded_app_exception_message; @app_exception_message; end
32
+
33
+ expect {
34
+ app.create_app
35
+ }.to raise_error("made you look")
36
+
37
+ expect(reaper.recorded_app_exception_message).to match("made you look")
38
+ end
39
+
4
40
  it "app with default" do
5
41
  app = Hatchet::App.new("default_ruby", buildpacks: [:default])
6
42
  expect(app.buildpacks.first).to match("https://github.com/heroku/heroku-buildpack-ruby")
@@ -13,6 +49,37 @@ describe "AppTest" do
13
49
  expect(app.platform_api.app.info(app.name)["build_stack"]["name"]).to eq(stack)
14
50
  end
15
51
 
52
+ it "marks itself 'finished' when done in block mode" do
53
+ app = Hatchet::Runner.new("default_ruby")
54
+
55
+ def app.push_with_retry!; nil; end
56
+ app.deploy do |app|
57
+ expect(app.platform_api.app.info(app.name)["maintenance"]).to be_falsey
58
+ end
59
+
60
+ # After the app is updated, there's no guarantee it will still exist
61
+ # so we cannot rely on an api call to determine maintenance mode
62
+ app_update_info = app.instance_variable_get(:"@app_update_info")
63
+ expect(app_update_info["name"]).to eq(app.name)
64
+ expect(app_update_info["maintenance"]).to be_truthy
65
+ end
66
+
67
+ it "marks itself 'finished' when done in non-block mode" do
68
+ app = Hatchet::Runner.new("default_ruby")
69
+
70
+ def app.push_with_retry!; nil; end
71
+ app.deploy
72
+ expect(app.platform_api.app.info(app.name)["maintenance"]).to be_falsey
73
+
74
+ app.teardown!
75
+
76
+ # After the app is updated, there's no guarantee it will still exist
77
+ # so we cannot rely on an api call to determine maintenance mode
78
+ app_update_info = app.instance_variable_get(:"@app_update_info")
79
+ expect(app_update_info["name"]).to eq(app.name)
80
+ expect(app_update_info["maintenance"]).to be_truthy
81
+ end
82
+
16
83
  it "before deploy" do
17
84
  @called = false
18
85
  @dir = false
@@ -65,23 +132,95 @@ describe "AppTest" do
65
132
  end
66
133
 
67
134
  it "run" do
68
- app = Hatchet::GitApp.new("default_ruby")
135
+ skip("Must set HATCHET_EXPENSIVE_MODE") unless ENV["HATCHET_EXPENSIVE_MODE"]
136
+
137
+ app = Hatchet::GitApp.new("default_ruby", run_multi: true)
69
138
  app.deploy do
70
139
  expect(app.run("ls -a Gemfile 'foo bar #baz'")).to match(/ls: cannot access 'foo bar #baz': No such file or directory\s+Gemfile/)
71
140
  expect((0 != $?.exitstatus)).to be_truthy
72
- sleep(4)
141
+
73
142
  app.run("ls erpderp", heroku: ({ "exit-code" => (Hatchet::App::SkipDefaultOption) }))
74
143
  expect((0 == $?.exitstatus)).to be_truthy
75
- sleep(4)
144
+
76
145
  app.run("ls erpderp", heroku: ({ "no-tty" => nil }))
77
146
  expect((0 != $?.exitstatus)).to be_truthy
78
- sleep(4)
147
+
79
148
  expect(app.run("echo \\$HELLO \\$NAME", raw: true, heroku: ({ "env" => "HELLO=ohai;NAME=world" }))).to match(/ohai world/)
80
- sleep(4)
149
+
81
150
  expect(app.run("echo \\$HELLO \\$NAME", raw: true, heroku: ({ "env" => "" }))).to_not match(/ohai world/)
82
- sleep(4)
151
+
83
152
  random_name = SecureRandom.hex
84
153
  expect(app.run("mkdir foo; touch foo/#{random_name}; ls foo/")).to match(/#{random_name}/)
85
154
  end
86
155
  end
156
+
157
+ class AtomicCount
158
+ attr_reader :value
159
+
160
+ def initialize(value)
161
+ @value = value
162
+ @mutex = Mutex.new
163
+ end
164
+
165
+ # In MRI the `+=` is not atomic, it is two seperate virtual machine
166
+ # instructions. To protect against race conditions, we can lock with a mutex
167
+ def add(val)
168
+ @mutex.synchronize do
169
+ @value += val
170
+ end
171
+ end
172
+ end
173
+
174
+ it "run multi" do
175
+ skip("Must set HATCHET_EXPENSIVE_MODE") unless ENV["HATCHET_EXPENSIVE_MODE"]
176
+
177
+ @run_count = AtomicCount.new(0)
178
+ app = Hatchet::GitApp.new("default_ruby", run_multi: true)
179
+ app.deploy do
180
+ app.run_multi("ls") { |out| expect(out).to include("Gemfile"); @run_count.add(1) }
181
+ app.run_multi("blerg -v") { |_, status| expect(status.success?).to be_falsey; @run_count.add(1) }
182
+ app.run_multi("ruby -v") do |out, status|
183
+ expect(out).to include("ruby")
184
+ expect(status.success?).to be_truthy
185
+
186
+ @run_count.add(1)
187
+ end
188
+
189
+ expect(app.platform_api.formation.list(app.name).detect {|ps| ps["type"] == "web"}["size"].downcase).to_not eq("free")
190
+ end
191
+
192
+ # After the deploy block exits `teardown!` is called
193
+ # this ensures all `run_multi` commands have exited and the dyno should be scaled down
194
+ expect(@run_count.value).to eq(3)
195
+ end
196
+
197
+ describe "running concurrent tests in different examples works" do
198
+ # This is not a great pattern if we're running tests via a parallel runner
199
+ #
200
+ # For example this will be guaranteed to be called, not just once, but at least once for every process
201
+ # that needs to run a test. In the best case it will only fire once, in the worst case it will fire N times
202
+ # if there are N tests. It is effectively the same as a `before(:each)`
203
+ #
204
+ # Documented here: https://github.com/grosser/parallel_split_test/pull/22/files
205
+ before(:all) do
206
+ skip("Must set HATCHET_EXPENSIVE_MODE") unless ENV["HATCHET_EXPENSIVE_MODE"]
207
+
208
+ @app = Hatchet::GitApp.new("default_ruby", run_multi: true)
209
+ @app.deploy
210
+ end
211
+
212
+ after(:all) do
213
+ @app.teardown! if @app
214
+ end
215
+
216
+ it "test one" do
217
+ expect(@app.run("ls")).to include("Gemfile")
218
+ expect(@app.platform_api.formation.list(@app.name).detect {|ps| ps["type"] == "web"}["size"].downcase).to_not eq("free")
219
+ end
220
+
221
+ it "test two" do
222
+ expect(@app.run("ruby -v")).to include("ruby")
223
+ expect(@app.platform_api.formation.list(@app.name).detect {|ps| ps["type"] == "web"}["size"].downcase).to_not eq("free")
224
+ end
225
+ end
87
226
  end
@@ -4,7 +4,8 @@ describe "CIFourTest" do
4
4
  it "error with bad app" do
5
5
  string = SecureRandom.hex
6
6
 
7
- Hatchet::GitApp.new("default_ruby").run_ci do |test_run|
7
+ app = Hatchet::GitApp.new("default_ruby")
8
+ app.run_ci do |test_run|
8
9
  expect(test_run.output).to_not match(string)
9
10
  expect(test_run.output).to match("Installing rake")
10
11
 
@@ -14,7 +15,15 @@ describe "CIFourTest" do
14
15
  expect(test_run.output).to match(string)
15
16
  expect(test_run.output).to match("Using rake")
16
17
  expect(test_run.output).to_not match("Installing rake")
18
+
19
+ expect(app.platform_api.app.info(app.name)["maintenance"]).to be_falsey
17
20
  end
21
+
22
+ # After the app is updated, there's no guarantee it will still exist
23
+ # so we cannot rely on an api call to determine maintenance mode
24
+ app_update_info = app.instance_variable_get(:"@app_update_info")
25
+ expect(app_update_info["name"]).to eq(app.name)
26
+ expect(app_update_info["maintenance"]).to be_truthy
18
27
  end
19
28
 
20
29
  it "error with bad app" do
@@ -1,9 +1,15 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe "GitAppTest" do
4
- it "can deploy git app" do
5
- Hatchet::GitApp.new("rails5_ruby_schema_format").deploy do |app|
6
- expect(app.run("ruby -v")).to match("2.6.6")
4
+ it "can deploy git app to the main branch" do
5
+ Hatchet::GitApp.new("lock_fail_main", allow_failure: true).deploy do |app|
6
+ expect(app.output).to match("INTENTIONAL ERROR")
7
7
  end
8
8
  end
9
+
10
+ it "returns the correct branch name on circle CI" do
11
+ skip("only runs on circle") unless ENV["CIRCLE_BRANCH"]
12
+
13
+ expect(Hatchet.git_branch).to eq(ENV["CIRCLE_BRANCH"])
14
+ end
9
15
  end
@@ -2,6 +2,10 @@ require "spec_helper"
2
2
  require 'yaml'
3
3
 
4
4
  describe "LockTest" do
5
+ before(:all) do
6
+ puts(`bundle exec hatchet lock`)
7
+ end
8
+
5
9
  it "app with failure can be locked to prior commit" do
6
10
  Hatchet::GitApp.new("lock_fail").deploy do |app|
7
11
  expect(app.deployed?).to be_truthy
@@ -9,11 +13,69 @@ describe "LockTest" do
9
13
  end
10
14
 
11
15
  it "app with failure can be locked to master" do
12
- puts(`bundle exec hatchet lock`)
13
16
  lock = YAML.load_file("hatchet.lock")
14
17
  name, branch = lock.select { |k, v| k.end_with?("lock_fail_master") }.first
15
18
 
16
19
  expect(name).to eq("repo_fixtures/repos/lock/lock_fail_master")
17
20
  expect(branch).to eq("master")
18
21
  end
22
+
23
+ it "app with failure can be locked to main" do
24
+ lock = YAML.load_file("hatchet.lock")
25
+ name, branch = lock.select { |k, v| k.end_with?("lock_fail_main") }.first
26
+
27
+ expect(name).to eq("repo_fixtures/repos/lock/lock_fail_main")
28
+ expect(branch).to eq("main")
29
+ end
30
+ end
31
+
32
+ describe "isolated lock tests" do
33
+ it "works when there's no hatchet.lock" do
34
+ Dir.mktmpdir do |dir|
35
+ dir = Pathname.new(dir)
36
+
37
+ dir.join("hatchet.json").open("w+") do |f|
38
+ f.puts %Q{{ "foo": ["sharpstone/lock_fail_main_default_is_master"] }}
39
+ end
40
+
41
+ output = `cd #{dir} && hatchet lock 2>&1`
42
+
43
+ raise "Expected cmd `hatchet lock` to succeed, but it did not: #{output}" unless $?.success?
44
+ expect(output).to include("locking")
45
+
46
+ lockfile_contents = dir.join('hatchet.lock').read
47
+ expect(lockfile_contents).to include("repos/foo/lock_fail_main_default_is_master")
48
+ end
49
+ end
50
+
51
+ it "works when a project is locked to main but the default branch is master" do
52
+ Dir.mktmpdir do |dir|
53
+ dir = Pathname.new(dir)
54
+
55
+ dir.join("hatchet.json").open("w+") do |f|
56
+ f.puts %Q{{ "foo": ["sharpstone/lock_fail_main_default_is_master"] }}
57
+ end
58
+
59
+ dir.join("hatchet.lock").open("w+") do |f|
60
+ f.puts <<~EOM
61
+ ---
62
+ - - "./repos/foo/lock_fail_main_default_is_master"
63
+ - main
64
+ EOM
65
+ end
66
+
67
+ output = `cd #{dir} && hatchet install 2>&1`
68
+
69
+ raise "Expected cmd `hatchet install` to succeed, but it did not:\n#{output}" unless $?.success?
70
+ expect(output).to include("Installing")
71
+
72
+ lockfile_contents = dir.join('hatchet.lock').read
73
+ contents = YAML.safe_load(lockfile_contents).to_h
74
+ expect(contents).to eq({"./repos/foo/lock_fail_main_default_is_master" => "main"})
75
+
76
+ contents.each do |repo_dir, commit_or_branch|
77
+ expect(`cd #{dir.join(repo_dir)} && git describe --contains --all HEAD`).to match("main")
78
+ end
79
+ end
80
+ end
19
81
  end
@@ -0,0 +1,169 @@
1
+ require "spec_helper"
2
+
3
+ describe "Reaper" do
4
+ it "destroy all" do
5
+ reaper = Hatchet::Reaper.new(api_rate_limit: Object.new, hatchet_app_limit: 1, io: StringIO.new)
6
+
7
+ def reaper.get_heroku_apps
8
+ @mock_apps ||= [
9
+ {"name" => "hatchet-t-unfinished", "id" => 2, "maintenance" => false, "created_at" => Time.now.to_s},
10
+ {"name" => "hatchet-t-foo", "id" => 1, "maintenance" => true, "created_at" => Time.now.to_s}
11
+ ]
12
+ end
13
+ def reaper.destroy_with_log(*args); @destroy_with_log_count ||= 0; @destroy_with_log_count += 1; end
14
+
15
+ reaper.destroy_all
16
+
17
+ expect(reaper.instance_variable_get("@destroy_with_log_count")).to eq(1)
18
+ end
19
+
20
+ describe "cycle" do
21
+ it "does not delete anything if under the limit" do
22
+ reaper = Hatchet::Reaper.new(api_rate_limit: Object.new, hatchet_app_limit: 1, io: StringIO.new)
23
+
24
+ def reaper.get_heroku_apps
25
+ @called_get_heroku_apps = true
26
+
27
+ @mock_apps ||= [{"name" => "hatchet-t-foo", "id" => 1, "maintenance" => true, "created_at" => Time.now.to_s}]
28
+ end
29
+ def reaper.check_get_heroku_apps_called; @called_get_heroku_apps ; end
30
+ def reaper.reap_once; raise "should not be called"; end
31
+
32
+ reaper.cycle
33
+
34
+ expect(reaper.check_get_heroku_apps_called).to be_truthy
35
+ end
36
+
37
+ it "deletes a maintenance mode app on error" do
38
+ reaper = Hatchet::Reaper.new(api_rate_limit: Object.new, hatchet_app_limit: 1, io: StringIO.new)
39
+
40
+ def reaper.get_heroku_apps
41
+ @mock_apps ||= [
42
+ {"name" => "hatchet-t-unfinished", "id" => 2, "maintenance" => false, "created_at" => Time.now.to_s},
43
+ {"name" => "hatchet-t-foo", "id" => 1, "maintenance" => true, "created_at" => Time.now.to_s}
44
+ ]
45
+ end
46
+ def reaper.destroy_with_log(name: , id: )
47
+ @reaper_destroy_called_with = {"name" => name, "id" => id}
48
+ end
49
+ def reaper.destroy_called_with; @reaper_destroy_called_with; end
50
+
51
+ reaper.cycle(app_exception_message: true)
52
+
53
+ expect(reaper.destroy_called_with).to eq({"name" => "hatchet-t-foo", "id" => 1})
54
+ end
55
+
56
+ it "deletes maintenance mode app when over limit" do
57
+ reaper = Hatchet::Reaper.new(api_rate_limit: Object.new, hatchet_app_limit: 0, io: StringIO.new)
58
+
59
+ def reaper.get_heroku_apps
60
+ @mock_apps ||= [{"name" => "hatchet-t-foo", "id" => 1, "maintenance" => true, "created_at" => Time.now.to_s}]
61
+ end
62
+ def reaper.destroy_with_log(name: , id: )
63
+ @reaper_destroy_called_with = {"name" => name, "id" => id}
64
+ end
65
+ def reaper.destroy_called_with; @reaper_destroy_called_with; end
66
+
67
+ reaper.cycle
68
+
69
+ expect(reaper.destroy_called_with).to eq({"name" => "hatchet-t-foo", "id" => 1})
70
+ end
71
+
72
+ it "deletes an old app that is past TLL" do
73
+ reaper = Hatchet::Reaper.new(api_rate_limit: Object.new, hatchet_app_limit: 0, io: StringIO.new)
74
+
75
+ def reaper.get_heroku_apps
76
+ two_days_ago = DateTime.now.new_offset(0) - 2
77
+ @mock_apps ||= [{"name" => "hatchet-t-foo", "id" => 1, "maintenance" => false, "created_at" => two_days_ago.to_s }]
78
+ end
79
+ def reaper.destroy_with_log(name: , id: )
80
+ @reaper_destroy_called_with = {"name" => name, "id" => id}
81
+ end
82
+ def reaper.destroy_called_with; @reaper_destroy_called_with; end
83
+
84
+ reaper.cycle
85
+
86
+ expect(reaper.destroy_called_with).to eq({"name" => "hatchet-t-foo", "id" => 1})
87
+ end
88
+
89
+ it "sleeps, refreshes app list, and tries again when an old app is not past TTL" do
90
+ warning = StringIO.new
91
+ reaper = Hatchet::Reaper.new(api_rate_limit: Object.new, hatchet_app_limit: 1, initial_sleep: 0, io: warning)
92
+
93
+ def reaper.get_heroku_apps
94
+ now = DateTime.now.new_offset(0)
95
+ @mock_apps ||= [{"name" => "hatchet-t-foo", "id" => 1, "maintenance" => false, "created_at" => now.to_s }]
96
+ end
97
+ def reaper.destroy_with_log(name: , id: )
98
+ @reaper_destroy_called_with = {"name" => name, "id" => id}
99
+ end
100
+ def reaper.destroy_called_with; @reaper_destroy_called_with; end
101
+ def reaper.sleep(val)
102
+ @_slept_for = val
103
+ end
104
+
105
+ def reaper.get_slept_for_val; @_slept_for; end
106
+
107
+ reaper.cycle(app_exception_message: true)
108
+
109
+ expect(reaper.get_slept_for_val).to eq(0)
110
+ expect(reaper.destroy_called_with).to eq(nil)
111
+
112
+ expect(warning.string).to match("WARNING")
113
+ expect(warning.string).to match("total_app_count: 1, hatchet_app_count: 1/#{Hatchet::Reaper::HATCHET_APP_LIMIT}, finished: 0, unfinished: 1")
114
+ end
115
+ end
116
+
117
+ describe "app age" do
118
+ it "calculates young apps" do
119
+ time_now = DateTime.parse("2020-07-28T14:40:00Z")
120
+ age = Hatchet::Reaper::AppAge.new(created_at: time_now, time_now: time_now, ttl_minutes: 1)
121
+ expect(age.in_minutes).to eq(0.0)
122
+ expect(age.too_young_to_die?).to be_truthy
123
+ expect(age.can_delete?).to be_falsey
124
+ expect(age.sleep_for_ttl).to eq(60)
125
+ end
126
+
127
+ it "calculates old apps" do
128
+ time_now = DateTime.parse("2020-07-28T14:40:00Z")
129
+ created_at = time_now - 2
130
+ age = Hatchet::Reaper::AppAge.new(created_at: created_at, time_now: time_now, ttl_minutes: 1)
131
+ expect(age.in_minutes).to eq(2880.0)
132
+ expect(age.too_young_to_die?).to be_falsey
133
+ expect(age.can_delete?).to be_truthy
134
+ expect(age.sleep_for_ttl).to eq(0)
135
+ end
136
+ end
137
+
138
+ describe "reaper throttle" do
139
+ it "increments and decrements based on min_sleep" do
140
+ reaper_throttle = Hatchet::Reaper::ReaperThrottle.new(initial_sleep: 2)
141
+ reaper_throttle.call(max_sleep: 5) do |sleep_for|
142
+ expect(sleep_for).to eq(2)
143
+ end
144
+ reaper_throttle.call(max_sleep: 5) do |sleep_for|
145
+ expect(sleep_for).to eq(4)
146
+ end
147
+ reaper_throttle.call(max_sleep: 5) do |sleep_for|
148
+ expect(sleep_for).to eq(5)
149
+ end
150
+ # The throttle is now reset since it hit the min_sleep value
151
+
152
+ reaper_throttle.call(max_sleep: 5) do |sleep_for|
153
+ expect(sleep_for).to eq(2)
154
+ end
155
+ end
156
+ end
157
+
158
+ it "over limit" do
159
+ reaper = Hatchet::Reaper.new(api_rate_limit: -> (){}, io: StringIO.new)
160
+ def reaper.hatchet_app_count; Hatchet::Reaper::HATCHET_APP_LIMIT + 1; end
161
+
162
+ expect(reaper.over_limit?).to be_truthy
163
+
164
+ reaper = Hatchet::Reaper.new(api_rate_limit: -> (){}, io: StringIO.new)
165
+ def reaper.hatchet_app_count; Hatchet::Reaper::HATCHET_APP_LIMIT - 1; end
166
+
167
+ expect(reaper.over_limit?).to be_falsey
168
+ end
169
+ end