knapsack_pro 1.20.2 → 1.21.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/README.md +24 -12
  4. data/lib/knapsack_pro.rb +6 -0
  5. data/lib/knapsack_pro/adapters/base_adapter.rb +16 -0
  6. data/lib/knapsack_pro/adapters/rspec_adapter.rb +11 -9
  7. data/lib/knapsack_pro/allocator.rb +7 -5
  8. data/lib/knapsack_pro/allocator_builder.rb +2 -1
  9. data/lib/knapsack_pro/base_allocator_builder.rb +41 -10
  10. data/lib/knapsack_pro/build_distribution_fetcher.rb +57 -0
  11. data/lib/knapsack_pro/client/api/v1/build_distributions.rb +13 -0
  12. data/lib/knapsack_pro/client/connection.rb +30 -12
  13. data/lib/knapsack_pro/config/env.rb +4 -0
  14. data/lib/knapsack_pro/queue_allocator.rb +7 -5
  15. data/lib/knapsack_pro/queue_allocator_builder.rb +2 -1
  16. data/lib/knapsack_pro/runners/queue/rspec_runner.rb +7 -0
  17. data/lib/knapsack_pro/runners/rspec_runner.rb +5 -2
  18. data/lib/knapsack_pro/slow_test_file_determiner.rb +28 -0
  19. data/lib/knapsack_pro/slow_test_file_finder.rb +27 -0
  20. data/lib/knapsack_pro/test_case_detectors/rspec_test_example_detector.rb +18 -2
  21. data/lib/knapsack_pro/test_case_mergers/base_merger.rb +29 -0
  22. data/lib/knapsack_pro/test_case_mergers/rspec_merger.rb +34 -0
  23. data/lib/knapsack_pro/test_file_finder.rb +43 -5
  24. data/lib/knapsack_pro/test_files_with_test_cases_composer.rb +22 -0
  25. data/lib/knapsack_pro/version.rb +1 -1
  26. data/spec/knapsack_pro/adapters/base_adapter_spec.rb +55 -0
  27. data/spec/knapsack_pro/adapters/rspec_adapter_spec.rb +61 -25
  28. data/spec/knapsack_pro/allocator_builder_spec.rb +7 -3
  29. data/spec/knapsack_pro/allocator_spec.rb +7 -5
  30. data/spec/knapsack_pro/base_allocator_builder_spec.rb +79 -27
  31. data/spec/knapsack_pro/build_distribution_fetcher_spec.rb +89 -0
  32. data/spec/knapsack_pro/client/api/v1/build_distributions_spec.rb +31 -0
  33. data/spec/knapsack_pro/client/connection_spec.rb +165 -103
  34. data/spec/knapsack_pro/config/env_spec.rb +14 -0
  35. data/spec/knapsack_pro/queue_allocator_builder_spec.rb +7 -3
  36. data/spec/knapsack_pro/queue_allocator_spec.rb +7 -5
  37. data/spec/knapsack_pro/runners/rspec_runner_spec.rb +4 -4
  38. data/spec/knapsack_pro/slow_test_file_determiner_spec.rb +74 -0
  39. data/spec/knapsack_pro/slow_test_file_finder_spec.rb +43 -0
  40. data/spec/knapsack_pro/test_case_detectors/rspec_test_example_detector_spec.rb +81 -35
  41. data/spec/knapsack_pro/test_case_mergers/base_merger_spec.rb +27 -0
  42. data/spec/knapsack_pro/test_case_mergers/rspec_merger_spec.rb +59 -0
  43. data/spec/knapsack_pro/test_file_finder_spec.rb +105 -29
  44. data/spec/knapsack_pro/test_files_with_test_cases_composer_spec.rb +41 -0
  45. metadata +27 -10
@@ -8,8 +8,11 @@ describe KnapsackPro::AllocatorBuilder do
8
8
  subject { allocator_builder.allocator }
9
9
 
10
10
  before do
11
- test_files = double
12
- expect(allocator_builder).to receive(:test_files).and_return(test_files)
11
+ fast_and_slow_test_files_to_run = double
12
+ expect(allocator_builder).to receive(:fast_and_slow_test_files_to_run).and_return(fast_and_slow_test_files_to_run)
13
+
14
+ fallback_mode_test_files = double
15
+ expect(allocator_builder).to receive(:fallback_mode_test_files).and_return(fallback_mode_test_files)
13
16
 
14
17
  repository_adapter = double
15
18
  expect(KnapsackPro::RepositoryAdapterInitiator).to receive(:call).and_return(repository_adapter)
@@ -20,7 +23,8 @@ describe KnapsackPro::AllocatorBuilder do
20
23
  expect(KnapsackPro::Config::Env).to receive(:ci_node_index).and_return(ci_node_index)
21
24
 
22
25
  expect(KnapsackPro::Allocator).to receive(:new).with(
23
- test_files: test_files,
26
+ fast_and_slow_test_files_to_run: fast_and_slow_test_files_to_run,
27
+ fallback_mode_test_files: fallback_mode_test_files,
24
28
  ci_node_total: ci_node_total,
25
29
  ci_node_index: ci_node_index,
26
30
  repository_adapter: repository_adapter,
@@ -1,12 +1,14 @@
1
1
  describe KnapsackPro::Allocator do
2
- let(:test_files) { double }
2
+ let(:fast_and_slow_test_files_to_run) { double }
3
+ let(:fallback_mode_test_files) { double }
3
4
  let(:ci_node_total) { double }
4
5
  let(:ci_node_index) { double }
5
6
  let(:repository_adapter) { instance_double(KnapsackPro::RepositoryAdapters::EnvAdapter, commit_hash: double, branch: double) }
6
7
 
7
8
  let(:allocator) do
8
9
  described_class.new(
9
- test_files: test_files,
10
+ fast_and_slow_test_files_to_run: fast_and_slow_test_files_to_run,
11
+ fallback_mode_test_files: fallback_mode_test_files,
10
12
  ci_node_total: ci_node_total,
11
13
  ci_node_index: ci_node_index,
12
14
  repository_adapter: repository_adapter
@@ -20,7 +22,7 @@ describe KnapsackPro::Allocator do
20
22
 
21
23
  before do
22
24
  encrypted_test_files = double
23
- expect(KnapsackPro::Crypto::Encryptor).to receive(:call).with(test_files).and_return(encrypted_test_files)
25
+ expect(KnapsackPro::Crypto::Encryptor).to receive(:call).with(fast_and_slow_test_files_to_run).and_return(encrypted_test_files)
24
26
 
25
27
  encrypted_branch = double
26
28
  expect(KnapsackPro::Crypto::BranchEncryptor).to receive(:call).with(repository_adapter.branch).and_return(encrypted_branch)
@@ -64,7 +66,7 @@ describe KnapsackPro::Allocator do
64
66
  end
65
67
 
66
68
  before do
67
- expect(KnapsackPro::Crypto::Decryptor).to receive(:call).with(test_files, response['test_files']).and_call_original
69
+ expect(KnapsackPro::Crypto::Decryptor).to receive(:call).with(fast_and_slow_test_files_to_run, response['test_files']).and_call_original
68
70
  end
69
71
 
70
72
  it { should eq ['a_spec.rb', 'b_spec.rb'] }
@@ -114,7 +116,7 @@ describe KnapsackPro::Allocator do
114
116
  context 'when fallback mode started' do
115
117
  before do
116
118
  test_flat_distributor = instance_double(KnapsackPro::TestFlatDistributor)
117
- expect(KnapsackPro::TestFlatDistributor).to receive(:new).with(test_files, ci_node_total).and_return(test_flat_distributor)
119
+ expect(KnapsackPro::TestFlatDistributor).to receive(:new).with(fallback_mode_test_files, ci_node_total).and_return(test_flat_distributor)
118
120
  expect(test_flat_distributor).to receive(:test_files_for_node).with(ci_node_index).and_return([
119
121
  { 'path' => 'c_spec.rb' },
120
122
  { 'path' => 'd_spec.rb' },
@@ -100,8 +100,22 @@ describe KnapsackPro::BaseAllocatorBuilder do
100
100
  end
101
101
  end
102
102
 
103
- describe '#test_files' do
104
- subject { allocator_builder.test_files }
103
+ describe '#fallback_mode_test_files' do
104
+ subject { allocator_builder.fallback_mode_test_files }
105
+
106
+ it do
107
+ test_file_pattern = double
108
+ expect(KnapsackPro::TestFilePattern).to receive(:call).with(adapter_class).and_return(test_file_pattern)
109
+
110
+ test_files = double
111
+ expect(KnapsackPro::TestFileFinder).to receive(:call).with(test_file_pattern).and_return(test_files)
112
+
113
+ expect(subject).to eq test_files
114
+ end
115
+ end
116
+
117
+ describe '#fast_and_slow_test_files_to_run' do
118
+ subject { allocator_builder.fast_and_slow_test_files_to_run }
105
119
 
106
120
  context 'when looking for test files on disk by default' do
107
121
  it do
@@ -115,10 +129,10 @@ describe KnapsackPro::BaseAllocatorBuilder do
115
129
  end
116
130
  end
117
131
 
118
- context 'when RSpec adapter and rspec split by test examples is enabled' do
132
+ context 'when RSpec adapter AND rspec split by test examples is enabled' do
119
133
  let(:adapter_class) { KnapsackPro::Adapters::RSpecAdapter }
120
- let(:test_files) { double(size: 1000) }
121
- let(:cmd) { 'bundle exec rake knapsack_pro:rspec_test_example_detector' }
134
+ let(:test_files_to_run) { double }
135
+ let(:cmd) { 'RACK_ENV=test RAILS_ENV=test bundle exec rake knapsack_pro:rspec_test_example_detector' }
122
136
 
123
137
  before do
124
138
  expect(KnapsackPro::Config::Env).to receive(:rspec_split_by_test_examples?).and_return(true)
@@ -126,53 +140,91 @@ describe KnapsackPro::BaseAllocatorBuilder do
126
140
  test_file_pattern = double
127
141
  expect(KnapsackPro::TestFilePattern).to receive(:call).with(adapter_class).and_return(test_file_pattern)
128
142
 
129
- expect(KnapsackPro::TestFileFinder).to receive(:call).with(test_file_pattern).and_return(test_files)
143
+ expect(KnapsackPro::TestFileFinder).to receive(:call).with(test_file_pattern).and_return(test_files_to_run)
144
+ end
145
+
146
+ context 'when RSpec version < 3.3.0' do
147
+ before do
148
+ stub_const('RSpec::Core::Version::STRING', '3.2.0')
149
+ end
130
150
 
131
- expect(Kernel).to receive(:system).with(cmd).and_return(cmd_result)
151
+ it do
152
+ expect { subject }.to raise_error RuntimeError, 'RSpec >= 3.3.0 is required to split test files by test examples. Learn more: https://github.com/KnapsackPro/knapsack_pro-ruby#split-test-files-by-test-cases'
153
+ end
132
154
  end
133
155
 
134
156
  context 'when rake task to detect RSpec test examples works' do
157
+ let(:slow_test_files) { double(size: 5) }
135
158
  let(:cmd_result) { true }
136
159
  let(:test_file_example_paths) { double }
137
160
  let(:logger) { instance_double(Logger) }
161
+ let(:test_files_with_test_cases) { double }
138
162
 
139
163
  before do
140
- rspec_test_example_detector = instance_double(KnapsackPro::TestCaseDetectors::RSpecTestExampleDetector)
141
- expect(KnapsackPro::TestCaseDetectors::RSpecTestExampleDetector).to receive(:new).and_return(rspec_test_example_detector)
142
- expect(rspec_test_example_detector).to receive(:test_file_example_paths).and_return(test_file_example_paths)
164
+ expect(allocator_builder).to receive(:get_slow_test_files).and_return(slow_test_files)
143
165
 
144
- expect(KnapsackPro).to receive(:logger).at_least(1).and_return(logger)
145
- end
166
+ expect(KnapsackPro).to receive(:logger).and_return(logger)
146
167
 
147
- context 'when up to 1000 test files detected on disk' do
148
- let(:test_files) { double(size: 1000) }
168
+ expect(Kernel).to receive(:system).with(cmd).and_return(cmd_result)
149
169
 
150
- it do
151
- expect(logger).to receive(:warn).with("Generating RSpec test examples JSON report to prepare your test suite to be split by test examples (by individual 'it's. Thanks to that a single test file can be split across parallel CI nodes). Analyzing 1000 test files.")
170
+ rspec_test_example_detector = instance_double(KnapsackPro::TestCaseDetectors::RSpecTestExampleDetector)
171
+ expect(KnapsackPro::TestCaseDetectors::RSpecTestExampleDetector).to receive(:new).and_return(rspec_test_example_detector)
172
+ expect(rspec_test_example_detector).to receive(:test_file_example_paths).and_return(test_file_example_paths)
152
173
 
153
- expect(subject).to eq test_file_example_paths
154
- end
174
+ expect(KnapsackPro::TestFilesWithTestCasesComposer).to receive(:call).with(test_files_to_run, slow_test_files, test_file_example_paths).and_return(test_files_with_test_cases)
155
175
  end
156
176
 
157
- context 'when more than 1000 test files detected on disk' do
158
- let(:test_files) { double(size: 1001) }
159
-
160
- it do
161
- expect(logger).to receive(:warn).with("Generating RSpec test examples JSON report to prepare your test suite to be split by test examples (by individual 'it's. Thanks to that a single test file can be split across parallel CI nodes). Analyzing 1001 test files.")
162
- expect(logger).to receive(:warn).with('You have more than 1000 test files, it may take longer to generate test examples. Please wait...')
177
+ it do
178
+ expect(logger).to receive(:info).with("Generating RSpec test examples JSON report for slow test files to prepare it to be split by test examples (by individual 'it's. Thanks to that a single slow test file can be split across parallel CI nodes). Analyzing 5 slow test files.")
163
179
 
164
- expect(subject).to eq test_file_example_paths
165
- end
180
+ expect(subject).to eq test_files_with_test_cases
166
181
  end
167
182
  end
168
183
 
169
184
  context 'when rake task to detect RSpec test examples failed' do
185
+ let(:slow_test_files) { double(size: 5) }
170
186
  let(:cmd_result) { false }
171
187
 
188
+ before do
189
+ expect(allocator_builder).to receive(:get_slow_test_files).and_return(slow_test_files)
190
+
191
+ expect(Kernel).to receive(:system).with(cmd).and_return(cmd_result)
192
+ end
193
+
172
194
  it do
173
- expect { subject }.to raise_error(RuntimeError, 'Could not generate JSON report for RSpec. Rake task failed when running bundle exec rake knapsack_pro:rspec_test_example_detector')
195
+ expect { subject }.to raise_error(RuntimeError, 'Could not generate JSON report for RSpec. Rake task failed when running RACK_ENV=test RAILS_ENV=test bundle exec rake knapsack_pro:rspec_test_example_detector')
174
196
  end
175
197
  end
176
198
  end
177
199
  end
200
+
201
+ describe 'private #get_slow_test_files' do
202
+ subject { allocator_builder.send(:get_slow_test_files) }
203
+
204
+ before do
205
+ expect(KnapsackPro::Config::Env).to receive(:slow_test_file_pattern).and_return(slow_test_file_pattern)
206
+ end
207
+
208
+ context 'when slow test file pattern is present' do
209
+ let(:slow_test_files) { double(:slow_test_files_based_on_pattern, size: 3) }
210
+ let(:slow_test_file_pattern) { double }
211
+
212
+ before do
213
+ expect(KnapsackPro::TestFileFinder).to receive(:slow_test_files_by_pattern).with(adapter_class).and_return(slow_test_files)
214
+ end
215
+
216
+ it { expect(subject).to eq slow_test_files }
217
+ end
218
+
219
+ context 'when slow test file pattern is not present' do
220
+ let(:slow_test_files) { double(:slow_test_files_based_on_api, size: 2) }
221
+ let(:slow_test_file_pattern) { nil }
222
+
223
+ before do
224
+ expect(KnapsackPro::SlowTestFileFinder).to receive(:call).with(adapter_class).and_return(slow_test_files)
225
+ end
226
+
227
+ it { expect(subject).to eq slow_test_files }
228
+ end
229
+ end
178
230
  end
@@ -0,0 +1,89 @@
1
+ describe KnapsackPro::BuildDistributionFetcher do
2
+ describe '.call' do
3
+ subject { described_class.call }
4
+
5
+ it do
6
+ build_distribution_fetcher = instance_double(described_class)
7
+ expect(described_class).to receive(:new).and_return(build_distribution_fetcher)
8
+ result = double
9
+ expect(build_distribution_fetcher).to receive(:call).and_return(result)
10
+
11
+ expect(subject).to eq result
12
+ end
13
+ end
14
+
15
+ describe '#call' do
16
+ let(:ci_node_total) { double }
17
+ let(:ci_node_index) { double }
18
+ let(:repository_adapter) { instance_double(KnapsackPro::RepositoryAdapters::EnvAdapter, commit_hash: double, branch: double) }
19
+
20
+ subject { described_class.new.call }
21
+
22
+ before do
23
+ expect(KnapsackPro::RepositoryAdapterInitiator).to receive(:call).and_return(repository_adapter)
24
+
25
+ expect(KnapsackPro::Config::Env).to receive(:ci_node_total).and_return(ci_node_total)
26
+ expect(KnapsackPro::Config::Env).to receive(:ci_node_index).and_return(ci_node_index)
27
+
28
+ action = double
29
+ expect(KnapsackPro::Client::API::V1::BuildDistributions).to receive(:last).with({
30
+ commit_hash: repository_adapter.commit_hash,
31
+ branch: repository_adapter.branch,
32
+ node_total: ci_node_total,
33
+ node_index: ci_node_index,
34
+ }).and_return(action)
35
+
36
+ connection = instance_double(KnapsackPro::Client::Connection,
37
+ call: response,
38
+ success?: success?,
39
+ errors?: errors?)
40
+ expect(KnapsackPro::Client::Connection).to receive(:new).with(action).and_return(connection)
41
+ end
42
+
43
+ context 'when successful request to API' do
44
+ let(:success?) { true }
45
+
46
+ context 'when response has errors' do
47
+ let(:errors?) { true }
48
+ let(:response) { 'fake error response' }
49
+
50
+ it do
51
+ expect { subject }.to raise_error(ArgumentError, response)
52
+ end
53
+ end
54
+
55
+ context 'when response has no errors' do
56
+ let(:errors?) { false }
57
+ let(:response) do
58
+ {
59
+ 'build_distribution_id' => 'be2b95b1-1b8b-43a3-9d66-cabebbf135b8',
60
+ 'time_execution' => 2.5,
61
+ 'test_files' => [
62
+ { 'path' => 'a_spec.rb', 'time_execution' => 1.5 },
63
+ { 'path' => 'b_spec.rb', 'time_execution' => 1.0 },
64
+ ]
65
+ }
66
+ end
67
+
68
+ it { expect(subject).to be_a described_class::BuildDistributionEntity }
69
+ it { expect(subject.time_execution).to eq 2.5 }
70
+ it do
71
+ expect(subject.test_files).to eq([
72
+ { 'path' => 'a_spec.rb', 'time_execution' => 1.5 },
73
+ { 'path' => 'b_spec.rb', 'time_execution' => 1.0 },
74
+ ])
75
+ end
76
+ end
77
+ end
78
+
79
+ context 'when not successful request to API' do
80
+ let(:success?) { false }
81
+ let(:errors?) { false }
82
+ let(:response) { double }
83
+
84
+ it { expect(subject).to be_a described_class::BuildDistributionEntity }
85
+ it { expect(subject.time_execution).to eq 0.0 }
86
+ it { expect(subject.test_files).to eq([]) }
87
+ end
88
+ end
89
+ end
@@ -38,4 +38,35 @@ describe KnapsackPro::Client::API::V1::BuildDistributions do
38
38
  expect(subject).to eq action
39
39
  end
40
40
  end
41
+
42
+ describe '.last' do
43
+ let(:commit_hash) { double }
44
+ let(:branch) { double }
45
+ let(:node_total) { double }
46
+ let(:node_index) { double }
47
+
48
+ subject do
49
+ described_class.last(
50
+ commit_hash: commit_hash,
51
+ branch: branch,
52
+ node_total: node_total,
53
+ node_index: node_index,
54
+ )
55
+ end
56
+
57
+ it do
58
+ action = double
59
+ expect(KnapsackPro::Client::API::Action).to receive(:new).with({
60
+ endpoint_path: '/v1/build_distributions/last',
61
+ http_method: :get,
62
+ request_hash: {
63
+ commit_hash: commit_hash,
64
+ branch: branch,
65
+ node_total: node_total,
66
+ node_index: node_index,
67
+ }
68
+ }).and_return(action)
69
+ expect(subject).to eq action
70
+ end
71
+ end
41
72
  end
@@ -1,167 +1,229 @@
1
+ shared_examples 'when request got response from API' do
2
+ context 'when body response is JSON and API response code is 400' do
3
+ let(:body) { '{"errors": "value"}' }
4
+ let(:code) { '400' } # it must be string code
5
+
6
+ before do
7
+ expect(KnapsackPro).to receive(:logger).exactly(4).and_return(logger)
8
+ expect(logger).to receive(:debug).with("#{expected_http_method} http://api.knapsackpro.test:3000/v1/fake_endpoint")
9
+ expect(logger).to receive(:debug).with('API request UUID: fake-uuid')
10
+ expect(logger).to receive(:debug).with('API response:')
11
+ end
12
+
13
+ it do
14
+ parsed_response = { 'errors' => 'value' }
15
+
16
+ expect(logger).to receive(:error).with(parsed_response)
17
+
18
+ expect(subject).to eq(parsed_response)
19
+ expect(connection.success?).to be true
20
+ expect(connection.errors?).to be true
21
+ end
22
+ end
23
+
24
+ context 'when body response is JSON with build_distribution_id' do
25
+ let(:body) { '{"build_distribution_id": "seed-uuid"}' }
26
+ let(:code) { '200' } # it must be string code
27
+
28
+ before do
29
+ expect(KnapsackPro).to receive(:logger).exactly(5).and_return(logger)
30
+ expect(logger).to receive(:debug).with("#{expected_http_method} http://api.knapsackpro.test:3000/v1/fake_endpoint")
31
+ expect(logger).to receive(:debug).with('API request UUID: fake-uuid')
32
+ expect(logger).to receive(:debug).with("Test suite split seed: seed-uuid")
33
+ expect(logger).to receive(:debug).with('API response:')
34
+ end
35
+
36
+ it do
37
+ parsed_response = { 'build_distribution_id' => 'seed-uuid' }
38
+
39
+ expect(logger).to receive(:debug).with(parsed_response)
40
+
41
+ expect(subject).to eq(parsed_response)
42
+ expect(connection.success?).to be true
43
+ expect(connection.errors?).to be false
44
+ end
45
+ end
46
+
47
+ context 'when body response is empty' do
48
+ let(:body) { '' }
49
+ let(:code) { '200' } # it must be string code
50
+
51
+ before do
52
+ expect(KnapsackPro).to receive(:logger).exactly(4).and_return(logger)
53
+ expect(logger).to receive(:debug).with("#{expected_http_method} http://api.knapsackpro.test:3000/v1/fake_endpoint")
54
+ expect(logger).to receive(:debug).with('API request UUID: fake-uuid')
55
+ expect(logger).to receive(:debug).with('API response:')
56
+ end
57
+
58
+ it do
59
+ expect(logger).to receive(:debug).with('')
60
+
61
+ expect(subject).to eq('')
62
+ expect(connection.success?).to be true
63
+ expect(connection.errors?).to be false
64
+ end
65
+ end
66
+ end
67
+
68
+ shared_examples 'when retry request' do
69
+ context 'when body response is JSON and API response code is 500' do
70
+ let(:body) { '{"error": "Internal Server Error"}' }
71
+ let(:code) { '500' } # it must be string code
72
+
73
+ before do
74
+ expect(KnapsackPro).to receive(:logger).at_least(1).and_return(logger)
75
+ expect(logger).to receive(:debug).exactly(3).with("#{expected_http_method} http://api.knapsackpro.test:3000/v1/fake_endpoint")
76
+ expect(logger).to receive(:debug).exactly(3).with('API request UUID: fake-uuid')
77
+ expect(logger).to receive(:debug).exactly(3).with('API response:')
78
+ end
79
+
80
+ it do
81
+ parsed_response = { 'error' => 'Internal Server Error' }
82
+
83
+ expect(logger).to receive(:error).exactly(3).with(parsed_response)
84
+
85
+ server_error = described_class::ServerError.new(parsed_response)
86
+ expect(logger).to receive(:warn).exactly(3).with(server_error.inspect)
87
+
88
+ expect(logger).to receive(:warn).with("Wait 4s and retry request to Knapsack Pro API.")
89
+ expect(logger).to receive(:warn).with("Wait 8s and retry request to Knapsack Pro API.")
90
+ expect(Kernel).to receive(:sleep).with(4)
91
+ expect(Kernel).to receive(:sleep).with(8)
92
+
93
+ expect(subject).to eq(parsed_response)
94
+
95
+ expect(connection.success?).to be false
96
+ expect(connection.errors?).to be true
97
+ end
98
+ end
99
+ end
100
+
1
101
  describe KnapsackPro::Client::Connection do
2
102
  let(:endpoint_path) { '/v1/fake_endpoint' }
3
- let(:http_method) { :post }
4
103
  let(:request_hash) { { fake: 'hash' } }
104
+ let(:http_method) { :post }
5
105
  let(:action) do
6
106
  instance_double(KnapsackPro::Client::API::Action,
7
107
  endpoint_path: endpoint_path,
8
108
  http_method: http_method,
9
109
  request_hash: request_hash)
10
110
  end
111
+ let(:test_suite_token) { '3fa64859337f6e56409d49f865d13fd7' }
11
112
 
12
113
  let(:connection) { described_class.new(action) }
13
114
 
14
115
  before do
15
116
  stub_const('ENV', {
16
117
  'KNAPSACK_PRO_ENDPOINT' => 'http://api.knapsackpro.test:3000',
17
- 'KNAPSACK_PRO_TEST_SUITE_TOKEN' => '3fa64859337f6e56409d49f865d13fd7',
118
+ 'KNAPSACK_PRO_TEST_SUITE_TOKEN' => test_suite_token,
18
119
  })
19
120
  end
20
121
 
21
122
  describe '#call' do
22
123
  let(:logger) { instance_double(Logger) }
124
+ let(:http) { instance_double(Net::HTTP) }
125
+ let(:http_response) do
126
+ header = { 'X-Request-Id' => 'fake-uuid' }
127
+ instance_double(Net::HTTPOK, body: body, header: header, code: code)
128
+ end
23
129
 
24
130
  subject { connection.call }
25
131
 
26
- context 'when http method is POST' do
27
- before do
28
- http = instance_double(Net::HTTP)
132
+ before do
133
+ expect(Net::HTTP).to receive(:new).with('api.knapsackpro.test', 3000).and_return(http)
29
134
 
30
- expect(Net::HTTP).to receive(:new).with('api.knapsackpro.test', 3000).and_return(http)
135
+ expect(http).to receive(:use_ssl=).with(false)
136
+ expect(http).to receive(:open_timeout=).with(15)
137
+ expect(http).to receive(:read_timeout=).with(15)
138
+ end
31
139
 
32
- expect(http).to receive(:use_ssl=).with(false)
33
- expect(http).to receive(:open_timeout=).with(15)
34
- expect(http).to receive(:read_timeout=).with(15)
140
+ context 'when http method is POST' do
141
+ let(:http_method) { :post }
35
142
 
36
- header = { 'X-Request-Id' => 'fake-uuid' }
37
- http_response = instance_double(Net::HTTPOK, body: body, header: header, code: code)
143
+ before do
38
144
  expect(http).to receive(:post).with(
39
145
  endpoint_path,
40
- "{\"fake\":\"hash\",\"test_suite_token\":\"3fa64859337f6e56409d49f865d13fd7\"}",
146
+ request_hash.to_json,
41
147
  {
42
148
  'Content-Type' => 'application/json',
43
149
  'Accept' => 'application/json',
44
150
  'KNAPSACK-PRO-CLIENT-NAME' => 'knapsack_pro-ruby',
45
151
  'KNAPSACK-PRO-CLIENT-VERSION' => KnapsackPro::VERSION,
152
+ 'KNAPSACK-PRO-TEST-SUITE-TOKEN' => test_suite_token,
46
153
  }
47
154
  ).and_return(http_response)
48
155
  end
49
156
 
50
- context 'when body response is json and API response code is 400' do
51
- let(:body) { '{"errors": "value"}' }
52
- let(:code) { '400' } # it must be string code
53
-
54
- before do
55
- expect(KnapsackPro).to receive(:logger).exactly(3).and_return(logger)
56
- expect(logger).to receive(:debug).with('API request UUID: fake-uuid')
57
- expect(logger).to receive(:debug).with('API response:')
58
- end
59
-
60
- it do
61
- parsed_response = { 'errors' => 'value' }
62
-
63
- expect(logger).to receive(:error).with(parsed_response)
64
-
65
- expect(subject).to eq(parsed_response)
66
- expect(connection.success?).to be true
67
- expect(connection.errors?).to be true
68
- end
157
+ it_behaves_like 'when request got response from API' do
158
+ let(:expected_http_method) { 'POST' }
69
159
  end
160
+ end
70
161
 
71
- context 'when body response is json with build_distribution_id' do
72
- let(:body) { '{"build_distribution_id": "seed-uuid"}' }
73
- let(:code) { '200' } # it must be string code
74
-
75
- before do
76
- expect(KnapsackPro).to receive(:logger).exactly(4).and_return(logger)
77
- expect(logger).to receive(:debug).with('API request UUID: fake-uuid')
78
- expect(logger).to receive(:debug).with("Test suite split seed: seed-uuid")
79
- expect(logger).to receive(:debug).with('API response:')
80
- end
81
-
82
- it do
83
- parsed_response = { 'build_distribution_id' => 'seed-uuid' }
84
-
85
- expect(logger).to receive(:debug).with(parsed_response)
162
+ context 'when http method is GET' do
163
+ let(:http_method) { :get }
86
164
 
87
- expect(subject).to eq(parsed_response)
88
- expect(connection.success?).to be true
89
- expect(connection.errors?).to be false
90
- end
165
+ before do
166
+ uri = URI.parse("http://api.knapsackpro.test:3000#{endpoint_path}")
167
+ uri.query = URI.encode_www_form(request_hash)
168
+ expect(http).to receive(:get).with(
169
+ uri,
170
+ {
171
+ 'Content-Type' => 'application/json',
172
+ 'Accept' => 'application/json',
173
+ 'KNAPSACK-PRO-CLIENT-NAME' => 'knapsack_pro-ruby',
174
+ 'KNAPSACK-PRO-CLIENT-VERSION' => KnapsackPro::VERSION,
175
+ 'KNAPSACK-PRO-TEST-SUITE-TOKEN' => test_suite_token,
176
+ }
177
+ ).and_return(http_response)
91
178
  end
92
179
 
93
- context 'when body response is empty' do
94
- let(:body) { '' }
95
- let(:code) { '200' } # it must be string code
96
-
97
- before do
98
- expect(KnapsackPro).to receive(:logger).exactly(3).and_return(logger)
99
- expect(logger).to receive(:debug).with('API request UUID: fake-uuid')
100
- expect(logger).to receive(:debug).with('API response:')
101
- end
102
-
103
- it do
104
- expect(logger).to receive(:debug).with('')
105
-
106
- expect(subject).to eq('')
107
- expect(connection.success?).to be true
108
- expect(connection.errors?).to be false
109
- end
180
+ it_behaves_like 'when request got response from API' do
181
+ let(:expected_http_method) { 'GET' }
110
182
  end
111
183
  end
112
184
 
113
185
  context 'when retry request for http method POST' do
114
- before do
115
- http = instance_double(Net::HTTP)
186
+ let(:http_method) { :post }
116
187
 
117
- expect(Net::HTTP).to receive(:new).exactly(3).with('api.knapsackpro.test', 3000).and_return(http)
118
-
119
- expect(http).to receive(:use_ssl=).exactly(3).with(false)
120
- expect(http).to receive(:open_timeout=).exactly(3).with(15)
121
- expect(http).to receive(:read_timeout=).exactly(3).with(15)
122
-
123
- header = { 'X-Request-Id' => 'fake-uuid' }
124
- http_response = instance_double(Net::HTTPOK, body: body, header: header, code: code)
188
+ before do
125
189
  expect(http).to receive(:post).exactly(3).with(
126
190
  endpoint_path,
127
- "{\"fake\":\"hash\",\"test_suite_token\":\"3fa64859337f6e56409d49f865d13fd7\"}",
191
+ request_hash.to_json,
128
192
  {
129
193
  'Content-Type' => 'application/json',
130
194
  'Accept' => 'application/json',
131
195
  'KNAPSACK-PRO-CLIENT-NAME' => 'knapsack_pro-ruby',
132
196
  'KNAPSACK-PRO-CLIENT-VERSION' => KnapsackPro::VERSION,
197
+ 'KNAPSACK-PRO-TEST-SUITE-TOKEN' => test_suite_token,
133
198
  }
134
199
  ).and_return(http_response)
135
200
  end
136
201
 
137
- context 'when body response is json and API response code is 500' do
138
- let(:body) { '{"error": "Internal Server Error"}' }
139
- let(:code) { '500' } # it must be string code
140
-
141
- before do
142
- expect(KnapsackPro).to receive(:logger).at_least(1).and_return(logger)
143
- expect(logger).to receive(:debug).exactly(3).with('API request UUID: fake-uuid')
144
- expect(logger).to receive(:debug).exactly(3).with('API response:')
145
- end
146
-
147
- it do
148
- parsed_response = { 'error' => 'Internal Server Error' }
149
-
150
- expect(logger).to receive(:error).exactly(3).with(parsed_response)
151
-
152
- server_error = described_class::ServerError.new(parsed_response)
153
- expect(logger).to receive(:warn).exactly(3).with(server_error.inspect)
202
+ it_behaves_like 'when retry request' do
203
+ let(:expected_http_method) { 'POST' }
204
+ end
205
+ end
154
206
 
155
- expect(logger).to receive(:warn).with("Wait 4s and retry request to Knapsack Pro API.")
156
- expect(logger).to receive(:warn).with("Wait 8s and retry request to Knapsack Pro API.")
157
- expect(Kernel).to receive(:sleep).with(4)
158
- expect(Kernel).to receive(:sleep).with(8)
207
+ context 'when retry request for http method GET' do
208
+ let(:http_method) { :get }
159
209
 
160
- expect(subject).to eq(parsed_response)
210
+ before do
211
+ uri = URI.parse("http://api.knapsackpro.test:3000#{endpoint_path}")
212
+ uri.query = URI.encode_www_form(request_hash)
213
+ expect(http).to receive(:get).exactly(3).with(
214
+ uri,
215
+ {
216
+ 'Content-Type' => 'application/json',
217
+ 'Accept' => 'application/json',
218
+ 'KNAPSACK-PRO-CLIENT-NAME' => 'knapsack_pro-ruby',
219
+ 'KNAPSACK-PRO-CLIENT-VERSION' => KnapsackPro::VERSION,
220
+ 'KNAPSACK-PRO-TEST-SUITE-TOKEN' => test_suite_token,
221
+ }
222
+ ).and_return(http_response)
223
+ end
161
224
 
162
- expect(connection.success?).to be false
163
- expect(connection.errors?).to be true
164
- end
225
+ it_behaves_like 'when retry request' do
226
+ let(:expected_http_method) { 'GET' }
165
227
  end
166
228
  end
167
229
  end