jira-auto-tool 1.1.5 → 1.2.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 +4 -4
- data/README.md +72 -18
- data/config/examples/jira-auto-tool.env.yaml.erb +4 -1
- data/ext/no_wrappers_win.rb +6 -0
- data/features/configure_environment.feature +59 -14
- data/features/control_http_request_rate_limit.feature +19 -7
- data/features/create_sprints_using_existing_ones_as_reference.feature +32 -4
- data/lib/jira/auto/tool/environment_loader.rb +25 -1
- data/lib/jira/auto/tool/helpers/environment_based_value.rb +6 -1
- data/lib/jira/auto/tool/performer/planning_increment_sprint_creator.rb +4 -3
- data/lib/jira/auto/tool/rate_limited_jira_client/in_process_based.rb +26 -0
- data/lib/jira/auto/tool/rate_limited_jira_client/redis_based.rb +40 -0
- data/lib/jira/auto/tool/rate_limited_jira_client.rb +28 -28
- data/lib/jira/auto/tool/version.rb +1 -1
- data/lib/jira/auto/tool.rb +35 -27
- data/lib/tasks/version.rake +1 -1
- data/spec/jira/auto/tool/environment_loader_spec.rb +64 -9
- data/spec/jira/auto/tool/performer/planning_increment_sprint_creator_spec.rb +76 -13
- data/spec/jira/auto/tool/rate_limited_jira_client/in_process_based_spec.rb +65 -0
- data/spec/jira/auto/tool/rate_limited_jira_client/redis_based_spec.rb +56 -0
- data/spec/jira/auto/tool/rate_limited_jira_client_spec.rb +69 -48
- data/spec/jira/auto/tool_spec.rb +27 -24
- metadata +20 -4
- data/bin/setup +0 -8
- data/bin/setup-dev-win.bat +0 -3
data/lib/jira/auto/tool.rb
CHANGED
@@ -83,15 +83,17 @@ module Jira
|
|
83
83
|
end
|
84
84
|
|
85
85
|
def jira_client
|
86
|
-
RateLimitedJiraClient
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
86
|
+
RateLimitedJiraClient
|
87
|
+
.implementation_class_for(self)
|
88
|
+
.new(jira_client_options,
|
89
|
+
rate_interval_in_seconds:
|
90
|
+
jat_rate_interval_in_seconds_when_defined_else(
|
91
|
+
RateLimitedJiraClient::RedisBased::NO_RATE_INTERVAL_IN_SECONDS
|
92
|
+
).to_i,
|
93
|
+
rate_limit_per_interval:
|
94
|
+
jat_rate_limit_per_interval_when_defined_else(
|
95
|
+
RateLimitedJiraClient::RedisBased::NO_RATE_LIMIT_PER_INTERVAL
|
96
|
+
).to_i)
|
95
97
|
end
|
96
98
|
|
97
99
|
def jira_client_options
|
@@ -105,6 +107,7 @@ module Jira
|
|
105
107
|
}
|
106
108
|
end
|
107
109
|
|
110
|
+
# TODO: fix this overly complex logic
|
108
111
|
def jira_http_debug?
|
109
112
|
value = if config.key?(:jira_http_debug)
|
110
113
|
config[:jira_http_debug]
|
@@ -136,24 +139,29 @@ module Jira
|
|
136
139
|
jira_base_url + url
|
137
140
|
end
|
138
141
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
142
|
+
HOLDS_A_SECRET = true
|
143
|
+
ENVIRONMENT_BASED_VALUE_SYMBOLS =
|
144
|
+
([[:jira_api_token, HOLDS_A_SECRET]] + %i[
|
145
|
+
art_sprint_regex
|
146
|
+
expected_start_date_field_name
|
147
|
+
implementation_team_field_name
|
148
|
+
jat_rate_interval_in_seconds
|
149
|
+
jat_rate_limit_implementation
|
150
|
+
jat_rate_limit_per_interval
|
151
|
+
jat_tickets_for_team_sprint_ticket_dispatcher_jql
|
152
|
+
jira_board_name
|
153
|
+
jira_board_name_regex
|
154
|
+
jira_context_path
|
155
|
+
jira_http_debug
|
156
|
+
jira_project_key
|
157
|
+
jira_site_url
|
158
|
+
jira_sprint_field_name
|
159
|
+
jira_username
|
160
|
+
].collect { |value_name| [value_name, !HOLDS_A_SECRET] }).freeze
|
161
|
+
|
162
|
+
ENVIRONMENT_BASED_VALUE_SYMBOLS.each do |method_name, holds_a_secret|
|
163
|
+
holds_a_secret ||= false
|
164
|
+
define_overridable_environment_based_value(method_name, holds_a_secret)
|
157
165
|
end
|
158
166
|
|
159
167
|
def board_controller
|
data/lib/tasks/version.rake
CHANGED
@@ -5,6 +5,7 @@ require "rspec"
|
|
5
5
|
module Jira
|
6
6
|
module Auto
|
7
7
|
class Tool
|
8
|
+
# rubocop:disable Metrics/ClassLength
|
8
9
|
class EnvironmentLoader
|
9
10
|
RSpec.describe EnvironmentLoader do
|
10
11
|
let(:environment_loader) { described_class.new(tool, auto_setup: auto_setup) }
|
@@ -56,7 +57,7 @@ module Jira
|
|
56
57
|
end
|
57
58
|
|
58
59
|
describe "#tool_environment" do
|
59
|
-
let(:environment_keys) { %i[
|
60
|
+
let(:environment_keys) { %i[JIRA_SITE_URL JIRA_USERNAME JIRA_API_TOKEN] }
|
60
61
|
|
61
62
|
before do
|
62
63
|
allow(Environment).to receive(:constants).and_return(environment_keys)
|
@@ -64,14 +65,29 @@ module Jira
|
|
64
65
|
environment_keys.each do |environment_key|
|
65
66
|
allow(ENV).to receive(:fetch).with(environment_key.to_s, nil).and_return("#{environment_key} value")
|
66
67
|
end
|
68
|
+
|
69
|
+
allow(tool).to receive_messages(
|
70
|
+
jira_api_token_holds_a_secret?: true,
|
71
|
+
jira_site_url_holds_a_secret?: false,
|
72
|
+
jira_username_holds_a_secret?: false
|
73
|
+
)
|
67
74
|
end
|
68
75
|
|
69
76
|
it do
|
70
|
-
expect(environment_loader.tool_environment)
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
77
|
+
expect(environment_loader.tool_environment)
|
78
|
+
.to eq(
|
79
|
+
"JIRA_API_TOKEN" => "****",
|
80
|
+
"JIRA_SITE_URL" => "JIRA_SITE_URL value",
|
81
|
+
"JIRA_USERNAME" => "JIRA_USERNAME value"
|
82
|
+
)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "#environment_variable_holds_a_secret?" do
|
87
|
+
it "allows checking that the corresponding constant is a secret or not" do
|
88
|
+
allow(tool).to receive(:jira_api_token_holds_a_secret?).and_return(true)
|
89
|
+
|
90
|
+
expect(environment_loader).to be_environment_variable_holds_a_secret("JIRA_API_TOKEN")
|
75
91
|
end
|
76
92
|
end
|
77
93
|
|
@@ -132,10 +148,12 @@ module Jira
|
|
132
148
|
end
|
133
149
|
|
134
150
|
describe "#setup" do
|
135
|
-
|
136
|
-
allow(environment_loader).to receive_messages(file_path: "
|
151
|
+
before do
|
152
|
+
allow(environment_loader).to receive_messages(file_path: "path/to/config/file.yaml")
|
137
153
|
allow(environment_loader).to receive_messages(config_file_content: "file_content")
|
154
|
+
end
|
138
155
|
|
156
|
+
it "sets up the value according to the configuration file content" do
|
139
157
|
allow(YAML)
|
140
158
|
.to receive(:safe_load)
|
141
159
|
.with("file_content")
|
@@ -147,10 +165,25 @@ module Jira
|
|
147
165
|
|
148
166
|
environment_loader.send(:setup)
|
149
167
|
end
|
168
|
+
|
169
|
+
context "when the YAML parsing fails" do
|
170
|
+
it "generates an error message including the file name" do
|
171
|
+
allow(YAML).to receive(:safe_load)
|
172
|
+
.with("file_content")
|
173
|
+
.and_raise(RuntimeError, "could not find expected ':' at line 3 column 6)")
|
174
|
+
|
175
|
+
expect { environment_loader.send(:setup) }
|
176
|
+
.to raise_error(RuntimeError, <<~EOEMSG
|
177
|
+
path/to/config/file.yaml:188: failed to load with the following error:
|
178
|
+
could not find expected ':' at line 3 column 6)
|
179
|
+
EOEMSG
|
180
|
+
)
|
181
|
+
end
|
182
|
+
end
|
150
183
|
end
|
151
184
|
|
152
185
|
describe "#config_file_content" do
|
153
|
-
let(:file_path) { "
|
186
|
+
let(:file_path) { "path/to/config/file" }
|
154
187
|
let(:file_content) do
|
155
188
|
<<-YAML_ERB
|
156
189
|
---
|
@@ -174,9 +207,31 @@ module Jira
|
|
174
207
|
end
|
175
208
|
|
176
209
|
it { expect(environment_loader.send(:config_file_content)).to eq(erb_result) }
|
210
|
+
|
211
|
+
context "when the ERB evaluation fails" do
|
212
|
+
let(:file_content) do
|
213
|
+
<<-YAML_ERB
|
214
|
+
---
|
215
|
+
a_key: <%= 4*4 %>
|
216
|
+
another_key: <%= 4*4 %>
|
217
|
+
<%
|
218
|
+
raise "An error that should be caught and reported!"#{" "}
|
219
|
+
%>
|
220
|
+
YAML_ERB
|
221
|
+
end
|
222
|
+
|
223
|
+
it "generates an error message including the file name" do
|
224
|
+
expect { environment_loader.send(:config_file_content) }
|
225
|
+
.to raise_error(RuntimeError, <<~EOEMSG)
|
226
|
+
path/to/config/file:5: failed to load with the following error:
|
227
|
+
An error that should be caught and reported!
|
228
|
+
EOEMSG
|
229
|
+
end
|
230
|
+
end
|
177
231
|
end
|
178
232
|
end
|
179
233
|
end
|
234
|
+
# rubocop:enable Metrics/ClassLength
|
180
235
|
end
|
181
236
|
end
|
182
237
|
end
|
@@ -33,27 +33,90 @@ module Jira
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
+
# rubocop:disable Naming/VariableNumber, RSpec/IndexedLet
|
36
37
|
describe "#act_on_sprints_for_sprint_prefix" do
|
37
|
-
def get_sprint(name, attributes)
|
38
|
-
instance_double(Sprint, name: name, to_s: name, **attributes
|
38
|
+
def get_sprint(name, attributes = {})
|
39
|
+
instance_double(Sprint, name: name, to_s: name, **attributes,
|
40
|
+
parsed_name: Sprint::Name.parse(name))
|
39
41
|
end
|
40
42
|
|
41
|
-
let(:
|
43
|
+
let(:sprint_prefix) do
|
44
|
+
instance_double(Sprint::Prefix, name: "Food_Delivery", last_sprint: last_sprint)
|
45
|
+
end
|
46
|
+
|
47
|
+
context "when the sprints to create are all posterior to the last sprint from a naming perspective" do
|
48
|
+
let(:last_sprint) do
|
49
|
+
get_sprint "Food_Delivery_25.2.1", end_date: "2025-02-16 12:45", length_in_days: 10
|
50
|
+
end
|
51
|
+
|
52
|
+
let(:sprint_25_3_1) { get_sprint("Food_Delivery_25.3.1") }
|
53
|
+
let(:sprint_25_3_2) { get_sprint("Food_Delivery_25.3.2") }
|
54
|
+
let(:sprint_25_3_3) { get_sprint("Food_Delivery_25.3.3") }
|
55
|
+
let(:sprint_25_3_4) { get_sprint("Food_Delivery_25.3.4") }
|
56
|
+
|
57
|
+
before do
|
58
|
+
allow(updater).to receive(:create_sprint_for).with(last_sprint,
|
59
|
+
"Food_Delivery_25.3.1")
|
60
|
+
.and_return(sprint_25_3_1)
|
61
|
+
|
62
|
+
allow(updater).to receive(:create_sprint_for).with(sprint_25_3_1, "Food_Delivery_25.3.2")
|
63
|
+
.and_return(sprint_25_3_2)
|
64
|
+
|
65
|
+
allow(updater).to receive(:create_sprint_for).with(sprint_25_3_2, "Food_Delivery_25.3.3")
|
66
|
+
.and_return(sprint_25_3_3)
|
67
|
+
|
68
|
+
allow(updater).to receive(:create_sprint_for).with(sprint_25_3_3, "Food_Delivery_25.3.4")
|
69
|
+
.and_return(sprint_25_3_4)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "creates the expected number of sprints with the expected names" do
|
73
|
+
expect(sprint_prefix).to receive(:<<).with(sprint_25_3_1)
|
74
|
+
expect(sprint_prefix).to receive(:<<).with(sprint_25_3_2)
|
75
|
+
expect(sprint_prefix).to receive(:<<).with(sprint_25_3_3)
|
76
|
+
expect(sprint_prefix).to receive(:<<).with(sprint_25_3_4)
|
77
|
+
|
78
|
+
updater.act_on_sprints_for_sprint_prefix(sprint_prefix)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context "when some sprints to create are anterior to the last sprint from a naming perspective" do
|
83
|
+
let(:last_sprint) do
|
84
|
+
get_sprint "Food_Delivery_25.3.2", end_date: "2025-02-16 12:45", length_in_days: 10
|
85
|
+
end
|
86
|
+
|
87
|
+
let(:sprint_25_3_3) { get_sprint("Food_Delivery_25.3.3") }
|
88
|
+
let(:sprint_25_3_4) { get_sprint("Food_Delivery_25.3.4") }
|
89
|
+
|
90
|
+
before do
|
91
|
+
allow(updater).to receive(:create_sprint_for).with(last_sprint, "Food_Delivery_25.3.3")
|
92
|
+
.and_return(sprint_25_3_3)
|
93
|
+
|
94
|
+
allow(updater).to receive(:create_sprint_for).with(sprint_25_3_3, "Food_Delivery_25.3.4")
|
95
|
+
.and_return(sprint_25_3_4)
|
96
|
+
end
|
97
|
+
|
98
|
+
it "only creates the ones posterior to the last sprint" do
|
99
|
+
expect(sprint_prefix).to receive(:<<).with(sprint_25_3_3)
|
100
|
+
expect(sprint_prefix).to receive(:<<).with(sprint_25_3_4)
|
101
|
+
|
102
|
+
updater.act_on_sprints_for_sprint_prefix(sprint_prefix)
|
103
|
+
end
|
104
|
+
end
|
42
105
|
|
43
|
-
|
106
|
+
context "when requested sprints to create are anterior to the last sprint from a naming perspective" do
|
107
|
+
let(:last_sprint) do
|
108
|
+
get_sprint "Food_Delivery_25.4.1", end_date: "2025-02-16 12:45", length_in_days: 10
|
109
|
+
end
|
44
110
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
expect(updater).to receive(:create_sprint_for).ordered.with(nil, "Food_Delivery_25.3.2")
|
49
|
-
expect(updater).to receive(:create_sprint_for).ordered.with(nil, "Food_Delivery_25.3.3")
|
50
|
-
expect(updater).to receive(:create_sprint_for).ordered.with(nil, "Food_Delivery_25.3.4")
|
51
|
-
expect(sprint_prefix).to receive(:<<).exactly(4).times
|
111
|
+
it "does not create any since they would be anterior to the last sprint" do
|
112
|
+
expect(updater).not_to receive(:create_sprint_for)
|
113
|
+
expect(sprint_prefix).not_to receive(:<<)
|
52
114
|
|
53
|
-
|
115
|
+
updater.act_on_sprints_for_sprint_prefix(sprint_prefix)
|
116
|
+
end
|
54
117
|
end
|
55
|
-
# rubocop:enable RSpec/MultipleExpectations
|
56
118
|
end
|
119
|
+
# rubocop:enable Naming/VariableNumber, RSpec/IndexedLet
|
57
120
|
end
|
58
121
|
end
|
59
122
|
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "jira/auto/tool/rate_limited_jira_client/in_process_based"
|
4
|
+
|
5
|
+
module Jira
|
6
|
+
module Auto
|
7
|
+
class Tool
|
8
|
+
class RateLimitedJiraClient
|
9
|
+
class InProcessBased
|
10
|
+
RSpec.describe InProcessBased do
|
11
|
+
def build_client
|
12
|
+
described_class.new({}, rate_interval_in_seconds:, rate_limit_per_interval:)
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:client) { build_client }
|
16
|
+
|
17
|
+
let(:rate_interval_in_seconds) { 2 }
|
18
|
+
let(:rate_limit_per_interval) { 1 }
|
19
|
+
|
20
|
+
describe "#rate_limit" do
|
21
|
+
let(:rate_queue) { instance_double(Limiter::RateQueue) }
|
22
|
+
|
23
|
+
it "properly initializes the rate queue" do
|
24
|
+
allow(Limiter::RateQueue)
|
25
|
+
.to receive(:new).with(rate_limit_per_interval, interval: rate_interval_in_seconds)
|
26
|
+
.and_return(rate_queue)
|
27
|
+
|
28
|
+
allow(rate_queue).to receive(:shift)
|
29
|
+
|
30
|
+
expect(client.rate_limit { :do_nothing }).to eq(:do_nothing)
|
31
|
+
end
|
32
|
+
|
33
|
+
context "when rate limiting multiple requests" do
|
34
|
+
let(:rate_limit_4_calls_to_original_request_code) do
|
35
|
+
4.times { client.rate_limit { client.original_request(:get, "/path/to/resource") } }
|
36
|
+
end
|
37
|
+
|
38
|
+
before do
|
39
|
+
allow(client).to receive(:original_request).with(:get, "/path/to/resource")
|
40
|
+
allow(client).to receive_messages(rate_queue: rate_queue)
|
41
|
+
allow(rate_queue).to receive(:shift)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "shifts the queue and performs the request call" do
|
45
|
+
rate_limit_4_calls_to_original_request_code
|
46
|
+
|
47
|
+
expect(rate_queue).to have_received(:shift).exactly(4).times
|
48
|
+
expect(client).to have_received(:original_request).exactly(4).times
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "#rate_queue" do
|
53
|
+
let(:another_client) { build_client }
|
54
|
+
|
55
|
+
it "creating a second client will return another queue" do
|
56
|
+
expect(another_client.rate_queue).not_to equal(client.rate_queue)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "jira/auto/tool/rate_limited_jira_client/redis_based"
|
4
|
+
|
5
|
+
module Jira
|
6
|
+
module Auto
|
7
|
+
class Tool
|
8
|
+
class RateLimitedJiraClient
|
9
|
+
class RedisBased
|
10
|
+
RSpec.describe RedisBased do
|
11
|
+
describe "#rate_limit" do
|
12
|
+
let(:client) { described_class.new({}, rate_interval_in_seconds:, rate_limit_per_interval:) }
|
13
|
+
|
14
|
+
let(:rate_interval_in_seconds) { 2 }
|
15
|
+
let(:rate_limit_per_interval) { 1 }
|
16
|
+
let(:rate_limiter) { instance_double(Ratelimit) }
|
17
|
+
|
18
|
+
let(:rate_limit_4_calls_to_original_request_code) do
|
19
|
+
4.times { client.rate_limit { client.original_request(:get, "/path/to/resource") } }
|
20
|
+
end
|
21
|
+
|
22
|
+
before do
|
23
|
+
allow(described_class).to receive_messages(rate_limiter: rate_limiter)
|
24
|
+
allow(client).to receive(:original_request)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "uses :exec_within_threshold to control rate limiting" do
|
28
|
+
allow(rate_limiter).to receive(:exec_within_threshold)
|
29
|
+
|
30
|
+
rate_limit_4_calls_to_original_request_code
|
31
|
+
|
32
|
+
expect(rate_limiter)
|
33
|
+
.to have_received(:exec_within_threshold)
|
34
|
+
.with("jira_auto_tool_api_requests", { interval: rate_interval_in_seconds,
|
35
|
+
threshold: rate_limit_per_interval })
|
36
|
+
.exactly(4).times
|
37
|
+
end
|
38
|
+
|
39
|
+
it "adds keeps track of the rate limiter key calls" do
|
40
|
+
allow(rate_limiter).to receive(:exec_within_threshold).and_yield
|
41
|
+
allow(rate_limiter).to receive(:add)
|
42
|
+
|
43
|
+
rate_limit_4_calls_to_original_request_code
|
44
|
+
|
45
|
+
expect(rate_limiter)
|
46
|
+
.to have_received(:add)
|
47
|
+
.with("jira_auto_tool_api_requests")
|
48
|
+
.exactly(4).times
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -1,78 +1,99 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "rspec"
|
4
4
|
|
5
5
|
module Jira
|
6
6
|
module Auto
|
7
7
|
class Tool
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
let(:rate_interval) { 2 }
|
13
|
-
let(:rate_limit) { 1 }
|
14
|
-
let(:oauth_client) { instance_double(JIRA::OauthClient, request: nil, consumer: nil) }
|
15
|
-
let(:rate_limiter) { instance_double(Ratelimit) }
|
8
|
+
RSpec.describe RateLimitedJiraClient do
|
9
|
+
describe ".implementation_class_for" do
|
10
|
+
let(:result) { described_class.implementation_class_for(tool) }
|
11
|
+
let(:tool) { instance_double(Tool) }
|
16
12
|
|
13
|
+
context "when the rate limiting implementation is unspecified" do
|
17
14
|
before do
|
18
|
-
allow(
|
19
|
-
|
20
|
-
allow(JIRA::OauthClient).to receive_messages(new: oauth_client)
|
15
|
+
allow(tool).to receive_messages(jat_rate_limit_implementation_when_defined_else: nil)
|
16
|
+
end
|
21
17
|
|
22
|
-
|
18
|
+
it { expect(result).to eq(RateLimitedJiraClient::InProcessBased) }
|
19
|
+
end
|
23
20
|
|
24
|
-
|
25
|
-
|
21
|
+
context "when using in process based rate limiting" do
|
22
|
+
before do
|
23
|
+
allow(tool).to receive_messages(jat_rate_limit_implementation_when_defined_else: "in_process")
|
26
24
|
end
|
27
25
|
|
28
|
-
it
|
29
|
-
|
26
|
+
it { expect(result).to eq(RateLimitedJiraClient::InProcessBased) }
|
27
|
+
end
|
28
|
+
|
29
|
+
context "when using in Redis based rate limiting" do
|
30
|
+
before do
|
31
|
+
allow(tool).to receive_messages(jat_rate_limit_implementation_when_defined_else: "redis")
|
30
32
|
end
|
31
33
|
|
32
|
-
it
|
33
|
-
|
34
|
+
it { expect(result).to eq(RateLimitedJiraClient::RedisBased) }
|
35
|
+
end
|
36
|
+
|
37
|
+
context "when the request implementation is unexpected" do
|
38
|
+
before do
|
39
|
+
allow(tool)
|
40
|
+
.to receive_messages(jat_rate_limit_implementation_when_defined_else: "unexpected_implementation")
|
41
|
+
end
|
34
42
|
|
35
|
-
|
43
|
+
it do
|
44
|
+
expect { result }
|
45
|
+
.to raise_error(RuntimeError,
|
46
|
+
%("unexpected_implementation": unexpected rate limiting implementation specified!"))
|
36
47
|
end
|
48
|
+
end
|
49
|
+
end
|
37
50
|
|
38
|
-
|
39
|
-
|
40
|
-
|
51
|
+
RSpec.shared_examples "a rate limited client" do
|
52
|
+
before do
|
53
|
+
allow(client).to receive_messages(original_request: :response)
|
54
|
+
end
|
41
55
|
|
42
|
-
|
56
|
+
it "returns the response" do
|
57
|
+
expect(client.request(:get, "/path/to/resource")).to eq(:response)
|
58
|
+
end
|
43
59
|
|
44
|
-
|
45
|
-
|
46
|
-
.with("jira_auto_tool_api_requests", { interval: rate_interval, threshold: rate_limit })
|
47
|
-
.exactly(4).times
|
48
|
-
end
|
60
|
+
it "calls the original request method" do
|
61
|
+
client.request(:get, "/path/to/resource")
|
49
62
|
|
50
|
-
|
51
|
-
|
63
|
+
expect(client).to have_received(:original_request).with(:get, "/path/to/resource")
|
64
|
+
end
|
65
|
+
end
|
52
66
|
|
53
|
-
|
67
|
+
describe "#request" do
|
68
|
+
let(:client) { described_class.new({}, rate_interval_in_seconds:, rate_limit_per_interval:) }
|
54
69
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
.exactly(4).times
|
59
|
-
end
|
60
|
-
end
|
70
|
+
context "when the rate limiter is not needed" do
|
71
|
+
let(:rate_interval_in_seconds) { 0 }
|
72
|
+
let(:rate_limit_per_interval) { 0 }
|
61
73
|
|
62
|
-
|
63
|
-
let(:rate_limit) { 0 }
|
74
|
+
it_behaves_like "a rate limited client"
|
64
75
|
|
65
|
-
|
66
|
-
|
76
|
+
it "does not use the rate limiter" do
|
77
|
+
allow(client).to receive(:original_request).with(:get, "/path/to/resource")
|
78
|
+
expect(client).not_to receive(:rate_limit)
|
67
79
|
|
68
|
-
|
69
|
-
|
80
|
+
client.request(:get, "/path/to/resource")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context "when the rate limiter is needed" do
|
85
|
+
let(:rate_interval_in_seconds) { 2 }
|
86
|
+
let(:rate_limit_per_interval) { 1 }
|
70
87
|
|
71
|
-
|
72
|
-
|
88
|
+
it_behaves_like "a rate limited client" do
|
89
|
+
before { allow(client).to receive(:rate_limit).and_yield }
|
90
|
+
end
|
91
|
+
|
92
|
+
it "uses the rate limiter" do
|
93
|
+
allow(client).to receive(:original_request).with(:get, "/path/to/resource")
|
94
|
+
expect(client).to receive(:rate_limit).and_yield
|
73
95
|
|
74
|
-
|
75
|
-
end
|
96
|
+
client.request(:get, "/path/to/resource")
|
76
97
|
end
|
77
98
|
end
|
78
99
|
end
|